In the xUnit world, tests are code. While there are testing tools which treats tests as data, phpunit and companions recognize classes and objects: this means that they are first class citizens and there should be no distinction in importance between production and test code.
Why it is important to refactor production code? To improve maintainability and ensure that changes which break the system appear less often. The same can be said for the tests: a suite that embrace change and is maintainable will make the developers actually use it, from the start to the long run. While the focus is usually on production code refactoring, today we will talk about test refactoring and the patterns where you should head to.
The worst thing that can happen is having an outdated test suite which fails because it is not maintained with production code: it will quickly lose credibility and thus it will be run sparingly, and then forgotten.
One of the best methodologies to improve production code maintainability is to test it: the more easy to test is a class, the more decoupled and maintainable it becomes. You will often find yourself refactoring a production class to simplify testing, for instance making Demeter happy, resulting in the application to have a simpler design.
Following our duality of production and test code, sometimes test methods and test cases grow and present a lot of repetition. What can be done to avoid these problems and maintain an agile (with the lowercase a) test suite is to refactor test code towards some patterns, some of them you already started to grasp in the previous parts of this series. Test code has often a low complexity compared to production code: it runs in an isolated environment, with nearly no state to maintain, with very decoupled classes (the test cases) and the help of a framework. Thus, it's tempting to use a lot of copy&paste to write tests, but knowing a bunch of patterns can flatten even tests little complexity and help you avoid duplication. As all patterns, they have been catalogued and given a standard name.
- Standard Fixture and Fresh Fixture reuse the code which builds fixtures for the tests (and not the fixture itself). This pattern can be implemented with phpunit setUp() method.
- Shared Fixture reuse the same object graph for a set of tests: obviously it should have no state or a way to reach a particular state for testing purposes. This pattern can be implemented with phpunit setUpBeforeClass() method.
- Four Phase Test is the classical motif of a test method: arrange, act, assert and the optional teardown.
- Test Runner and Test Suite Object are pattern which phpunit implements for you. You can then specifiy metadata to alter the building of a test suite or execution options, or specifical annotations which the runner supports.
- State Verification is the simplest way of using phpunit and it's what we have done until now, writing assertions on explicit results of the system under test. Behavior Verification is based on making assertiong on indirect results, like method calls on collaborators and will be treated in the next parts of this series; Mock and Stub are patterns used in Behavior Verification, and phpunit provides support for their dynamic creation.
- Table Truncation Teardown and Transaction RollBack TearDown are standard patterns for testing components which interact with a database.
- Literal, Derived and Generated Value are patterns to provide fake data to the system under test. They all have their place in unit testing, depending on the unit purpose.
Moreover, remember that test code is still code and the basic refactorings like Extract Method, Extract Superclass, Introduce Explaining Variable etc. are valid also in the testing land. Simply refactor some boilerplate code in private methods of a test case can save you the boring job of updating duplicated blocks.
As a side note, remember that when refactoring production code you have the safety net of the test suite, that will tell you when you have just broke something. No one tests the tests, however, and so you may want to temporarily break the behavior under test before refactoring a method or a test case. Simply altering the return statements of production methods can make the test fail so you can control that it continue to fail after the refactoring. When writing the original test, the TDD methodology crafts the method even before the production code exists and this is one of the main reason why the test is solid; a test is valid if it's able to find problems in the production code: that is, failing when it should fail.
I hope this series is becoming interesting as now you have learned your tests have the same importance of the production code. They can even be more important: if I have to choose between throwing away the production code and its documentation, and losing a good test suite, I will definitely throw away the first. It can be rewritten using the tests, while writing a complete test suite of an application without any tests is an harder task.
In the next parts, we'll enter the world of Test Doubles and of behavior verification, taking advantage of Mocks, Stubs, Fakes and similar pattern.
You may want to subscribe to the feed to remain updated about new articles in this series.