Practical PHP Testing Patterns: Testcase Class per Feature
Join the DZone community and get the full member experience.
Join For FreeThere are cases when you want to break the parallelism between production code classes and test cases. Let's start from some of these scenarios.
You may have too much things to test in a single class, which presents many methods in its public Api. Or you may want to get a better picture of what a group of classes does.
Then break down the giant Testcase Class derived from the Testcase Class per Class pattern in two or more test cases.
The criterion for dividing the Test Methods is their relatedness to different features. By feature, a generic term, I mean a set of operations used together, but we'll see more examples of this division. The result of breaking up a large test case is lots of smaller and more understandable tests, and a better picture of the responsibilities of the SUT.
This 600-line class smells...
I bet you have said that too many features can be a smell of a God class. And indeed you're right; but if the test is a functional or end-to-end one is quite handy to divide Testcases by feature: you're testing the whole application and you cannot just divide end-to-end tests by the class they act on. They act on a whole copy of the system.
The same goes for certain all-encompassing classes such as Facades. This pattern comes handy for tests that do not have a natural single production code class equivalent.
Implementation
Transitioning from Testcase per Class to Testcase per Feature should not be really difficult as test methods should already be independent. If you have common code shared between test cases, you can use a superclass.
Since you have now different test case classes, choose a good convention for naming. You should nevertheless name them with a 'Test' suffix and keep the -Test.php file name, so that PHPUnit automatically sees them.
A common way of refactoring towards this pattern is to duplicate the class. Then, renaming takes place along with the deletion of everything that is responsibility of another class (Test Methods of other features) and that is not used in the current one (unrelated Test Utility Methods).
Variations
Again, remember these are examples of a class not tested in isolation, but more of tests required for an application. So many features to test does not imply a God class, only a system which does many things, which is normal when you're paid to develop it (although many of your tests should be at the lowest possible level, which is often the unit one.)
- a Testcase Class per Method is used when methods are really complex to call, with many parameters. Different methods are addressed by different classes.
- a Testcase Class per User Story is used for high level tests which address the various acceptance criteria for a story; in the xUnit patterns book it is said that this division prevents commit conflicts as long as working on user stories is not simultaneous.
The ultimate criteria is to keep cohesive Testcase Classes: if you have many Test Methods and they can be logically divided into groups, you can break the Testcase into pieces. A rule of thumb for measuring cohesion is checking how many methods use a Testcase field: when fields are referencedonly in a small subset of methods, those methods become candidates for extraction.
Example
The code sample of today is related to a small e-commerce Facade, which is tested at the acceptance level. Since the Facade hides the underlying system, it has a lot of features to test: you can consider these tests as functional over the whole object graph referred internally by the Facade.
Here's one of the two tests:
<?php require_once 'Facade.php'; class FacadeBuyingTest extends PHPUnit_Framework_TestCase { public function setUp() { /** * For simplicity I instance the object with new, but in reality * it would be an object graph created by a Factory or a DI * container. */ $this->facade = new Facade(); // other setup code: database of the users for example } public function testUserCanBuyAnItem() { $userId = 42; $productId = 1000000; $this->facade->buy($userId, $productId); } public function testUserCanPayAnItem() { $userId = 42; $transactionId = 200000000; $this->facade->payFor($userId, $transactionId); } }
And here is the other:
<?php require_once 'Facade.php'; class FacadeSearchTest extends PHPUnit_Framework_TestCase { public function setUp() { $this->facade = new Facade(); // other setup code: product database for example } public function testUserCanSearchForProducts() { $products = $this->facade->search('hard disks'); $this->assertTrue(count($products) > 0); // ... more assertions } public function testUserCanSearchForProductsAndExcludeUnwantedOnes() { $products = $this->facade->search('hard disks -ssd'); $this->assertTrue(count($products) == 1); // ... more assertions } }
Sometimes you can't split a class, but you can always split its tests.
Opinions expressed by DZone contributors are their own.
Comments