rootpd@webelement:~$ whoami

Peťo Dulačka

  • Backend dev
    • PHP, Golang, Ruby
  • Not afraid to go into deep refactoring
    • Manual testing to rescue
    • Code reviews to rescue
    • CI and automated tests to rescue
    • Production logs to rescue (:sadface:)

#awesome-*

Awesome lists are awesome

Currated lists of tools

  • https://github.com/sindresorhus/awesome
  • https://github.com/ziadoz/awesome-php
  • https://github.com/markets/awesome-ruby
  • https://github.com/avelino/awesome-go
  • https://github.com/christian-bromann/awesome-selenium
  • https://github.com/atinfo/awesome-test-automation

Basics (tests vocabulary)

setUp/tearDown, beforeEach/afterEach

Support methods for tests lifecycles to prepare repetitive sets of data and to verify and cleanup after the test.

Dummies

Double has no behaviour, it only extends or implements something

Stubs

A stub is an object double, which doesn't have any expectations about the object behavior, but when put in specific environment, behaves in specific way.

Fake

Similar to Stub, but constains simple logic to decide what to return.

Mocks

Doubles having a expectation (e.g. a method will be called)

Spies

Track method calls for latter examination.

PHPUnit

PHP Git wrapper

  • https://github.com/sebastianbergmann/git
  • 52 stars, 20 forks
    public function __construct($repositoryPath) {
        $this->repositoryPath = realpath($repositoryPath);
    }
    public function checkout($revision) {
        $this->execute('git checkout --force --quiet ' . $revision . ' 2>&1');
    }
    public function getCurrentBranch() {
        $output = $this->execute('git symbolic-ref HEAD');
        $tmp = explode('/', $output[0]);
        return $tmp[2];
    }

    /* more commands ... */

PHPUnit 4.* changelog

  • The latest interesting new in January 2015 (PEAR support discontinued)
  • Prophecy support since 4.5
  • very minor updates for a very long time (old/complete)
  • xUnit family for easy integration with CI

PHPUnit 5.0 changelog

  • Released October 2nd
  • Support for PHP 5.6 and PHP 7.0
  • No major feature added, incremented because of dropping support of <5.6
  • Some asserts added, some removed, --strict switch and @depends upgrades
  • Noone in my Twitter feed noticed, Wikipedia didn't notice either

PHPUnit extensions

  • Code coverage
  • Mock support
  • No custom third party implementation really..

Paratest

  • Concurrency for PHPUnit
  • Just specify amount of cores and profit! Sort of...
  • TEST_TOKEN for working with DB/external dependencies
  • Only for pure unit tests

PHPUnit VW

class VWTest extends PHPUnit_Framework_TestCase {
    private $emissions = 12000;
    private $legalLimit = 300;

    public function testEnvironmentalImpactCompliance() {
        $this->assertLessThan($this->legalLimit, $this->emissions);
    }
}

PHPUnit VW

VW makes failing test cases succeed in continuous integration tools.

Your primary objective is to ship more code to the world. No need to be slowed down by regressions or new bugs that happen during development.

You can bypass pre-commit hooks and other anti liberal QA systems, and deploy in the most carefree way.

  • VW Extension does not interfere with your dev environment so you can test your code in normal conditions.
  • It automatically detects CI environments and makes your test suites succeed even with failing assertions or unwanted exceptions \o/

Codeception

Codeception

  • Covering unit, functional and integration tests.
  • Huge amounts of modules (some obsolete since 2.0)
  • Support for frameworks (Symfony2, Laravel, Yii, Zend)
  • Ships with lot of great modules (REST, Db, Doctrine, WebDriver, Ftp..)
  • Easily configurable and integratable custom helpers.

Codeception

$I->wantTo('create a new user by API');
$I->amHttpAuthenticated('davert','123456');
$I->haveHttpHeader('Content-Type','application/x-www-form-urlencoded');
$I->sendPOST('/users', array('name' => 'davert' ));
$I->seeResponseCodeIs(200);
$I->seeResponseIsJson();
$I->seeResponseContainsJson(array('result' => 'ok'));

Peridot

Peridot

  • Write tests using the describe-it syntax (know rspec?).
  • Scopes are your friend.
  • Allows to use your custom DSL.
  • Event driven for easier customization (more plugins!)

Peridot

describe('TodoRepository', function () {
    beforeEach(function () {
        $interface = 'Doctrine\Common\Persistence\ObjectManager';
        // lets assume we are using the peridot-prophecy-plugin
        $this->em = $this->getProphet()->prophesize($interface);
        $this->repository = new TodoRepository($this->em->reveal());
    });

    afterEach(function () {
        $this->getProphet()->checkPredictions();
    });

    context('when calling ->get()', function () {
        it('should find the todo', function () {
            $this->repository->get(1);
            $this->em->find('Todos\Todo', 1)->shouldBeCalled();
        });
    });
});

Behat

Intro

  • "Behat is a tool that makes behavior driven development (BDD) possible."
  • Inspired by Ruby’s Cucumber project, especially its syntax (called Gherkin).
  • Goal is to make non-developer people write features and tests at once by almost plain English
  • Probably unachievable, but would be amazing...

Gherkin

Feature: Some terse yet descriptive text of what is desired
  In order to realize a named business value
  As an explicit system actor
  I want to gain some beneficial outcome which furthers the goal

  Scenario: Some determinable business situation
    Given some precondition
      And some other precondition
     When some action by the actor
      And some other action
      And yet another action
     Then some testable outcome is achieved
      And something else we can check happens too

  Scenario: A different situation
      ...

Features

  • Every *.feature file conventionally consists of a single “feature”.
  • A line starting with the keyword Feature: followed by its title and three indented lines defines the start of a new feature.
  • A feature usually contains a list of scenarios.
  • Each scenario starts with the Scenario: keyword followed by a short description of the scenario. Under each scenario is a list of steps, which must start with one of the following keywords: Given, When, Then, But or And. Behat treats each of these keywords the same, but you should use them as intended for consistent scenarios.
/** @Given /^I am in a directory "([^"]*)"$/ */
public function iAmInADirectory($dir) {
    if (!file_exists($dir)) {
        mkdir($dir);
    }
    chdir($dir);
}

Support libraries

Prophecy

class UserTest extends PHPUnit_Framework_TestCase {
    private $prophet;

    public function testPasswordHashing() {
        $hasher = $this->prophet->prophesize('App\Security\Hasher');
        $user = new App\Entity\User($hasher->reveal());
        $hasher->generateHash($user, 'qwerty')->willReturn('hashed_pass');
        $user->setPassword('qwerty');
        $this->assertEquals('hashed_pass', $user->getPassword());
    }

    protected function setup() {
        $this->prophet = new \Prophecy\Prophet;
    }

    protected function tearDown() {
        $this->prophet->checkPredictions();
    }
}

Prophecy

Stubs define Promises

  • ReturnPromise or ->willReturn(1) - returns a value from a method call
  • ReturnArgumentPromise or ->willReturnArgument($index) - returns the nth method argument from call
  • ThrowPromise or ->willThrow - causes the method to throw specific exception
  • CallbackPromise or ->will($callback) - gives you a quick way to define your own custom logic

Prophecy

Mocks define Predictions

  • CallPrediction or shouldBeCalled() - checks that the method has been called 1 or more times
  • NoCallsPrediction or shouldNotBeCalled() - checks that the method has not been called
  • CallTimesPrediction or shouldBeCalledTimes($count) - checks that the method has been called $count times
  • CallbackPrediction or should($callback) - checks the method against your own custom callback

VCR

  • Automatically records and replays your HTTP(s) interactions with minimal setup/configuration code.
  • Supports common http functions and extensions
    • everyting using streamWrapper: fopen(), fread(), file_get_contents(), ... without any modification
    • SoapClient by adding \VCR\VCR\turnOn(); in your tests/bootstrap.php
    • curl(), by adding \VCR\VCR::turnOn(); in your tests/bootstrap.php
  • The same request can receive different responses in different tests -- just use different cassettes.
  • Disables all HTTP requests that you don't explicitly allow by setting the record mode
  • Request matching is configurable based on HTTP method, URI, host, path, body and headers, or you can easily implement a custom request matcher to handle any need.
  • The recorded requests and responses are stored on disk in a serialization format of your choice (currently YAML and JSON are built in, and you can easily implement your own custom serializer)

PHP-VFS (virtual file system)

"If you need to ask what you'd use a virtual file system for, you probably don't need one, but just in case, I've compiled a small list of examples:"

  • Testing file system libraries without writing to disc
  • Runtime evaluation without eval (via write and require)

// Create and mount the file system
$fs = FileSystem::factory('vfs://');
$fs->mount();

// Add `/foo` and `/foo/bar.txt`
$foo = new Directory(['bar.txt' => new File('Hello, World!')]);
$fs->get('/')->add('foo', $foo);

// Get contents of `/foo/bar.txt`
$fs->get('/foo/bar.txt')->getContent(); // Hello, World!
file_get_contents('vfs://foo/bar.txt'); // Hello, World!

Faker

Factories for Base, Lorem Ipsum Text, Person, Address, Phone Number, Company, Real Text, Date and Time, Internet, User Agent, Payment, Color, File, Image, Uuid, Barcode, Miscellaneous, Biased

$faker = Faker\Factory::create();

// generate data by accessing properties
echo $faker->name;
  // 'Lucy Cechtelar';
echo $faker->address;
  // "426 Jordy Lodge
  // Cartwrightshire, SC 88120-6700"
echo $faker->text;
  // Dolores sit sint laboriosam dolorem culpa et autem. Beatae 
  // nam sunt fugit et sit et mollitia sed.

Alice

Nelmio\Entity\User:
    user{1..10}:
        username: <username()>
        fullname: <firstName()> <lastName()>
        birthDate: <date()>
        email: <email()>
        favoriteNumber: 50%? <numberBetween(1, 200)>

Nelmio\Entity\Group:
    group1:
        name: Admins
        owner: @user1
        members: <numberBetween(1, 10)>x @user*
        created: <dateTimeBetween('-200 days', 'now')>
        updated: <dateTimeBetween($created, 'now')>
$objects = \Nelmio\Alice\Fixtures::load(__DIR__.'/fixtures.yml', $objectManager);

Alice

  • Based on top of the Faker
  • Third party libraries
    • Symfony2
    • Zend2
    • Nette

Golang testing

Go

  • Designed with testing in mind
  • Not an xUnit library you'd expect
  • "go test" binary provided within language

Why does Go have no assertions?

They are undeniably convenient, but our experience has been that programmers use them as a crutch to avoid thinking about proper error handling and reporting.

Proper error reporting means that errors are direct and to the point, saving the programmer from interpreting a large crash trace. Precise errors are particularly important when the programmer seeing the errors is not familiar with the code.

Proper error handling means letting other tests run after one has failed, so that the person debugging the failure gets a complete picture of what is wrong. It is more useful for a test to report that isPrime gives the wrong answer for 2, 3, 5, and 7 (or for 2, 4, 8, and 16) than to report that isPrime gives the wrong answer for 2 and therefore no more tests were run. The programmer who triggers the test failure may not be familiar with the code that fails. Time invested writing a good error message now pays off later when the test breaks.

Code analysis

PHP Metrics (http://www.phpmetrics.org)

  • Nice HTML and terminal output
  • Non-trivial metrics
  • Visualizations
  • But some metrics are too abstract

PHP Metrics

PHP Mess Detector (http://phpmd.org)

  • Different level of checking
  • Buildable rule sets. Default are: cleancode, codesize, controversial, design, naming, unusedcode
  • Supports custom rules
  • CI ready

If nothing, go through http://phpmd.org/rules/index.html to read about good practices.

IdentityRestController.php:35 Avoid unused parameters such as '$request'.
IdentityRestController.php:35  Avoid unused parameters such as '$paramFetcher'.
IdentityRestController.php:71  Avoid variables with short names like $id. Configured minimum length is 3.
IdentityRestController.php:116 Avoid variables with short names like $id. Configured minimum length is 3.
IdentityRestController.php:123 The method putIdentityAction uses an else expression.
Else is never necessary and you can simplify the code to work without else.
IdentityRestController.php:150 Avoid variables with short names like $id. Configured minimum length is 3.

PDepend (http://pdepend.org)

Seven don'ts of testing by DHH

1. Don’t aim for 100% coverage.

2. Code-to-test ratios above 1:2 is a smell, above 1:3 is a stink.

3. You’re probably doing it wrong if testing is taking more than 1/3 of your time. You’re definitely doing it wrong if it’s taking up more than half.

4. Don’t test standard Active Record associations, validations, or scopes.

5. Reserve integration testing for issues arising from the integration of separate elements (aka don’t integration test things that can be unit tested instead).

6. Don’t use Cucumber unless you live in the magic kingdom of non-programmers-writing-tests (and send me a bottle of fairy dust if you’re there!)

7. Don’t force yourself to test-first every controller, model, and view (my ratio is typically 20% test-first, 80% test-after).