Yes, you can, part II
Hi,
Following the previous post topic regarding building your own solutions when using a package or framework is not an option, I proceed to show how CITestCase looks:
Following the previous post topic regarding building your own solutions when using a package or framework is not an option, I proceed to show how CITestCase looks:
The Unit Testing bespoke solution
abstract class CITestCase
{
private $printer = null;
public function __construct(OutputPrinter $printer)
{
$this->printer = $printer;
}
abstract protected function setUp();
abstract protected function tearDown();
public function run()
{
$results = [];
$me = new ReflectionClass(get_called_class());
$batch = array_filter($me->getMethods(ReflectionMethod::IS_PROTECTED),
function(ReflectionMethod $method) {
return preg_match('/Test$/', $method->getName()) === 1;
});
/* @var $testMethod ReflectionMethod */
foreach ($batch as $testMethod) {
$this->setUp();
$results[] = ['name' => $testMethod->getName(),
'assertion' => $this->{$testMethod->getName()}()];
$this->tearDown();
}
return $this->printer->printResults($results);
}
/**
* Allows us to execute private methods out of an object
*/
protected function invokeMethod(&$object,
$methodName,
array $parameters = array())
{
$reflection = new ReflectionClass(get_class($object));
$method = $reflection->getMethod($methodName);
$method->setAccessible(true);
return $method->invokeArgs($object, $parameters);
}
/**
* Sets/Gets a private property from a given object
*/
protected function setValue(&$object, $propertyName, $value)
{
$property = $this->setPropertyAccesible($object, $propertyName);
return $property->setValue($object, $value);
}
protected function getValue(&$object, $propertyName)
{
$property = $this->setPropertyAccesible($object, $propertyName);
return $property->getValue($object);
}
private function setPropertyAccesible(&$object, $propertyName)
{
$reflection = new ReflectionClass(get_class($object));
if (!$reflection->hasProperty($propertyName)) {
throw new Exception('Property not found.');
}
$property = $reflection->getProperty($propertyName);
$property->setAccessible(true);
return $property;
}
public function __destruct()
{
$this->tearDown();
}
}
Constructor
We inject the OutputPrinter object via constructor. Depending on how we want to display the result we write a concrete class. At execution time we can decide which one to use: html, console, file, ...
See Strategy pattern [Design Patterns, GoF]
See Strategy pattern [Design Patterns, GoF]
Abstract methods setUp and tearDown
We make it mandatory to implement these two methods for each concrete test case that inherits from CITestCase. Note that the interface for any test case is set by the parent class.
setUp usually will contain all the code necessary to declare and initialise variables prior to the test execution.
tearDown will destroy or unset any object that may interfere with other tests, since every Test method is run consecutively.
See Template Method pattern [Design Patterns, GoF]
setUp usually will contain all the code necessary to declare and initialise variables prior to the test execution.
tearDown will destroy or unset any object that may interfere with other tests, since every Test method is run consecutively.
See Template Method pattern [Design Patterns, GoF]
The public run method
Reflection allows us to automatically go through all the methods ended with the suffix Test. The setUp and the tearDown methods are invoked before and after respectively of the corresponding test case. In addition, we have a couple of methods to set and get private properties in order to check values if necessary.
Read the 3rd (and last) part of these mini series
Read the 3rd (and last) part of these mini series
Comments
Post a Comment