INTRO 

to

Testing

By Vaidas Mikalauskas

Testing can be:

TDD 

Test-driven development (TDD) is a development technique where you must first write a test that fails before you write new functional code

BDD

Behaviour-driven development is largely facilitated through the use of a simple domain-specific language (DSL) using natural language constructs (e.g., English-like sentences) that can express the behavior and the expected outcomes

Techniques

TDD vs BDD

We are learning:

PHPUnit

BEHAT

Feature: ls
  In order to see the directory structure
  As a UNIX user
  I need to be able to list the contents

  Scenario: List 2 files in a directory
    Given I am in a directory "test"
    And I have a file named "foo"
    And I have a file named "bar"
    When I run "ls"
    Then I should get:
      """
      bar
      foo
      """
<?php

class Ls {
    function show($dir) {
        return array_splice(scandir($dir), 2);
    }
}

class LsTest extends PHPUnit_Framework_TestCase {
    function setUp() {
        !is_dir('/tmp/files') && mkdir('/tmp/files');
    }

    function testCurrentDirectoryLists2Files() {
        $ls = new Ls();
        touch('/tmp/files/foo');
        touch('/tmp/files/bar');
        $files = $ls->show('/tmp/files');
        $this->assertEquals(['bar', 'foo'], $files);
    }
}

BEHAT

Leave it for the next time

PHPUnit

Installation

{
    "autoload": {
        "psr-4": {
            "App\\": "src"
        }
    },
    "require-dev": {
        "phpunit/phpunit": "^5.0"
    }
}

PHPUnit Config

phpunit.xml

<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
    <testsuites>
        <testsuite name="Application Test Suite">
            <directory>./tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

PHPUnit

Conventions

  • Tests must mirror your codebase and have `Test` at the end
./src/Foo.php
./src/Bar.php
./src/Controller/Baz.php
./tests/FooTest.php
./tests/BarTest.php
./tests/Controller/BazTest.php

PHPUnit

Conventions

  • Tests are public methods which start with `test` in a class that extends `PHPUnit_Framework_TestCase`
<?php namespace Tests;

class FooTest extends \PHPUnit_Framework_TestCase {

    function test...() {
        ...
    }

}

PHPUnit

Tests

<?php namespace Tests\App;

use App\TestableFoo;

class TestableFooTest extends \PHPUnit_Framework_TestCase {

    /**
     * @var TestableFoo
     */
    private $object;

    function setUp() {
        $this->object = new TestableFoo();
    }

    function testIsInitializable() {
        $this->assertInstanceOf(
            TestableFoo::class, $this->object
        );
    }

    function testGetTrueIsTrue() {
        $this->assertTrue(
            $this->object->getTrue()
        );
    }

}
<?php namespace App;

class TestableFoo {

    public function getTrue() {
        return true;
    }

}

Asserts

 

The main assertations are:

  • assertTrue
  • assertFalse
  • assertEqual

 

The list is:

https://phpunit.de/manual/current/en/appendixes.assertions.html

PHPUnit

Mocking is creating objects that

simulate the behaviour of real objects

There are a few libraries for php:

  • php-mock
  • mockery
  • prophecy
  • others

 

 

Mocking

Mocking is creating objects that

simulate the behaviour of real objects

There are a few libraries for php:

  • php-mock
  • mockery
  • prophecy
  • others

 

 

Mocking

<?php namespace App;

class Payment {
    public $from;
    public $to;
    public $amount;

    public function __construct(
        $from, $to, $amount
    ) {
        $this->from = $from;
        $this->to = $to;
        $this->amount = $amount;
    }
}

class PaymentManager {
    const $url = "https://do.more.com/payment";

    public function __construct(\GuzzleHttp\Client $client) {
        $this->client = $client;
    }

    public function makePayment(Payment $payment) {
        $response = $this->sendRequest($payment);
        return $response->getStatusCode() == 200;
    }

    private function sendRequest(Payment $payment) {
        return $this->client->request("POST", self::URL, [
            'form_params' => [
                'to' => $payment->to,
                'from' => $payment->from,
                'amount' => $payment->amount,
            ]
        ]);
    }
}

Mocking

<?php namespace Test\App;

use App\PaymentManager;
use App\Payment;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;

class PaymentManagerTest extends \PHPUnit_Framework_TestCase {

    private $client;
    private $object;

    function setUp() {
        $this->client = $this->getMockBuilder(Client::class)->getMock();
        $this->object = new PaymentManager($this->client);
    }

    function testPaymentIsSuccessfull() {
        $response = new Response();

        $this->client->expects($this->once())->method('request')
            ->will($this->returnValue($response));

        $payment = new Payment("to@datadog.org", "from@datadog.org", 10.00);
        $result = $this->object->makePayment($payment);
        $this->assertTrue($result);
    }
}

Mocking

Common questions:

  • How do I test what's in the private methods?
  • How do I test queries?
  • How do I test the code I have? (not tested before)

Constructors

<?php namespace App;

class NaughtyConstructor {
    public $html;

    public function __construct($url) {
        $this->html = file_get_contents($url);
    }

    public function getMetaTags() {
        $mime = 'text/plain';
        $filename = "data://{$mime};base64," . base64_encode($this->html);
        return get_meta_tags($filename);
    }

    public function getTitle() {
        preg_match("#<title>(.+)</title>#siU", $this->html, $matches);
        return !empty($matches[1]) ? $matches[1] : false;
    }
}

Constructors

<?php namespace Test\App;

use App\NaughtyConstructor;

class NaughtyConstructorTest extends \PHPUnit_Framework_TestCase {
    public function testGetMetaTagsReturnsArrayOfProperties() {
        $naughty = $this->getMockBuilder(NaughtyConstructor::class)
            ->setMethods(array('__construct'))
            ->setConstructorArgs(array('http://data.dog'))
            ->disableOriginalConstructor()
            ->getMock();

        $naughty->html = $this->getHtml();
        $result = $naughty->getMetaTags();
        $this->assertEquals('DATADOG', $result['author']);
    }

    protected function getHtml() {
        return '<html lang="en">
            <head>
              <meta name="title" content="Welcome"/>
              <meta name="author" content="DATADOG"/>
            </head>
          <body></body>
        </html>';
    }
}

PHPUnit

Succesfull tips

  • Testable (beautiful) code has one purpose only
  • Let's say you have code that imports data, try to do separate imports in many classes
  • If you have more than 3 (not always the truth) dependencies, it does too much

PHPUnit

Warnings

  • new keyword in a constructor or at field declaration
  • static method calls in a constructor
  • anything more than field assignment in constructors
  • control flow (conditional or looping logic) in a constructor
  • adding or using singletons
  • adding or using static fields or static methods
  • class has static methods that only operate on parameters

Thanks

Q&A

Made with Slides.com