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:

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]

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]

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

Comments

Popular posts from this blog

Bulk Inserts and the Performance Holy Grial (Part II)

Legacy code concepts II: the Seam Model

Bulk Inserts and the Performance Holy Grial (Part I)