QA Thing – test-driven developing it 04

Getting to know functional tests.

Part of a series

This post is the fourth one in a series of posts I’m publishing to chronicle my personal test-driven development flow to develop a WordPress plugin.
The first post in the series, the second and the third one could be better starting points than this post.

##The starting code and a the tools
I’m pushing the code to GitHub as I work tagging it when a relevant change happens.
The code I’m starting from for this post is tagged post-04 on GitHub.
As tools of the trade I’m relying on wp-browser and Codeception for the testing, DI52 as dependency injection container and xrstf PHP 5.2 compatible autoloader for Composer to autoload the classes.
While the plugin code is PHP 5.2 compatible the tests code won’t.

Are these acceptance tests?

So far I’ve written two “acceptance” tests, one in Cept and the other in Cest format, the code below comes from one of those tests:

/**
 * I can see a plugin provided configuration on the options page
 */
public function test_i_can_see_a_plugin_provided_configuration_on_the_options_page( AcceptanceTester $I ) {
    $I->am('QA person');
    $I->wantTo('see a plugin provided configuration');

    $I->cli('scaffold plugin acme');

    $pluginPath = $I->cli('plugin path acme --dir');
    $qaFolder = $pluginPath . '/qa';
    $I->copyDir(codecept_data_dir('configurations/one-configuration'), $qaFolder);
    $I->seeFileFound($qaFolder . '/qa-config.json');

    $I->loginAsAdmin();
    $I->amOnAdminPage('/admin.php?page=qa-options');

    $I->canSeeNumberOfElements('#qa-configuration option', 4);}

While Codeception ablative methods syntax makes it easy to understand what is happening it’s clear I’m using the WPCLI module to set up the testing fixture and fiddle with the filesystem.
Acceptance tests are usually described as black box tests where functional tests are usually described as white box tests.
The former are supposed to interact with with the application using its API (or UI) only without knowledge of its inner workings; the latter are instead tests built with knowledge of the behaviour implementation where that knowledge is used to set up pre-conditions, interact with the object and assert post-conditions.
There is always confusion between the two kind of tests and it only gets worse when I rewrite the test above as a functional test:

use function tad\WPBrowser\Tests\Support\rrmdir;

class PluginConfigurationReadCest {
    /**
     * @var string
     */
    protected $acmePlugin;

    /**
     * @var string
     */
    protected $qaFolder;

    public function _before(FunctionalTester $I) {
        $this->acmePlugin = $I->getWpRootFolder() . '/wp-content/plugins/acme';
        rrmdir($this->acmePlugin);
        $I->cli('scaffold plugin acme');
        $this->qaFolder = $this->acmePlugin . '/qa';
    }

    public function _after(FunctionalTester $I) {
        rrmdir($this->acmePlugin);
    }

    /**
     * I can see a plugin provided configuration on the options page
     */
    public function test_i_can_see_a_plugin_provided_configuration_on_the_options_page(FunctionalTester $I) {
        $I->copyDir(codecept_data_dir('configurations/one-configuration'), $this->qaFolder);
        $I->seeFileFound($this->qaFolder . '/qa-config.json');

        $I->loginAsAdmin();
        $I->amOnAdminPage('/admin.php?page=qa-options');

        $I->canSeeNumberOfElements('#qa-configuration option', 4);
    }
}

The lack of difference is striking: beside changing the tester from AcceptanceTester to FunctionalTester and dealing with the filesystem directly thanks to the WordPress::getWpRootFolder() method I’m doing exactly what I was doing before.

Understanding the difference

Acceptance tests are meant to simulate a user interaction with the system through its UI.
“Simulating a user” does not mean simulating a “dumb” user: it means simulating someone that cannot access and manipulate the system internals during the tests beyond what means the system provides to do so.
The “QA Person” I’m simulating in the tests is capable of pulling an acme plugin from his/her team repository and install the plugin in his/her own WordPress local installation: keeping in mind the kind of user I’m simulating so far there is no violation of the “black box” approach.
The difference is more evident writing one more functional test:

/**
 * @test
 * it should not show example configurations if disabled
 */
public function it_should_not_show_example_configurations_if_disabled(FunctionalTester $I) {
    $I->copyDir(codecept_data_dir('configurations/one-configuration'), $this->qaFolder);
    $I->seeFileFound($this->qaFolder . '/qa-config.json');
    $I->haveOptionInDatabase('qa-thing', ['disable-examples' => true]);

    $I->loginAsAdmin();
    $I->amOnAdminPage('/admin.php?page=qa-options');

    $I->canSeeNumberOfElements('#qa-configuration option', 1);
}

I want to make sure the plugin will provide a way to deactivate the example configurations provided; specifically that way is a toggle option (disable-examples key in the qa-thing option).
I’m testing the expected behaviour, seeing just the configuration provided by the plugin, setting up the test fixture with a direct database access and setting the disable-examples sub-option in the qa-thing one.
A user should not care or know such an option exists: this makes the test a “functional” one.
The “acceptance” counterpart of this is one where the developer provides some UI the user can interact with, say a checkbox, to disable the example configurations, the user checks that and asserts that example configurations will not show up.

So a new suite?

I’ve shown and used the functional suite to run the test above and the “switch” deserves some explanation.
I’ve updated the acceptance suite configuration file (acceptance.suite.yml) to only use the WPBrowser and WPCLI modules removing the WPDb module as violating a “black box approach”:

class_name: AcceptanceTester
modules:
    enabled:
        - \Helper\Acceptance
        - WPBrowser
        - WPCLI
    config:
        WPBrowser:
            url: 'http://wp.dev'
            adminUsername: admin
            adminPassword: admin
            adminPath: /wp-admin
        WPCLI:
            path: '/Users/Luca/Sites/wp'

and updated the functional.suite.yml file to use the WPCLI and Filesystem modules too.
wp-browser scaffolds the functional suite to use the WordPress and WPDb modules so the final version of the functional.suite.yml configuration file is:

class_name: FunctionalTester
modules:
    enabled:
        - \Helper\Functional
        - WPDb
        - WordPress
        - WPCLI
        - Filesystem
    config:
        WPDb:
            dsn: 'mysql:host=localhost;dbname=wp'
            user: root
            password: root
            dump: tests/_data/dump.sql
            populate: true
            cleanup: true
            url: 'http://wp.dev'
            tablePrefix: wp_
        WordPress:
            depends: WPDb
            wpRootFolder: /Users/Luca/Sites/wp
            adminUsername: admin
            adminPassword: admin
        WPCLI:
            path: '/Users/Luca/Sites/wp'

After the updates rebuilding the Tester classes requires running codecept build.
The biggest difference between the two “core” modules, WPBrowser for the acceptance suite and WordPress for the functional suite, is how they “hit” the code base: WPBrowser is a PHP-based browser that will go through all the steps of a real browser to fetch the response from the server while the WordPress module will hit the “route handlers” directly.
To make a simple example writing this line of test will lead to different actions in the two modules:

$I->amOnPage('/');

WPBrowser will send a GET request to http://wp.dev while WordPress will include the /Users/Luca/Sites/wp/index.php file directly and buffer its output.
Other frameworks that support long-running processes (handling more than one HTTP request in a life cycle) allow the test code to share the same variable scope as the tested code but WordPress, with its heavy use of globals and constants, will not allow for that; in stricter terms the “functional” testing the WordPres module offers is not the same offered by modules like the Laravel one but it’s a step forward nonetheless.

A failing test!

The test I’ve just written, be it functional or not, is failing.

First functional test failing

Time to make it pass again writing as little code as possible; the simplest way to do it is to add a class in charge of managing the plugin options, qa_Options_Repository, require it as a dependency in the options page class and call it in the render method:

public function render() {
    ?>
    <select name="qa-configuration" id="qa-configuration">

        <?php if (!$this->options->disableExamples()) : ?>
            <option value="foo" class="qa-configuration-option">Foo</option>
            <option value="baz" class="qa-configuration-option">Baz</option>
            <option value="bar" class="qa-configuration-option">Bar</option>
        <?php endif; ?>

        <?php /** @var qa_Configurations_ConfigurationI $configuration */
        foreach ($this->scanner->configurations() as $configuration) : ?>
            <option value="<?php echo $configuration->id() ?>"
                    class="qa-configuration-option"><?php echo $configuration->name() ?></option>
        <?php endforeach; ?>

    </select>
    <?php
}

The new test is now a success:

First functional test passing

Time to write a new functional test to make sure the example configuration scripts will provide some real code.
Or not?

Time for some lower level tests maybe?

Knowing what has to be tested, how and how much is not an exact science.
The blanket approach of testing “everything” works to a point: knowing that smaller changes in minor details like output strings or CSS classes could break 20 tests does not help rapid refactoring and iteration.
Only testing code I own is the first rule I use to avoid testing code but others apply to the code I own:

  • test any code that’s in contact with user input
  • test any code that “forks” (contains conditional statements)
  • test any code that “scales” (contains loops on variable number of objects)

Applying these rules to the current code it’s evident I need to write some tests, and I’m not committing to the type of tests I need to write yet, for the following classes:

  • the plugins scanner, class qa_Configurations_Scanner
  • the options repository, class qa_Options_Repository
  • the options page, class qa_Options_Page

Current code

The code current to this article can be found

I appreciate your input