Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How far should I go with unit testing?

Tags:

unit-testing

I'm trying to unit test in a personal PHP project like a good little programmer, and I'd like to do it correctly. From what I hear what you're supposed to test is just the public interface of a method, but I was wondering if that would still apply to below.

I have a method that generates a password reset token in the event that a user forgets his or her password. The method returns one of two things: nothing (null) if everything worked fine, or an error code signifying that the user with the specified username doesn't exist.

If I'm only testing the public interface, how can I be sure that the password reset token IS going in the database if the username is valid, and ISN'T going in the database if the username is NOT valid? Should I do queries in my tests to validate this? Or should I just kind of assume that my logic is sound?

Now this method is very simple and this isn't that big of a deal - the problem is that this same situation applies to many other methods. What do you do in database centric unit tests?

Code, for reference if needed:

public function generatePasswordReset($username)
{
    $this->sql='SELECT  id
                FROM    users
                WHERE   username = :username';

    $this->addParam(':username', $username);
    $user=$this->query()->fetch();

    if (!$user)
        return self::$E_USER_DOESNT_EXIST;
    else
    {
        $code=md5(uniqid());
        $this->addParams(array(':uid'        => $user['id'],
                               ':code'       => $code,
                               ':duration'   => 24 //in hours, how long reset is valid
                              ));

        //generate new code, delete old one if present
        $this->sql ='DELETE FROM password_resets WHERE user_id=:uid;';
        $this->sql.="INSERT INTO password_resets (user_id, code, expires)
                     VALUES      (:uid, :code, now() + interval ':duration hours')";

        $this->execute();
    }
}
like image 443
ryeguy Avatar asked Jul 29 '09 13:07

ryeguy


People also ask

How much should be unit test coverage?

Minimum Test Coverage Rate: Keeping it between 60 - 70%. Optimal Test Coverage Rate: Keeping it between 70 - 80%. Overkill Test Coverage Rate: Keeping it between 80 - 100%. N.B: The above are my personal opinion and it may vary depending upon specific system design or requirements.

How long should unit tests run?

Still, it seems as though a 10 second short-term attention span is more or less hard-wired into the human brain. Thus, a unit test suite used for TDD should run in less than 10 seconds. If it's slower, you'll be less productive because you'll constantly lose focus.

How long does it take to learn unit testing?

Typical time budgeted on writing unit tests is about 1 day for every feature that takes 3-4 days of heads down coding.

How often should you run unit tests?

Run all your unit tests as often as possible, ideally every time the code is changed. Make sure all your unit tests always run at 100%. Frequent testing gives you confidence that your changes didn't break anything and generally lowers the stress of programming in the dark.


2 Answers

The great thing about unit testing, for me at least, is that it shows you where you need to refactor. Using your sample code above, you've basically got four things happening in one method:

//1. get the user from the DB
//2. in a big else, check if user is null
//3. create a array containing the userID, a code, and expiry
//4. delete any existing password resets
//5. create a new password reset

Unit testing is also great because it helps highlight dependencies. This method, as shown above, is dependent on a DB, rather than an object that implements an interface. This method interacts with systems outside its scope, and really could only be tested with an integration test, rather than a unit test. Unit tests are for ensuring the working/correctness of a unit of work.

Consider the Single Responsibility Principle: "Do one thing". It applies to methods as well as classes.

I'd suggest that your generatePasswordReset method should be refactored to:

  • be given a pre-defined existing user object/id. Do all those sanity checks outside of this method. Do one thing.
  • put the password reset code into its own method. That would be a single unit of work that could be tested independently of the SELECT, DELETE and INSERT.
  • Make a new method that could be called OverwriteExistingPwdChangeRequests() which would take care of the DELETE + INSERT.
like image 172
p.campbell Avatar answered Nov 05 '22 10:11

p.campbell


The reason this function is more difficult to unit test is because the database update is a side-effect of the function (i.e. there is no explicit return for you to test).

One way of dealing with state updates on remote objects like this is to create a mock object that provides the same interface as the DB (i.e. it looks identical from the perspective of your code). Then in your test you can check the state changes within this mock object and confirm you have received what you should.

like image 39
ire_and_curses Avatar answered Nov 05 '22 10:11

ire_and_curses