How far should I go for unit testing?

I am trying to unit test in a personal PHP project as a good little programmer, and I would like to do it right. From what I heard, you should check what you should test, this is just the method’s open interface, but I was wondering if this would apply below.

I have a method that generates a reset password token in case the user forgets his password. The method returns one of two things: nothing (null) if everything works fine, or an error code indicating that the user with the specified username does not exist.

If I test only the public interface, how can I be sure that the reset token will be in your database if the username is valid and DOES NOT go to the database if the username is NOT valid? Do I have to execute queries in my tests to verify this? Or am I just assuming my logic sounds?

Now this method is very simple, and this is not the point, but the fact that this situation applies to many other methods. What do you do in the unit test unit tests?

Code, for reference, if necessary:

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(); } } 
+6
unit testing
source share
8 answers

The great thing for unit testing, at least for me, is that it shows you where you need to refactor. Using your sample code above, you basically got four things 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, depends on the database, and not on the object that implements the interface. This method interacts with systems beyond its capabilities and can really only be tested with an integration test, and not with a unit test. Unit tests are designed to ensure performance / correct operation.

Consider the Principle of One Responsibility : Do one thing . "This applies to both methods and classes.

I would suggest that your generatePasswordReset method should be reorganized to:

  • a predefined existing user object / id is specified. Perform all of these sanity checks outside of this method. Do one thing.
  • enter the reset password into your own method. This will be a single unit of work that can be tested independently of SELECT , DELETE and INSERT .
  • Create a new method, which could be called OverwriteExistingPwdChangeRequests() , which takes care of DELETE + INSERT.
+6
source share

The reason this function is more complex than unit test is that updating the database is a side effect of the function (i.e. there is no explicit return for testing).

One way to handle state updates on remote objects such as this is to create a mock object that provides the same interface as the database (that is, it looks identical from the point of view of your code). Then in your test you can check the state changes in this mock object and confirm that you got what you should.

+3
source share

You can break it a little more, this function does a lot, which makes testing a little complicated, not impossible, but complicated. If, on the other hand, you pulled out some smaller additional functions (getUserByUsername, deletePasswordByUserID, addPasswordByUserId, etc. Then you can check them easily enough once and know that they work, so you do not need to retest them. Thus , you check the bottom ones to make sure they sound, so you don’t need to worry about them further down the chain, then for this function you just need to drop the user who does not exist and make sure that he returns with the error USER_DOESNT_EXIST where user noun It exists (this is where you test the DB). Domestic work has already been carried out differently, where (hopefully).

+1
source share

Unit tests are used to verify the health of the device. If you want to know if the device is working or not, write a test. It is so simple. The choice of the unit test record or should not be based on any diagram or rule of thumb. As a professional, it is your responsibility to deliver the working code, and you cannot know if it works or not if you do not test it.

Now this does not mean that you write a test for each line of code. It also does not mean that you write unit test for each separate function. The decision to test or not to test a specific unit of work comes down to risk. How much are you willing to risk having your piece of untested code deployed?

If you ask yourself “how do I know if this function works,” the answer is “you do not do it until you have repeated tests proving that it works.”

+1
source share

In general, you can make fun of the called object by checking that it receives the expected requests.

In this case, I'm not sure how useful this is, you end up writing the same logic twice ... we thought we sent “DELETE with password”, etc. Oh, let's see what we did!

Hmmm, what we actually checked. If the string was poorly formed, we do not know!

Perhaps this contradicts the letter of the law of unit testing, but I would instead test these side effects by doing separate database queries.

0
source share

Testing the public interface is necessary, but not enough. There are many philosophies about how many trials are required, and I can only give my own opinion. Try it all. Literally. You should have a test that checks that each line of code is implemented by a set of tests. (I only say "every line" because I think of C and gcov, and gcov provides line level granularity. If you have a finer resolution tool, use it.) If you can add a piece of code to your code base without adding a test, the test suite should fail.

0
source share

Databases are global variables. Global variables are public interfaces for each unit that uses them. Therefore, your test cases should include not only the parameters of the function, but also the input data of the database.

0
source share

If your device tests have side effects (like changing a database), they become integration tests . There is nothing wrong with integration tests; any automated testing is appropriate for the quality of your product. But integration tests have a higher maintenance cost because they are more complex and easier to break.

The trick is to minimize code that can only be tested with side effects. Isolate and hide SQL queries in a separate MyDatabase class that does not contain any business logic. Pass an instance of this object to the business logic code.

Then, when you unit test your business logic, you can substitute the MyDatabase object MyDatabase a mock instance that is not connected to a real database, and which can be used to verify the correctness of your business logic code.

See the SimpleTest documentation (flash fake framework) for an example.

0
source share

All Articles