TDDing the “Gattiny” plugin – 02

Part of a series

This is the second post in a series of posts chronicling my attempt to use test-driven development techniques to develop the “Gattiny” WordPress plugin.
The first post is probably a better starting point than this one.

Picking up where I’ve left

I’ve concluded the first post writing a first, successful, functional test.
Trying to follow a proper TDD flow I’m writing another test with the intent of watching it fail; it’s the translation into code of the second statement outlining the plugin work:

when uploading a GIF animated image each resized version should still be an animated image

Since I had created a Cest format test before I’m adding the new test method to the tests/functional/FormatCreationCest.php file.
The tricky part of the assertion I’m trying to make is at the end of the sentence:

[…] should still be an animated image.

Asserting that an image is an animated one will require using much, if not all, of the code that I’m using in the code itself; this if often the case with test-driven development that provides yet another benefit to reap.

Assertions

In the test method I’m writing, I’ll need PhpUnit like assertions and Codeception provides a module, the Assertions one, to use in acceptance and fucntional tests that provides that kind of methods.
To have the methods provided by the module available in my test methods, I’m including the Asserts module in the tests/functiona.suite.yml configuration file:

# Codeception Test Suite Configuration

# Suite for WordPress functional tests.
# Emulate web requests and make the WordPress application process them.


class_name: FunctionalTester
modules:
    enabled:
        - \Helper\Functional
        - Filesystem
        - WPDb
        - WPBrowser
        - Asserts
    config:
        WPDb:
            dsn: 'mysql:host=localhost;dbname=wp'
            user: root
            password: root
            dump: tests/_data/dump.sql
            populate: true
            cleanup: true
            url: 'http://wp.dev'
            tablePrefix: wp_
        WPBrowser:
            url: 'http://wp.dev'
            adminUsername: admin
            adminPassword: admin
            adminPath: /wp-admin


The module requires no configuration parameters and all that’s left to rebuild the suites:

wpcept build

Counting the frames

The Imagick extension provides an easy to use API to manipulate and gather information about images that I can use to “count” the frames in the original image; once I have that value, I’ll know the plugin is doing its job when the same frame count is read from the resized images.
Using the Imagick::count method the assertion is easy enough to write in the test method:

// file tests/functional/FormatCreationCest.php

/**
 * It should create an animated resized version of each animated GIF image
 * @test
 */
public function create_an_animated_resized_version_of_each_animated_gif_image( FunctionalTester $I ) {
    $I->loginAsAdmin();
    $I->amOnAdminPage( 'media-new.php' );
    $I->attachFile( 'input[name="async-upload"]', $this->gif );
    $I->click( 'input[name="html-upload"]' );

    $I->seeResponseCodeIs( 200 );

    $I->amInPath( $this->uploads );
    foreach ( $this->sizeMap as $slug => $size ) {
        $suffix = '' !== $size ? '-' . $size : '';
        $file   = $this->uploads . DIRECTORY_SEPARATOR . basename( $this->gif, '.gif' ) . $suffix . '.gif';
        $image  = ( new Imagick( $file ) )->coalesceImages();
        $I->assertEquals( $this->frameCount, $image->count() );
    }
}

And the manual test I had run before starting to work is confirmed by the test failure:

The minimum required code

Now, the important step: writing the code to pass the test.
This is the step where I used to get lost; in over-engineering for the most part.
What I’ll do next is to write the minimum amount of code needed to pass the second test; I will get back to the implementation and lower level tests later.
After some fiddling and much struggle, here it is the updated plugin code:

// file gattiny.php
<?php
/*
Plugin Name: Gattiny
Plugin URI: https://wordpress.org/plugins/gattiny/
Description: Resize animated GIF images on upload.
Version: 0.1.0
Author: Luca Tumedei
Author URI: http://theaveragedev.local
Text Domain: gattiny
Domain Path: /languages
*/

add_filter( 'wp_image_editors', 'gattiny_filterImageEditors' );
function gattiny_filterImageEditors( array $imageEditors ) {
    require_once dirname( __FILE__ ) . '/src/GifEditor.php';

    array_unshift( $imageEditors, 'gattiny_GifEditor' );

    return $imageEditors;
}

The code of the version of the gattiny_GifEditor class passing the tests can be found on GitHub.

Are we sure?

Test-driven development serves many purposes and one of the prominent ones is automation.
Being able to automate tests that are usually in the domain of users means saving much time and “trusting” the tests; sooner or later any “manual” verification of an exception or edge case is lost in turnover and handoffs until it’s forgotten.
Tests, on the other hand, are good for years.
I trust myself little to none when it comes to remembering, and the lingering question is:

So far the tests are asserting creation, number, and frames: but are the images correctly resized?

The quality of the images, a “correct” resizing criteria, can be attested by looking them up by hand, and asserting that what I see in the original picture I still see in the resized one at different dimensions.
But this procedure has all the qualities of a human-driven test that cannot be replicated in an automated test; I’d like but to try and make that assertion too one that can be run in a test.
Once again it’s just a question of adding a test method to the testcase:

/**
 * It should create resized version that are still the same image
 *
 * @test
 */
public function create_resized_version_that_are_still_the_same_image(FunctionalTester $I) {
    $I->loginAsAdmin();
    $I->amOnAdminPage('media-new.php');
    $I->attachFile('input[name="async-upload"]', $this->gif);
    $I->click('input[name="html-upload"]');

    $I->seeResponseCodeIs(200);

    $originalCoalesced = (new Imagick(codecept_data_dir($this->gif)))->coalesceImages();

    $I->amInPath($this->uploads);
    foreach ($this->sizeMap as $slug => $size)
    {
        if ($slug === 'full')
        {
            continue;
        }
        $suffix           = '' !== $size ? '-' . $size : '';
        $file             = $this->uploads . DIRECTORY_SEPARATOR . basename($this->gif, '.gif') . $suffix . '.gif';
        $resizedCoalesced = (new Imagick($file))->coalesceImages();
        list($w, $h) = explode('x', $size);
        // we test just the first frame
        $originalFrame = $originalCoalesced->getImage();
        $resizedFrame  = $resizedCoalesced->getImage();
        $originalFrame->resizeImage($w, $h, Imagick::FILTER_BOX, 1);
        $comparison = $originalFrame->compareImages($resizedFrame, Imagick::METRIC_ROOTMEANSQUAREDERROR);
        $I->assertTrue(0 <= $comparison[1] && $comparison[1] <= 0.1, "The {$slug} format image is not the same.");
    }
}

Running the functional suite again sees it fail:

Manually verifying the assertion confirms it: the thumbnail format image has no resemblance with the original; the other images, just resized and not cropped, are “instead” very close.

Next

The code I’ve shown in the post is on GitHub tagged post-02.
Now that I’ve got a failing test I have a result to “work to” in the next article.