QA Thing - test-driven developing it 01

Growing an object-oriented WordPress plugin guided by tests, part 1.

The idea

I’ve outlined the “why” of the QA Thing WordPress plugin in a previous post and I’ve tried to restrain my imagination to make the plugin, at first, resolve a simple problem:

Allow people to run developer-provided scripts in the context of a WordPress installation without requiring knowledge or experience of the code.

The name “QA Thing” originates in the idea that the only possible scenario where people could be willing to run some PHP script written by others on their WordPress installation and thats’ the “itch” I’m “scratching” with the plugin.

The book

I’ve read the book “Growing Object-Oriented Software Guided by Tests” some 2 and a half years ago and it contributed to shape the way I use Test-driven development; so much so that it’s among the books I commit to re-read every 2 years or so and the time and coincidence of this plugin development with it seems to be perfect.
I will read the book chapters as I go about the plugin development and document my personal, opinionated and biased point of view about development, test-driven development and, probably, WordPress.
The book is recommended for developers that know, at least, what testing is; the code examples are in Java but those are ablative enough that little extra “googling” of particular code structures is needed to understand them and all the building blocks of Object-oriented development are there. Want a book recommendation? That’s it.

I want to test-driven develop it: where do I start?

The book answers this question simply and brilliantly: with an acceptance test (also called “end to end” test).
This is a notion I hold dear: there are many examples of unit/integration testing in the WordPress echosystem and so many call to arms on the line “You should test your code” but few answer the question above in clear terms.

Setting up to run a first WordPress acceptance test

The testing environment setup process is one to be done just once but it’s where many give up so it’s probably worth detailing step by step.
The starting point is that I’ve got a bare bones version of the plugin setup but no testing environment for it; the plugin sits in my GitHub repositories folder and is symbolically linked in the WordPress development installation.
The WordPress development installation is handled with a Laravel Valet-like setup on my Mac:

  • installed in the ~/Sites/wp folder, vanilla WordPress file structure
  • uses the wp database
  • is served by Apache at
  • the plugin sits in ~/repos/qa-thing and is symlinked to the ~/Sites/wp/wp-content/plugins/qa-thing folder

The project PHP dependencies are managed using Composer so I will add wp-browser as a development requirement:

cd ~/repos/qa-thing
composer require --dev lucatume/wp-browser

After the installation is done I will bootstrap the testing suite:

wpcept bootstrap

Notice that I’m using the wpcept command directly: that’s because my shell PATH var is prefixed with the vendor/bin relative path: that’s where Composer will put any package generated script file and it allows me to avoid installing dependencies globally. Notice also that the command will output an error telling me that /var/www/wordpress is not a valid WordPress installation path:

[caption id=“attachment_3201” align=“aligncenter” width=“1153”]Build error on bootstrap, it's ok Build error on bootstrap, it’s ok[/caption]

Nothing wrong here but it’s time to configure the scaffolded suites to work with my local setup.

About the repository

All the code I’m showing in the post can be found on the plugin repository on GitHub in the book-follow branch keeping in mind that the master branch contains a barely working prototype for the time being, the two branches will merge eventually but not soon.

Configuring the wp-browser/Codeception suites

The Codeception testing framework and its wp-browser extension scaffolded four suites and, for each, a configuration file:

  • tests/acceptance.suite.yml
  • tests/functional.suite.yml
  • tests/integration.suite.yml
  • tests/unit.suite.yml

Each but the unit one will need to be configured to point to and fetch resources from the right places. Starting from the acceptance suite configuration file and keeping the local set up shown above in mind:

class_name: AcceptanceTester
        - \Helper\Acceptance
        - WPDb
        - WPBrowser
            dsn: 'mysql:host=localhost;dbname=wp'
            user: root
            password: root
            dump: tests/_data/dump.sql
            populate: true
            cleanup: true
            url: ''
            tablePrefix: wp_
            url: ''
            adminUsername: admin
            adminPassword: admin
            adminPath: /wp-admin

The WPDb module will bootstrap the WordPress installation before each acceptance test loading the tests/_data/dump.sql file in the wp database; while the database exists already (wp-browser will not dare create databases for me) the dump is empty and I will create a good starting dump for the project using wp-cli:

cd ~/Sites/wp
wp site empty --yes
wp plugin deactivate $(wp plugin list --status=active --field=name)
wp plugin activate qa-thing
wp db export ~/repos/qa-thing/tests/_data/dump.sql

[caption id=“attachment_3202” align=“aligncenter” width=“1153”]Dumping an initial database fixture using wp-cli Dumping an initial database fixture using wp-cli[/caption]

What’s happening? In plain English:

  • remove any content from the website (and this is why real production sites should never be used for tests)
  • deactivate all plugins
  • activate just the qa-thing plugin
  • export the resulting database to the dump file

To test that it’s working let’s try running the suite:

codecept run acceptance

[caption id=“attachment_3203” align=“aligncenter” width=“1153”]Running empty acceptance suite Running empty acceptance suite[/caption]

The functional suite requires little work:

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

and since this suite was the one failing to build before I will rebuild Codeception modules before running it:

codecept build
codecept run functional

[caption id=“attachment_3204” align=“aligncenter” width=“1153”]Build and run empty functional suite Build and run empty functional suite[/caption]

The integration suite will not rely on the database fixture to setup the tests but will use the same system used by WordPress automated testing suite:

class_name: IntegrationTester
        - \Helper\Integration
        - WPLoader
            wpRootFolder: /Users/Luca/Sites/wp
            dbName: wp-tests
            dbHost: localhost
            dbUser: root
            dbPassword: root
            tablePrefix: wp_
            title: WP Tests
            plugins: [qa-thing/qa-thing.php]
            activatePlugins: [qa-thing/qa-thing.php]

To note here:

  • the only module used is the WPLoader one
  • the module will access the database but I’m not pointing it to the wp database as I did for the acceptance and functional suites but to the wp-tests one; this database does not exist and I’ve created it
  • I’m telling the module to activate and include the qa-thing/qa-thing.php plugin; the fact that the plugin is symbolically linked there is of no consequence as WordPress will handle symbolic links like a champion

Again run the suite and see if it works:

codecept run integration

[caption id=“attachment_3205” align=“aligncenter” width=“1153”]Run empty integration suite Run empty integration suite[/caption]

The unit suite will require no configuration so it’s time to see if all the suites work together:

codecept run

[caption id=“attachment_3206” align=“aligncenter” width=“1153”]Running all Codeception suites Running all Codeception suites[/caption]

How is this supposed to work?

The afore-mentioned book underlines one of the key benefits of test-driven development: worrying about “what” is supposed to happen before “how” it’s supposed to happen.
The “what” here is better explained with a couple of user stories:

As a plugin developer part of the team
I want to be able to add a script responsible for setting up a specific issue I' like QA to look into somewhere in the plugin folder
And a configuration for the issue in the `qa/qa-config.json` file
So that a QA person can apply the configuration (run the script) using a GUI

As a QA person part of the team charged with testing a specific issue
I want to be able to apply the issue configuration in my local test WordPress installation
So that I can have the issue fixture set up for me exactly how it's meant to be

That’s essentially all the plugin is meant to do in its first iteration.

Writing the first acceptance test

The user stories above are too wide to be used to write an acceptance test and I will split them into smaller scoped acceptance tests written using Codeception acceptance format.
Time to generate the first real acceptance test:

codecept generate:cept acceptance ConfigurationRead

Notice that the QA Thing plugin itself provides, at this stage, 3 valid configurations as a showcase in the qa folder:

$I = new AcceptanceTester( $scenario );

$I->am( 'a QA person' );
$I->wantTo( 'see plugin provided configurations in the QA options page' );

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

// The QA Thing plugin itself provides them
$I->canSeeNumberOfElements( '#qa-configuration option', 3 );

[caption id=“attachment_3217” align=“aligncenter” width=“1138”]Running the first failing acceptance test Running the first failing acceptance test[/caption]

Current code version.


Now that I have a first failing acceptance tests is time to put in place, following along the book steps and flow, just enough code and tests to make the test pass.