OCTOBER 24, 2013
Covering every line is not the standard
Getting to 100% Unit Test code coverage should not mean ignoring some code fragments, and should never mean ignoring the contract your code makes with the rest of the codebase and with users of you services. Good unit testing will be covering all assertions and promises code is making to the rest of the world.
Covering trivial methods, for trivial cost
Some functions/methods may be comprising a single code path of a line or two, and using only built-in functionality of the language. Functions can change over time, so, even though a functions is trivial now, I recommend covering it anyway. Covering a trivial function is a trivial task, and what's more important is guaranteeing the contract code is providing. So, rather than basing coverage on trivialness, I recommend thinking of the contract, we're making with consumers of that code. Cover your bases by ensuring code is delivering what it promises to do.
In PHPUnit, I am ignoring code blocks by adding @ignoreCodeCoverage above methods, or @ignoreCodeCoverageStart and @ignoreCodeCoverageEnd around blocks of code that are trivial and not affecting the code's contract.
The Code Contract
Each module or service is advertising an implied contract with it's consumers, through public methods and APIs. It is promising it's classes, methods, functions and variables behave functionally under some set of conditions. These promises add up to a code contract. Defining interfaces or abstract classes better defines the public promises and the contract, but being more specific to enforce those promises is necessary for full coverage. Unit Testing define the promises code is making about the contents of inputs and outputs, and describe both the capabilities and limitations of functionality.
Interfaces, Abstract methods, and Containers
Defining dat is the first step to creating a better code contract. Implementing an Interface quickly tells consumers the bare minimum required by that class, but does not define whether that class and how that class should be used. Making a clear breach of contract, by breaking the interface requirement shows a giant red squiggly line and other errors in most IDEs.
The Interface is enforcing the terms of the contract, by requiring the consumer implementing SubscriptionEmailPayloadBuilder implement a method called queue_email_payload.
Now that I am implementing the queue_email_payload, I am fulfilling my duty under the Interface contract. I am not, however, doing anything inside the method body. The Abstract and Interface are only requiring the input and/or the output be defined and implemented. Defining a deeper relationship between specific inputs and outputs is what 100% code coverage is about.
Unit testing, and defining the input/output case by case
Defining input and output is creating an expectation for code which consumers can rely on. Taking it a step further means making a more specific promise, by defining the way functionality is responding to test cases.
Above, I am promising that my code will set $some_boolean_value to true. Consumers can read my unit test, seeing that promise, and rely on it. If I am demonstrating responses for all of the expected conditions, then I am defining a contract telling consumers how to fulfill their end of the deal. Making more meaningful and more thorough assertions creates more detailed expectations and an easier contract to uphold.
Below I am promising that if calling my method queue_email_payload with new objects() of the types I am requiring, my method will return true every time.
I am going to also want to define how my method is treating the objects it is expecting. Defining how my method handles other combinations of objects is important. It will likely be returning different values, throwing exceptions, and perhaps doing some writing to the database. It is in my best interest to continue defining all of the things I am expecting my code to do given the full array of practical uses of my code. Consumers can depend on unit tested code to do what is being tested. They are more likely to implement well tested code more in accordance with it's intended usage.
Expecting for the Unexpected
Code can be written to perform a very specific task: receiving objects, and responding with objects. It can be designed to expect to receive what it knows how to use. Throw an exception, when consumers input bad data, setting a clear expectation of what is right and what is wrong. Doing this ensures that functionality is being communicated. My method will be better defining what it is meant to do by throwing exceptions when it is not getting what it needs.
Throwing exceptions is handling an unexpected input or dependency state, and my unit tests are expecting this un-expectation.
I am expecting the unexpected, yes I said that (well, sort of.) I am telling the consumer that my code will make a loud noise when using it to queue an email about a frog product. My code is not being designed to send frog email.
Using more of PHPUnit's capabilities is awesome because I am telling other coders more about how I am expecting them to use my code.