An
Introduction to Unit Testing
(for WordPress)


A workshop with @thorstenfrommen

Who’s That Guy?

Thorsten Frommen

Thorsten Frommen

  • Certified PHP engineer, web development professional, and tester.
  • WordPress engineer and technical project lead at Inpsyde
  • Lead developer of MultilingualPress.
  • Maintainer of WP REST Starter.

Expectations

Outline

  • Testable Code
  • Unit Testing
  • Unit Test Examples
  • Writing Good Unit Tests
  • Take-aways
  • Questions
  • Hands-on Time

Testable Code

Every Piece of Software is Testable

There are several things you might be able to test:

  • the return value of a function;
  • the output of a function;
  • other side effects of executing a function;
  • whether or not a program crashes;
  • ...

Testable Code
in the Context of
Unit Testing

Unit Testing

Also component testing or module testing.

What Is Unit Testing?

Where Is Unit Testing?

The “Common” V-model

The common V-model

Unit Testing Is
Dynamic Testing of Individual Units in Isolation

Unit Testing Is Dynamic Testing

Dynamic Testing

  • Execution of code.
  • Potential defects will manifest as failures.
  • These failures will get debugged.
  • The individual bugs will get fixed.
  • As a consequence, the quality of the software will have been improved.

Validation

The process of evaluating a system or component during or at the end of the development process to determine whether it satisfies specified requirements. IEEE Std 610.12-1990

“Are we building the right product?”

Unit Testing Requires
Full Knowledge of Internals

White-box Testing

Unit testing is applying white-box testing techniques,
meaning close examination of procedural level of detail.

In order for this to work, the person designing the test is
required to have full knowledge of internals.

Subject Under Test


					function answer() {

						return '42';
					}
				

In order to test if the function behaves as expected,
we have to know what the expectation is.

Defining Units

From Characters to Classes

Possible units in code:

  • atomic level is characters;
  • form characters into words (e.g., values, keywords, and names);
  • respecting the syntax, multiple words make expressions (or statements);
  • combining words and expressions results in logic;
  • wrap logic in functions;
  • encapsulate functions (and state) in classes;
  • ...

Reasonable Units

Logic is testable, and so are functions and classes.

Going even higher to namespaces, packages and so on is no good.

Define a unit as either logic, or function, or class.

Revisiting Logic


					function convert( $number ) {

						if ( $number > 0 ) {
							return $number * 2;
						}

						return $number / 2;
					}
				

While logic might be testable, very often it is not testable as a unit.

A reasonable unit is either a function, or a class.

Testing in Isolation

Testing in Isolation

One of the most important and yet often overlooked aspects of unit testing.

Why? If a test fails, you cannot know what went wrong where and why.

If the only real code is the method under test,
you know it is the reason for a failing unit test.

Unit Test...?


					function test_register_taxonomy() {

						$tax = rand_str();

						$this->assertFalse( taxonomy_exists( $tax ) );

						register_taxonomy( $tax, 'post' );

						$this->assertTrue( taxonomy_exists( $tax ) );
						$this->assertFalse( is_taxonomy_hierarchical( $tax ) );

						unset( $GLOBALS['wp_taxonomies'][ $tax ] );
					}
				

This is not a unit test. But it has every right to exist.

Mocking Dependencies to Take Control

How to test a unit in isolation?

⇒ Define dummy functions that you have absolute control over.


Take care of two things:

  • you can execute the real method under test without any errors thrown;
  • all other units are only fake ones without real logic, if any.

Mocking Dependencies out of Convenience

It is easier to create a mock object than to instantiate a real object.

Oftentimes, this real object has dependencies of its own, which means:

  • (auto)load one or more files;
  • instantiate one or more real objects;
  • maybe provide dummy data;
  • ...

Unit Test Examples

Unit Test Example 1

Unit Test Example 1: PHP Code


					class Oracle {

						public function answer() {

							return '42';
						}
					}
				

Unit Test Example 1: JavaScript Code


					class Oracle {
						answer() {

							return '42';
						}
					}
				

Unit Test Example 1: PHP Test


					class OracleTest extends PHPUnit_Framework_TestCase {

						public function test_return_expected_answer() {

							$this->assertSame( '42', ( new Oracle() )->answer() );
						}
					}
				

Unit Test Example 1: JavaScript Test


					test( ( assert ) => {
						assert.equal( ( new Oracle() ).answer(), '42' );

						assert.end();
					} );
				

Unit Test Example 2

Unit Test Example 2: PHP Code


					class FortuneTeller {

						private $oracle;

						public function __construct( Oracle $oracle ) {

							$this->oracle = $oracle;
						}

						public function answer( $money = 0 ) {

							return ( $money < 5 ) ? '' : $this->oracle->answer();
						}
					}
				

Unit Test Example 2: JavaScript Code


					class FortuneTeller {
						constructor( oracle ) {
							this.oracle = oracle;
						}

						answer( money = 0 ) {
							return ( money < 5 ) ? '' : this.oracle.answer();
						}
					}
				

Unit Test Example 2: PHP Test


					class FortuneTellerTest extends PHPUnit_Framework_TestCase {

						public function test_return_expected_answer() {

							$answer = 'some answer here';

							$oracle = $this->createConfiguredMock( Oracle::class, [
								'answer' => $answer,
							] );

							$testee = new FortuneTeller( $oracle );

							$this->assertSame( '', $testee->answer( 0 ) );
							$this->assertSame( $answer, $testee->answer( 100 ) );
						}
					}
				

Unit Test Example 2: JavaScript Test


					test( ( assert ) => {
						const answer = 'some answer here';

						const oracle = {
							answer: () => answer
						};

						const testee = new Testee( oracle );

						assert.equal( testee.answer( 0 ), '' );
						assert.equal( testee.answer( 100 ), answer );

						assert.end();
					} );
				

Unit Test Example 3

Unit Test Example 3: PHP Code


					class TranslatingOracle {

						public function answer() {

							return sprintf(
								__( 'The answer is: %d.', 'some-text-domain-here' ),
								mt_rand()
							);
						}
					}
				

Unit Test Example 3: PHP Test


					class TranslatingOracleTest extends PHPUnit_Framework_TestCase {

						public function test_return_expected_answer() {

							Patchwork\replace( '__', function () {

								return '%d';
							} );

							$this->assertRegExp(
								'/^\d+$/',
								( new TranslatingOracle() )->answer()
							);
						}
					}
				

Unit Test Example 4

Unit Test Example 4: PHP Code


					class HookAwareOracle {

						public function answer() {

							do_action( 'give_answer' );

							return (string) apply_filters( 'answer', '42' );
						}
					}
				

Unit Test Example 4: PHP Test


					class HookAwareOracleTest extends PHPUnit_Framework_TestCase {

						public function test_return_unfiltered_answer() {

							$this->assertSame( '42', ( new HookAwareOracle() )->answer() );

							$this->assertSame( 1, did_action( 'give_answer' ) );

							$this->assertSame( 1, Monkey::filters()->applied( 'answer' ) );
						}
					}
				

Unit Test Example 5

Unit Test Example 5: JavaScript Code


					class ThankfulOracle {
						answer( money ) {
							showNotice.note( `Thanks for "${Number( money )}", buddy.` );

							return Math.random();
						}
					}
				

Unit Test Example 5: JavaScript Test


					test( ( assert ) => {
						global.showNotice = {
							note: sinon.spy()
						};

						const money = 42;

						assert.equal( typeof ( new ThankfulOracle() ).answer( money ),
							'number' );
						assert.equal( global.showNotice.note.callCount, 1 );
						assert.equal( global.showNotice.note.calledWithMatch(
							new RegExp( `"${Number( money )}"` ) ), true );

						delete global.showNotice;
						assert.end();
					} );
				

Writing Good Unit Tests

Every Unit Test Should Answer Five Questions

  1. What are you testing?
  2. What should it do?
  3. What is the actual result (i.e., output, or return value)?
  4. What is the expected result?
  5. How can the test be reproduced?

Unit Test Template: PHP


					class WhatClassTest extends PHPUnit_Framework_TestCase {

						public function test_what_method_should_do_what() {

							$actual = 'What is the actual result?';

							$expected = 'What is the expected result?';

							$this->assertSame( $expected, $actual, 'What should it do?' );
						}
					}
				

Unit Test Template: JavaScript


					import test from 'tape';

					test( 'What are you testing?', ( assert ) => {
						const actual = 'What is the actual result?';
						const expected = 'What is the expected result?';

						assert.equal( actual, expected, 'What should it do?' );

						assert.end();
					} );
				

Take-aways

Unit testing...

  • ...aims at finding defects;
  • ...involves execution of code;
  • ...means testing of individual units in isolation;
  • ...requires full knowledge of application internals;
  • ...determines if the software satisfies the requirements.

Oh, and Inpsyde is hiring! :)

References and Further Reading

Questions?


@thorstenfrommen

slides.tfrommen.de/unit-testing-workshop

Hands-on Time

Hands-on Time

Pick whatever interests you the most:

Thanks!


@thorstenfrommen

slides.tfrommen.de/unit-testing-workshop