Intro To Unit Testing

and PHPUnit

study-group-06

Created by Juan Manuel Torres / @onema / onema.io

Follow Live

http://slides.com/onema/intro-to-unit-testing/live  

What is Unit Testing?

It refers to the practice of testing individual units of source code: functions, methods and different areas of our code.

 

The purpose is to ensure that given a set of inputs a unit returns an expected output 

What is PHPUnit?

PHPUnit is a programmer-oriented testing framework for PHP.
It is an instance of the xUnit architecture for unit testing frameworks. [8]

Installing PHPUnit

NOTE: If you have the vaprobash VM provided by the Study Group, PHPUnit is already installed globally 

// composer.json
{
    "require-dev": {
        "phpunit/phpunit": "4.2.*"
    }
}
composer global require "phpunit/phpunit=4.2.*" 

Or install it globally

Writing Tests

Assert True

// test/SDPHP/Test/MyTest.php
class MyTest extends \PHPUnit_Framework_TestCase
{
    public function testTrue()
    {
        $this->assertTrue(false);
    }
}
PHPUnit 4.1.4 by Sebastian Bergmann.

Configuration read from /vagrant/studygroup/phpunit.xml

F

Time: 29 ms, Memory: 3.50Mb

There was 1 failure:

1) SDPHP\Test\MyTest::testAssertTrue
Failed asserting that false is true.

/vagrant/studygroup/test/SDPHP/Test/MyTest.php:21
                                     
FAILURES!                            
Tests: 1, Assertions: 1, Failures: 1.

Writing Tests

Assert False

    public function testAsserts()
    {
        // . . .
        $this->assertFalse(false);
    }
PHPUnit 4.1.4 by Sebastian Bergmann.

Configuration read from /vagrant/studygroup/phpunit.xml

.

Time: 29 ms, Memory: 3.50Mb

OK (1 test, 1 assertion)

Writing Tests

Assert Equals

    public function testAsserts()
    {
        $foo = 'soemthing';
        $bar = $foo;
        $this->assertEquals($foo, $bar, , 'Values are not equal.');
    }

Available Assertions

There are multiple assertions to help you test. Take a look at a complete list here.

A Simple Example

Vehicles

Vehicle Interface

Car and Boat Classes

interface Vehicle {

    public function drive ();

}
class Car implements Vehicle {

    public function drive () {
        return 'Move on ground';
    }
}
class Boat implements Vehicle {

    public function drive () {
        return 'Move on water';
    }
}

Test the Car and Boat Classes

// test/SDPHP/Test/MyTest.php
class VehiclesTest extends \PHPUnit_Framework_TestCase
{
    public function testCar()
    {
        $car = new Car();
        $expectedResult = 'Move on ground';
        $message = $car->drive();
        $this->assertEquals($expectedResult, $message);
    }

    public function testBoat()
    {
        $boat = new Boat();
        $expectedResult = 'Move on water';
        $message = $boat->drive();
        $this->assertEquals($expectedResult, $message);
    }
}

Test the correct

message

Adding People to our car

class Person 
{
    const PASSENGER = 'passenger';
    const DRIVER = 'driver';
    private $type;
    private $name;

    public function __construct($name, $type = 'passenger') {
        $this->type = $type;
        $this->name = $name;
    }

    public function getType() {
        return $this->type;
    }

    public function setType($type) {
        $this->type = $type;
    }

    public function getName() {
        return $this->name;
    }
}

Adding People to our car

class Car {
    const SIZE = 2;

    public function addPerson(Person $person) {
        $type = $person->getType();
    
        if ($this->personCount == self::SIZE) {
            throw new \Exception('Car is full, cannot add another person.');
        }
    
        if ($type == Person::DRIVER) {
            if (empty($this->personList[Person::DRIVER])) {
                $this->personList[Person::DRIVER][] = $person;
                $this->personCount++;
                return 'Person ' .  $person->getName() . ' is the driver';
            } else {
                throw new \Exception('Some one is already driving the car.');
            }
        } else {
            $this->personList['passenger'][] = $person;
            $this->personCount++;
            return 'Person ' .  $person->getName() . ' is a passenger';
        }
    }

    public function getPersonCount() {
        return $this->personCount;
    }
}

Check for size and throw Exception

Check for Driver type and

add it if it is

Throw exception if we attempt to add 2 drivers

Every other person in the car is a passenger

Count the number of opeople in the car

Test add a Driver

public function testCarAddDriver($car)
{
    if ($car instanceof Car) {
        $person = new Person('Juan', Person::DRIVER);

        $message = $car->addperson($person);
        $this->assertEquals(1, $car->getPersonCount(), 'Car count is incorrect.');




        $this->assertEquals(
            'Person ' .  $person->getName() . ' is the driver.', 
            $message);
    }

    return $car;
}

Test count and set a custom message

Test the message return by the addPerson method

Return the Car to be used in other test

Test add Passenger

/**
 * @depends testCarAddDriver
 */
public function testCarAddPassenger($car)
{

    $person = new Person('Jolene', Person::PASSENGER);

    $message = $car->addPerson($person);
    $this->assertEquals(2, $car->getPersonCount(), 'Car count is incorrect.');
    $this->assertEquals(
        'Person ' .  $person->getName() . ' is a passenger', 
        $message);
    return $car;
}

Annotation will tell PHPUnit to Inject the $car returned by testCarAddDriver

It will be injected as a method parameter

Test Exception

/**
 * @depends testCarAddPassenger
 */
public function testCarDriverException($car)
{
    $this->setExpectedException(
        '\Exception', 
        'Some one is already driving the car');

    if ($car instanceof Car) {
        $person = new Person('John', Person::DRIVER);
        $car->addPerson($person);
    }
}

Set an expectation

Exception type and Message

No assertion needed, we use setExpectedException

New Method:

Remove Person from the car

class Car {
    // . . .
    public function removePerson($type = 'passenger') {
        if (!empty($this->personList[$type])) {
            $person = array_pop($this->personList[$type]);
            $this->personCount--;
            return 'Person ' . $person->getName() . ' was kicked out of the car.';
        } else {
            return 'There are no people in the car.';
        }
    }
}

For simplicity we'll use array_pop to remove a person

Test Remove person

/**
 * @depends testCarAddPassenger
 */
public function testRemovePersonInOrder($car)
{
    $message = $car->removePerson(Person::PASSENGER);
    $this->assertEquals(1, $car->getPersonCount(), 'Car count is incorrect');
    $this->assertEquals('Person Jolene was kicked out of the car.', $message);

    $message = $car->removePerson(Person::DRIVER);
    $this->assertEquals(0, $car->getPersonCount(), 'Car count is incorrect.');
    $this->assertEquals('Person Juan was kicked out of the car.', $message);

    $message = $car->removePerson(Person::PASSENGER);
    $this->assertEquals(0, $car->getPersonCount(), 'Car count is incorrect.');
    $this->assertEquals('There are no people in the car.', $message);

    $message = $car->removePerson(Person::DRIVER);
    $this->assertEquals(0, $car->getPersonCount(), 'Car count is incorrect.');
    $this->assertEquals('There are no people in the car.', $message);
}

PHPUnit Mock Objects

The practice of replacing an object with a test double that verifies expectations, for instance asserting that a method has been called, is refered to as mocking.

 

Title Text


protected $driverMock;

public function setup() {

    // DRIVER
    $this->driverMock =
    $this->getMockBuilder('\SDPHP\StudyGroup06\Person')
         ->disableOriginalConstructor()
         ->setMethods(['getType', 'getName'])
         ->getMock();

    $this->driverMock
         ->expects($this->any())
         ->method('getType')
         ->willReturn(Person::DRIVER);

    $this->driverMock
         ->expects($this->any())
         ->method('getName')
         ->willReturn('robot-driver');
}

Use PHPUnit MockBuilder

Set method return

values

The setup method will be executed before ever test

Test using the Mock

public function testCarAddDriver() {
    $car = new Car();
    $message = $car->addperson(clone $this->driverMock);
    $this->assertEquals(1, $car->getPersonCount(), 'Car count is incorrect');
    $this->assertEquals('Person robot-driver is the driver', $message);

    return $car;
}

THE END

BY Juan Manuel Torres / onema.io / @onema / kinojman@gmail.com

Reference

Intro to Unit Testing

By Juan Manuel Torres

Intro to Unit Testing

  • 1,476