Tuesday, January 28, 2014

Adding CLI options to PHPUnit

Issue:  I've got PHPUnit set up to run Selenium tests on varying combinations of browsers/platforms.  When I'm writing tests and doing day-to-day stuff, I want to run just on one (a VM with Firefox on Win 7, at the moment).  When I want to look into a new IE11 bug, I want to run a particular test just on Win 8/ IE 11.  When I'm running a full test run, I want to run across all target browsers/platforms.

The browser/platform combination to use has been specified up to now as a static property of my base TestCase class.  It's a shorthand: I can set it to 'FF' for Firefox on Win7, 'IE' for Internet Explorer 10 on Win7, '8' for Firefox, IE11 and Chrome on Windows 8, or 'all' for 3 browsers across Win7 and Win8.

It's not much, but I was wanting to set this as a command line option, rather than changing the value of the property on the class manually:

$ phpunit --browser-set="FF" tests/GoTestSomeStuffTest.php

The phpunit code makes it sound like this'll be straightforward.  Just subclass PHPUnit_TextUI_Command, add in a hook to the constructor and handler... easy.

Almost.

The suggested code is:
* <?php
* class MyCommand extends PHPUnit_TextUI_Command
* {
* public function __construct()
* {
* $this->longOptions['--my-switch'] = 'myHandler';
* }
*
* // --my-switch foo -> myHandler('foo')
* protected function myHandler($value)
* {
* }
* }



Firstly, that's not quite right.  Looking at the other longOptions, it's clear that the initial double hyphen shouldn't be there:
* public function __construct()
* {
* $this->longOptions['my-switch'] = 'myHandler';
* }

Secondly, if you want to pass an option value you need to append an equals sign to the switch name:

* public function __construct()
* {
* $this->longOptions['my-switch='] = 'myHandler';
* }


But how do we get to use MyCommand instead of PHPUnit_TextUI_Command?  I'm not sure if this is the right answer, but it's an answer.  I've copied the bash script phpunit that starts it all up (from https://github.com/sebastianbergmann/phpunit/blob/master/phpunit), and instead of 

PHPUnit_TextUI_Command::main();

I have

require_once 'MyCommand.php';
MyCommand::main();

(OK it's not really called MyCommand, I'm just staying with the original docs!).  Plus a chmod to 755 to get it to run.

Almost there.  However, our new MyCommand constructor isn't being called still.  main() on the original looks like this:

    /**
* @param boolean $exit
*/
    public static function main($exit = true)
    {
        $command = new static;
        return $command->run($_SERVER['argv'], $exit);
    }


so the line new static  gives us an instance of PHPUnit_TextUI_Command, because that's where we are when it gets called.  So MyCommand needs this method too, just copied straight across.  In the end it looks like this:

<?php
class MyCommand extends PHPUnit_TextUI_Command
{
public function __construct()
{
$this->longOptions['browser-set='] = 'setBrowserSet';
}
// --browser-set="FF" -> setBrowserSet("FF")
protected function setBrowserSet($value)
{
MyTestSetup::setBrowserSetToUse($value);
}
public static function main($exit = true)
{
$command = new static;
return $command->run($_SERVER['argv'], $exit);
}
}


Validation and a default value, and handling it all, sit in MyTestSetup (which indirectly extends PHPUnit_Extensions_Selenium2TestCase).

Note there is a way to define $browsers in a configuration somehow - I think through phpunit.xml (I seem to remember seeing it there) but it sets it on PHPUnit_Extensions_SeleniumTestCase, but I'm using PHPUnit_Extensions_Selenium2TestCase and so don't think it'll work.    


(Edit: excuse the horrible code line spacing.  Another to-do for the list.)

No comments:

Post a Comment