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
- Unit Testing Tutorial Part 1: Introduction to PHPUnit
- Unit Testing Tutorial Part 2: Assertions, Writing a Useful Test and @dataProvider
- Unit Testing Tutorial Part 3: Testing Protected/Private Methods, Coverage Reports and CRAP
- Unit Testing Tutorial Part 4: Mock Objects, Stub Methods and Dependency Injection
- Unit Testing Tutorial Part 5: Mock Methods and Overriding Constructors
- Real-World Solutions for Developing High-Quality PHP Frameworks and Applications (book)
- The Beginner's Guide to Unit Testing: What Is Unit Testing?
- PHPUnit
Intro to Unit Testing
By Juan Manuel Torres
Intro to Unit Testing
- 1,534