Rocking my testing world.
Gherkin support in Codeception
As I read the article tag line it feels more and more awkward.
Beside this detail Codeception beta 2.2 is rolling and it’s introducing, among other things, native Gherkin support.
For anyone involved in a team with varied development skills this is huge.
Until now the closest Codeception could get to “writing tests in plain English” was the cept test format: extremely understandable to me (a developer), readable by non developers but easy to complicate beyond recognition and readability.
Gherkin syntax would allow someone with no development skill, but a knowledge of the framework and the feature at hand, to write a test like this in my environment of choice, WordPress:
Feature: Installation
In order to use the plugin
As an administrator
I need to be able to activate and deactivate the plugin
Scenario: see the plugin in the plugins admin screen
Given I am logged in as an administrator
When I go to the plugins administration page
Then I should see the "Codeception Beta 22" plugin
Scenario: activate the plugin
Given I am logged in as an administrator
When I go to the plugins administration page
And I activate the "Codeception Beta 22" plugin
Then I should see the "Codeception Beta 22" plugin activated
Scenario: deactivate the plugin
Given I am logged in as an administrator
And the "Codeception Beta 22" plugin is activated
When I go to the plugins administration page
And I deactivate the "Codeception Beta 22" plugin
Then I should see the "Codeception Beta 22" plugin deactivated
Now this is easy to read for anyone. Sure the language has to adhere to some formalities but that’s a test, not a narrative book.
The “technicalities of test implementation” are hidden from the tester and fall on the developer to implement and maintain.
I have to try this
While the release it’s still in beta I had to put my hands on it and try to build something so I’ve scaffolded a basic WordPress plugin called “Codeception Beta 22” and wrote the least possible amount of code, the codeception-beta-22/plugin.php
file reads:
<?php
/**
* Plugin Name: Codeception Beta 22
* Plugin URI: http://theAverageDev.com
* Description: A WordPress plugin to test Codeception beta 2.2 functionality with WPBrowser.
* Version: 1.0
* Author: theAverageDev
* Author URI: http://theAverageDev.com
* License: GPL 2.0
*/
Doing nothing but still enough to show up in WordPress plugins list.
Second step was the usual Codeception and wp-browser set up drill to then jump in generate the first feature:
codecept generate:feature acceptance Installation
I’ve filled in the tests/acceptance/Installation.feature
file to exactly the text above and had Codeception generate the snippets required to support the steps using
codecept gherkin:snippets acceptance
The CLI output is a long copy-and-paste-able list of methods that should be implemented in the AcceptanceTester
class.
Filling in the blanks
Now that Codeception has given me empty methods it’s time to fill those in.
While the way tests are written is different this is still a Codeception acceptance test and I will be using methods dealing with the UI and the database.
I’ve configured the acceptance
suite to use the WPBrowser
and WPDb
modules:
# Codeception Test Suite Configuration
# suite for WordPress acceptance tests.
# perform tests in browser using WPBrowser or WPWebDriver modules.
class_name: AcceptanceTester
modules:
enabled:
- WPBrowser
- \Helper\Acceptance
- WPDb
config:
WPBrowser:
url: 'http://wp.dev'
adminUsername: admin
adminPassword: admin
adminUrl: /wp-admin
WPDb:
dsn: 'mysql:host=127.0.0.1;dbname=wp'
user: root
password: root
dump: tests/_data/dump.sql
populate: true
cleanup: true
url: 'http://wp.dev'
tablePrefix: wp_
Please note I’m resetting the state of the database before each test setting WPDb::populate
and WPDb::cleanup
to true
; the tests/_data/dump.sql
file being a dump of my empty local WordPress installation.
Now that I have the two modules configured it’s time to rebuild the suites
codecept build
and tap into the methods provided by those modules to implement the Gherkin steps.
The build
command will regenerate the _generated\AcceptanceTesterActions
trait and that will be included by default in the AcceptanceTester
class.
After some back and forth here it is the finished code:
<?php
// file AcceptanceTester.php
/**
* Inherited Methods
* @method void wantToTest($text)
* @method void wantTo($text)
* @method void execute($callable)
* @method void expectTo($prediction)
* @method void expect($prediction)
* @method void amGoingTo($argumentation)
* @method void am($role)
* @method void lookForwardTo($achieveValue)
* @method void comment($description)
* @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
*
* @SuppressWarnings(PHPMD)
*/
class AcceptanceTester extends \Codeception\Actor
{
// the ones Codeception built using `codecept build`
// we can now access the modules provided methods
use _generated\AcceptanceTesterActions;
/**
* @Given I am logged in as an administrator
*/
public function iAmLoggedInAsAnAdministrator()
{
// from WPBrowser module
$this->loginAsAdmin();
}
/**
* @When I go to the plugins administration page
*/
public function iGoToThePluginsAdministrationPage()
{
// from WPBrowser module
$this->amOnPluginsPage();
}
/**
* @Then I should see the :pluginName plugin
*/
public function iShouldSeeThePlugin($pluginName)
{
$pluginSlug = $this->buildPluginSlug($pluginName);
// from WPBrowser module
$this->seeElement('#the-list tr[data-slug="' . $pluginSlug . '"]');
}
/**
* @When I activate the :pluginName plugin
*/
public function iActivateThePlugin($pluginName)
{
$pluginSlug = $this->buildPluginSlug($pluginName);
// from WPBrowser module
$this->activatePlugin($pluginSlug);
}
/**
* @Then I should see the :pluginName plugin activated
*/
public function iShouldSeeThePluginActivated($pluginName)
{
$pluginSlug = $this->buildPluginSlug($pluginName);
// from WPBrowser module
$this->seePluginActivated($pluginSlug);
}
/**
* @Given the :pluginNameOrFullSlug plugin is activated
*/
public function thePluginIsActivated($pluginNameOrFullSlug)
{
// from WPDb module
$activePlugins = $this->grabOptionFromDatabase('active_plugins');
$activePlugins = empty($activePlugins) ? [] : $activePlugins;
// let's make the assumption that either the user passed us the full slug
// e.g. "some-plugin/main.php"
// or a plain language name like "Some Plugin"
$isFullSlug = preg_match('/^.*\\.php$/', $pluginNameOrFullSlug);
if ($isFullSlug) {
$pluginFullSlug = [$pluginNameOrFullSlug];
} else {
// we'll cover the 80% of the cases: it's either `some-plugin/some-plugin.php`
// or `some-plugin/plugin.php`
// something else? User should pass the full slug
$pluginSlug = $this->buildPluginSlug($pluginNameOrFullSlug);
$pluginFullSlug = ["{$pluginSlug}/{$pluginSlug}.php", "{$pluginSlug}/plugin.php"];
}
if (empty($activePlugins) || !count(array_intersect($pluginFullSlug, $activePlugins))) {
$activePlugins = array_merge($activePlugins, $pluginFullSlug);
// again from WPDb module
$this->haveOptionInDatabase('active_plugins', $activePlugins);
}
}
/**
* @When I deactivate the :pluginName plugin
*/
public function iDeactivateThePlugin($pluginName)
{
$pluginSlug = $this->buildPluginSlug($pluginName);
// from WPBrowser module
$this->deactivatePlugin($pluginSlug);
}
/**
* @Then I should see the :pluginName plugin deactivated
*/
public function iShouldSeeThePluginDeactivated($pluginName)
{
$pluginSlug = $this->buildPluginSlug($pluginName);
// from WPBrowser module
$this->seePluginDeactivated($pluginSlug);
}
/**
* @param $pluginName
* @return string
*/
protected function buildPluginSlug($pluginName)
{
$pluginSlug = implode('-', array_map('strtolower', preg_split('/[-_\\s]/', $pluginName)));
return $pluginSlug;
}
}
Running the tests now will see them pass:
and running codecept gherkin:steps
again will show an informative screen:
Next
While the possibility is nice I would not like to write all this code every time: a porting over to the new Gherkin syntax of WPBrowser and WPDb methods is in order.