two-test-suites



two-test-suites

0 0


two-test-suites

WordPress, API Mocking, and Integrations

On Github ericmann / two-test-suites

A Tale of Two Test Suites

Presented by Eric Mann / @ericmann

It was the best of code,

It was the worst of code

No code is ever written, shipped, or executed entirely bug-free. It can be the best code on the planet, but given time, will be proven to suck.

Technical Debt

  • Poor abstractions
  • Insufficient namespacing
  • Singletons and other anti-patterns
All code is riddled with things we know we should change ... later ... when we have time ... after that other thing.

Integration Testing

  • Database interaction
  • Remote APIs
  • Micro-services
  • Downstream consumers
Your code will inevitably need to interact with some other system - these integration points need to be fully tested.

Regression Testing

  • Customers will report bugs in your application
  • A large development team will re-introduce fixed bugs
  • Tests can help ensure these bugs don't derail your project
Automated unit tests help ensure that bugs don't reappear as you're developing an application.

Guilt

I once worked with a developer who ...

No one wants to be that guy at the conference admitting they've not yet built out a test suite.

WordPress + Testing = ?

  • Not many people test their WordPress code
  • Core, however, requires testing
I'll be the first to admit it: Not many people really test their WordPress code ...

Two Ways to Test

  • Use the WordPress Core testing framework
  • Mock your dependency on WordPress with WP_Mock
If you're building code for WordPress, either core or a theme or plugin, you should test things to keep your code stronger. There are two ways you can do it, too!

Core Test Framework

The core testing framework is, obviously, part of WordPress core.

Core Framework: History

  • Originally by Nikolay Bachiyski in 2011
  • Officially adopted in 2012 (Test Trac #42)
The core test framework started as an external project, became a sister project to WordPress itself, and was eventually rolled in to the core development suite itself.

Core Framework: Coverage

  • The entire WordPress API is available
  • Data is written to and read from MySQL
  • All new code for core must have tests
The test suite doesn't have 100% code coverage for WordPress ... yet ...

Core Framework: Goals

The Core Framework aims to test the entire WordPress stack

The Core testing framework isn't a unit test suite, it's an integration test suite. It loads the entire WordPress API and communicates actively with a real MySQL database.

Core Framework: Example

class Importer {
  /**
   * @var Array Mapping of MT usernames to WordPress user IDs
   */
  private $mtnames = array();

  /**
   * Check that the author exists within WordPress and, if not, create it.
   *
   * @param String $author
   *
   * @return int|\WP_Error
   */
  public function checkauthor( $author ) {
    $pass = wp_generate_password();
    if ( ! isset( $this->mtnames[ $author ] ) ) {
      $user_id = wp_create_user( $author, $pass );
      $this->mtnames[ $author ] = $user_id;
    } else {
      $user_id = $this->mtnames[ $author ];
    }

    return $user_id;
  }
}
Our test code comes from a MovableType importer. Our object needs to test incoming author names against existing WordPress objects. First things first, it tests to see if we already know of an author - if so, we return the existing ID and move along. If not, we create a new WordPress user, store their ID for later, and return the new entry.

Core Framework: Example

/**
 * Make sure new authors are created in WordPress
 */
public function test_checkauthor() {
  $command = new Importer();

  // First author
  $mary_id = $command->checkauthor( 'maryshelley' );
  $mary = \get_user_by( 'id', $mary_id );
  $this->assertEquals( 'maryshelley', $mary->user_login );

  // Second author
  $jane_id = $command->checkauthor( 'janeausten' );
  $this->assertNotEquals( $mary_id, $jane_id );

  // Repeat first
  $mary_redux = $command->checkauthor( 'maryshelley' );
  $this->assertEquals( $mary_id, $mary_redux );
}
We test our checkauthor() function by requesting authors that should be in the database. If a user doesn't exist, a new entry is created. We want to verify new entries are created each time and are unique. We'll test against a real WordPress database.

Integration vs Unit Testing

Two sides of the same coin ...

Integration testing and unit testing are different, but share many of the same goals: preventing regressions, ensuring proper behavior, and documenting potential side-effects.

WP_Mock

WP Mock isn't part of core. As the name implies, it allows you to mock WordPress in your tests.

WP_Mock: History

WP_Mock started as a passion project because I didn't want to install MySQL or other development tools on my laptop.

WP_Mock: Coverage

  • APIs are not available unless you define them
  • No external services/integrations are used
  • Every class/namespace can be tested separately
The point of the mocking framework is to test your code in isolation from the environment in which it runs - you have to define your dependencies explicitly.

WP_Mock: Goals

WP_Mock tests only your code, not its environment

WPMock isn't an integration testing suite, its goal is to separate your code into the smallest _units possible and test them individually.

WP_Mock: Example

class Importer {
  /**
   * @var Array Mapping of MT usernames to WordPress user IDs
   */
  private $mtnames = array();

  /**
   * Check that the author exists within WordPress and, if not, create it.
   *
   * @param String $author
   *
   * @return int|\WP_Error
   */
  public function checkauthor( $author ) {
    $pass = wp_generate_password();
    if ( ! isset( $this->mtnames[ $author ] ) ) {
      $user_id = wp_create_user( $author, $pass );
      $this->mtnames[ $author ] = $user_id;
    } else {
      $user_id = $this->mtnames[ $author ];
    }

    return $user_id;
  }
}
For comparison's sake, we're going to use the same code as before. This way, we compare apples to apples when viewing the two different test paradigms.

WP_Mock: Example

/**
 * Make sure new authors are created in WordPress
 */
public function test_checkauthor() {
  \WP_Mock::wpFunction( 'wp_generate_password', array(
    'times'  => 3,
    'return' => 'password',
  ) );

  \WP_Mock::wpFunction( 'wp_create_user', array(
    'times'           => 2,
    'return_in_order' => array( 1, 2 ),
  ) );

  $command = new Importer();

  // First author
  $mary_id = $command->checkauthor( 'maryshelley' );
  $this->assertEquals( 1, $mary_id );

  // Second author
  $jane_id = $command->checkauthor( 'janeausten' );
  $this->assertNotEquals( $mary_id, $jane_id );

  // Repeat first
  $mary_redux = $command->checkauthor( 'maryshelley' );
  $this->assertEquals( $mary_id, $mary_redux );
}
We test the same function as before, but this time we use WPMock to _mock WordPress' API. There is no wp_generate_password() or wp_create_user() function, so we have to program the implementations.

Implementation

  • Keep your test goals in mind
  • The Core framework is for integration testing
  • The mocking framework is for unit testing
  • Ultimately, both are free...
Always keep in mind the final goals with which you started testing: What are we testing? Why are we testing it? What will a failing test tell us?

Questions?

Closing

If this is the kind of thing that interests you, we're hiring ...
A Tale of Two Test Suites Presented by Eric Mann / @ericmann