Testing for Websites and Plugins
@jackbarker
Presented @ #WPMelb (October 2014)
To advance to the next slide use the arrow keys (or swipe left/right on touch devices).
Agenda
- Testing 101 (What / Why)
- Testing for Beginners with Selenium (Browser Automation)
- Testing for Developers with PHPUnit and WP Testing Framework
Testing : 101
A Test is:
- An Instruction with an Expected Outcome, that shall be compared against an Actual Outcome.
- IF (Expected == Actual), the test shall PASS.
- IF (Expected <> Actual), the test shall FAIL.
- A test must be repeatable.
- Where possible, each test should focus on a single item of functionality.
What a test looks like:
In Selenium IDE (which we'll explore shortly), a test might look like this:
Command | Target | Value | Explanation |
---|---|---|---|
AssertElementExists | css=body #my-form input#firstname | This test expects that an input with id "firstname" exists | |
AssertText | css=body #my-form label.firstname | Enter your first name: | This test expects that a label with class "firstname" exists, and with text "Enter your first name:" |
What a test looks like:
In PHPUnit, a test may look like this:
// Test that the sky's colour is blue
$testResult = assertEquals('blue', $sky->color);
Your website/product's automated test suite should:
- Provide a good description of the functionality your system is intends to deliver.
- Alert you if you break something.
- Assist you in checking whether you have fixed something.
- Improve the quality of your work.
- Allow you (and/or others) to have a greater confidence in the system, and in proposed changes.
- Recieve updates/maintenance inline with changes to the codebase
Browser Automation with Selenium IDE
or, "Testing for Beginners"
Selenium IDE
(What IS it?)
- It is a Browser Automation Tool.
- Installed as a Firefox Browser Plugin.
- Alternatively: Chrome, Safari, IE, Opera (note: these versions are built/maintained by 3rd parties).
Selenium IDE
(What does it DO?)
- Allows you to record or write scripts, and then replay the script in the browser.
- Because it's browser based, you can record/replay on any website.
- It includes a testing framework.
- It's FREE to use, and licensed under Apache 2.0.
Getting started
Installing Selenium IDE
- Make sure you have Firefox installed.
- Grab Selenium IDE Firefox Plugin at: SeleniumHQ.org/download
- (Note - for our purposes, ignore "Selenium Server" and other products)
Using Selenium
Worked Example for WhereTheTruck.at
Source Code available here: Selenium Test Files
Let's create a test for the following:
Test 1
Instruction: Select a city from the MapPicker
Expected behaviour: The map should be updated, to reflect the city you have selected.
Source code:Test 1
Saved Tests
Note: Tests are saved in an HTML format:
<!DOCTYPE html>
<html>
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<link rel="selenium.base" href="http://wherethetruck.at/" />
</head>
<body>
<table><tbody>
<!--Open homepage-->
<tr>
<td>open</td> <!-- Command -->
<td>/</td> <!-- Arg 1 -->
<td></td> <!-- Arg 2 -->
</tr>
<!--Click Mapselector, and select "Melbourne"-->
<tr>
<td>click</td>
<td>id=mapselector-dd</td>
<td></td>
</tr>
<tr>
<td>clickAndWait</td>
<td>link=Melbourne</td>
<td></td>
</tr>
<!--Expected: "Melbourne" is selected-->
<tr>
<td>assertText</td>
<td>css=#mapselector-dd > span</td>
<td>exact:City: Melbourne</td>
</tr>
</tbody></table>
</body></html>
Let's try a harder one:
Test 2
Instruction: Reload the page
Expected behaviour: The page should "remember" which city you were viewing earlier, and display the same city.
Source code:Test 2
Limitations of Selenium IDE
- Selenium IDE is a very powerful tool for browser automation.
- It can be used any number of tasks (not just testing).
- That said, it does have some limitations.
- Is your UI likely to undergo changes? If so, rework to your testing will likely be significant.
- How complex are the items you are seeking to test? In some cases you may be limited in what the tool allows you to do (e.g. Test that "on Tuesdays, a different banner is displayed")
- Some testing simply needs to be done manually.
Limitations of Selenium IDE
- Great for testing something that already works (but not a great option for TDD).
- Selenium's developers cite:
"Selenium IDE is simply intended as a rapid prototyping tool...
For serious, robust test automation use either Selenium 2 or Selenium 1."
-- SeleniumHQ.org
- Whilst highly flexible, and easy for beginners, there are more precise tools (targetted at developers) that may provide a more reliable solution...
- Speaking of which... enter:
PHPUnit
The PHP Testing Framework
Installing PHPUnit
PHPUnit Basics
PHPUnit is started from the command line, using:
$ phpunit
- Note: When you run PHPUnit, there is no webserver.
- Note: When you run PHPUnit, there is no webserver.
- Note: When you run PHPUnit, there is no webserver!!!
- For this reason, in addition to your PHPUnit environment, you will also need an environment for manual (web browser) testing.
- These environments can "share" plugin code - as I'll demonstrate shortly.
- These environments should use different databases.
Sample Output:
10 tests:
$ phpunit
PHPUnit 4.2.0 by Sebastian Bergmann.
..........
Time: 200 ms, Memory: 2.50Mb
OK (10 tests, 18 assertions)
2 failures:
$ phpunit
PHPUnit 4.2.0 by Sebastian Bergmann.
..F....F...
Time: 200 ms, Memory: 2.50Mb
FAILURES!
Tests: 10, Assertions: 18, Failures: 2.
Coding for PHPUnit
- PHPUnit declares the PHPUnit_Framework_TestCase class.
- Wordpress's testing framework declares the WP_UnitTestCase class (which extends PHPUnit_Framework_TestCase).
- When writing tests for your Wordpress plugins, you will extend WP_UnitTestCase, with your own unique class/es.
-
<?php class MyPlugin_Test extends WP_UnitTestCase { // Write your test logic here...
- But - let's not get ahead of ourselves, we'll explore WP_UnitTestCase after first examining PHPUnit.
What do we get from PHPUnit?
Class: PHPUnit_Framework_TestCase
- PHPUnit_Framework_TestCase provides you with a number of Assertion functions.
- These functions include:.
assertEquals($expected, $actual);
assertTrue();
assertFalse();
assertArrayHasKey();
assertEmpty();
assertFileExists(); - (and more)
Example Use
PHPUnit_Framework_TestCase Assertions
-
class My_Test extends WP_UnitTestCase { // Perform some logic $value = 1 + 1; // We expect the $value to equal '2'. $this->assertEquals(2, $value); // A single test case may include many assertions $this->assertContains('tina', array('tina', 'bill', 'tim', 'samantha') );
- Additional documentation: PHPUnit Manual > Assertions
What about WP_UnitTestCase?
- For testing of WordPress plugins/themes/core, you must download the Wordpress Developer Environment:
http://develop.svn.wordpress.org/trunk/ - This includes the WP_UnitTestCase class, in addition to the WordPress Core tasks, and the WordPress development build.
- The Wordpress Developer Environment also requires a database, and some minor configuration. I do not cover this topic in within these slides.
- But - if you're using VVV, then: the Wordpress Developer Environment, all necessary databases, and PHPUnit - will already be installed (and correctly configured) by default.
What do I inherit from WP_UnitTestCase?
I will explain these items further in my next slides:
- Object factories (Post / User / ...etc)
- setUp() function
- tearDown() function
Object Factories
- WP_UnitTestCase provides you with a number of "factories"
- These factories allow you to create new Posts / Comments / Users / ...etc, with a minimal amount of code.
-
// Create a post, return the id. $post_id = $this->factory->post->create(); // Create 10 users, return array of ids. $user_ids = $this->factory->user->create_many( 10 );
- The factory property allows you to create the following:
post attachment comment user term category tag blog
WP_UnitTestCase functions
The following functions are called immediatedly before (setUp) / after (tearDown) a test has been executed:
- setUp()
Before a test case is run, the template method called setUp() is invoked.
SetUp() is where you create the objects against which you will test.
This method is declared in PHPUnit_Framework_TestCase, and extended in WP_UnitTestCase. - tearDown()
As above, with the difference that this method is called after the test.
You may choose to use this method to flush caches / ...etc, if applicable.
Setting up your development workspace
for PHPUnit and Wordpress Plugin Development
- In the root of my (VVV) working directory [www], I have:
- [www / wordpress-develop] The Wordpress Developer Environment.
- [www / wordpress-default] An instance of wordpress (for manual testing).
- [www / myplugin-repo] The git repo for ALL my plugin's files.
- The git repo / folder has:
- [www / myplugin-repo / myplugin] a subfolder for the plugin itself.
- [www / myplugin-repo / myplugin-tests] a subfolder for my tests.
- To ensure that myplugin's code is accessible in the manual environment:
- I create a symlink to my plugin for this environment:
$ cd www/wordpress-default/wp-content/plugins $ ln -s ../../../myplugin-repo/myplugin .
Config files
phpunit.xml
This is the file that PHPUnit loads first.
<phpunit
bootstrap="bootstrap.php"
backupGlobals="false"
colors="true"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true">
<testsuites>
<testsuite>
<directory prefix="test-" suffix=".php">./</directory>
</testsuite>
</testsuites>
</phpunit>
Location:
[ www / myplugin-repo / myplugin-tests / phpunit.xml ]
Config files
bootstrap.php
This is the file that sets up the Wordpress Test Environment
<?php
// The path to the WordPress tests checkout.
define( 'WP_TESTS_DIR', '/srv/www/wordpress-develop/tests/phpunit/' );
// The path to the main file of the plugin to test.
define( 'TEST_PLUGIN_FILE', '/srv/www/myplugin-repo/myplugin/myplugin.php' );
// Load the The WordPress suite
require_once WP_TESTS_DIR . 'includes/functions.php';
// Manually load the plugin main file.
function _manually_load_plugin() {
require TEST_PLUGIN_FILE;
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
// Commence our tests
require WP_TESTS_DIR . 'includes/bootstrap.php';
Location:
[ www / myplugin-repo / myplugin-tests / bootstrap.php ]
Test Files
test-[whatever].php
Based on the configuration I've described, all PHP files in the myplugin-tests directory begining with "test-" shall be treated as tests for the purpose of exceuting phpunit from the command line.
<?php
class MyPlugin_TestComponentOne extends WP_UnitTestCase {
private $myplugin;
public function setUp() {
parent::setUp(); // Perform inherited setUp tasks.
$this->myplugin = new MyPlugin; // Get an instance of our plugin.
}
public function myPluginTest1() {
// insert test logic
assertTrue( $myplugin->do_some_plugin_stuff() );
}
public function tearDown() {
parent::tearDown(); // Perform any inherited tearDown tasks.
}
}
Location:
[ www / myplugin-repo / myplugin-tests / test-*.php ]
Tying it all together
Building your first plugin, using PHPUnit and TDD.
THIS SECTION IS COMING SOON!
Conclusion + "Gotchas"
Just some things to watch out for -
- No webserver when running PHPUNIT
- PHP caching
There are functions in PHP that explicity cache data. If you use any custom caching in your plugins (or your dependencies) - then you should clear these caches in your tearDown(). - Databases
The database attached to WP Developer Environment (i.e. specified in wp-devel/wp-config.php), will be "nuked" every time you run 'phpunit'. Be careful.