Unit Testing

I don't have time to write tests because I am too busy

Types of software testing

  • Unit Testing (do the parts perform correctly alone?)
  • Integration Testing (do the parts perform correctly together?)
  • User Acceptance Testing (does the system meet the end user's expectations?)

Types of software testing

Unit Testing vs Integration Testing

  • Unit Testing tests one layer
  • Integration Testing tests across layers

Concept of Unit Testing

  • A unit test is code that tests as small as possible a piece of functionality (the unit)
  • One function may have multiple unit tests according to the usage and outputs of the function
  • tests ensure
    • the code meets expectations and specifications
    • the code continues to meet expectations over time: avoiding regression
  • tests are consider as code behavior documentation

Unit Test Best Practices

  • Consistent
  • Atomic
  • Single Responsibility
  • Self descriptive
  • No conditional logic or loops
  • No exception handling
  • Informative Assertion messages

Consistent

Multiple runs of the test should consistently return true or consistently return false, provided no changes

Atomic

Only two possible results : PASS or FAIL
No partially successful tests

No side effects between tests


Isolation of tests

  • Different execution order must yield same results
  • Test B should not depend on outcome of Test A (use mocks instead)

Single Responsibility

One test should be responsible for one scenario only
Test behavior, not methods

  • One method, multiple behaviors -> multiple tests
  • One behavior, multiple methods -> one test

Self-descriptive

Unit test must be easy to read and understand

  • variable, method and class names must be self descriptive
  • no conditional logic
  • no loops

Name tests to represent PASS conditions:

  • canMakeReservation
  • totalBillEqualsSumOfMenuItemPrices
<?php
namespace Riskio\SpecificationTest;

use Riskio\Specification\AndSpecification;
use Riskio\SpecificationTest\Fixtures\NotSatisfiedSpecification;
use Riskio\SpecificationTest\Fixtures\SatisfiedSpecification;

class AndSpecificationTest extends \PHPUnit_Framework_TestCase
{
    /**
     * @test
     */
    public function isSatisfiedBy_GivenBothSpecificationsSatisfied_ShouldReturnTrue()
    {
        $spec = new AndSpecification(
            new SatisfiedSpecification(),
            new SatisfiedSpecification()
        );

        $isSatisfied = $spec->isSatisfiedBy('foo');

        $this->assertThat($isSatisfied, $this->equalTo(true));
    }

    /**
     * @test
     */
    public function isSatisfiedBy_GivenOneSpecificationNotSatisfied_ShouldReturnFalse()
    {
        $spec = new AndSpecification(
            new SatisfiedSpecification(),
            new NotSatisfiedSpecification()
        );

        $isSatisfied = $spec->isSatisfiedBy('foo');

        $this->assertThat($isSatisfied, $this->equalTo(false));
    }

    /**
     * @test
     */
    public function isSatisfiedBy_GivenBothSpecificationsNotSatisfied_ShouldReturnFalse()
    {
        $spec = new AndSpecification(
            new NotSatisfiedSpecification(),
            new NotSatisfiedSpecification()
        );

        $isSatisfied = $spec->isSatisfiedBy('foo');

        $this->assertThat($isSatisfied, $this->equalTo(false));
    }
}

No conditional logic or loops

Test should have no uncertainty:

  • All inputs should be known
  • Method behavior should be predictable
  • Expected output should be strictly defined
  • Split in to two tests rather than using "if" or "switch"

Test should not contain "while", "for" or "foreach" loops

  • If test logic has to be repeated, it probably means the test is too complicated
  • Call method multiple times rather than looping inside of method

No exception handling

Indicate expected exception
Catch only the expected type of exception
Fail test if expected exception is not caught
Let other exceptions go uncaught

<?php
use PHPUnit\Framework\TestCase;

class MyTest extends TestCase
{
    /**
     * @expectedException MyException
     * @expectedExceptionCode 20
     */
    public function testExceptionHasErrorCode20()
    {
        throw new MyException('Foo', 20);
    }

    /**
     * @expectedException MyException
     * @expectedExceptionMessage Foo
     */
    public function testExceptionHasErrorMessageFoo()
    {
        throw new MyException('foo', 20);
    }

    public function testExceptionHasErrorMessageBar()
    {
        $this->expectException(MyException::class);
        throw new MyException('foo', 20);
    }
}

Informative Assertion messages

By reading the assertion message, one should know why the test failed and what to do
Include business logic information in the assertion message
Good assertion messages:

  • Improve code documentation
  • Inform developers about the problem if the test fails

Pattern Arrange/Act/Assert (aka AAA)

  • Arrange initialize objects and prepare data needed for the test
  • Act call the tested method with prepared data
  • Assert uses assertion to determine if test passes or not

PHPUnit

Assertions

Assertions are the "checks" that you may perform to determine if a test passes or fails.

  • assertEquals/assertSame
  • assertTrue/assertFalse
  • assertInstanceOf
  • assertCount
  • assertArrayHasKey
  • assertInternalType
  • assertEmpty
  • assertRegExp

Assertions

As possible, you use only one assertion per test

Assertions

Use the more relevant assertion in each case.

 

assertEquals(true, $val)

assertTrue($val)

Assertions

Use the more relevant assertion in each case.

 

assertEquals(null, $val)

assertNull($val)

Assertions

Use the more relevant assertion in each case.

 

assertEquals(true, array_key_exists($items, $key))

assertArrayHasKey($key, $items)

Mock/Stub

A mock allows a dependency to be imitated so the Unit test can be isolated

  • Stub concern system under test (SUT). The Stub can never fail the test. We check the status of our SUT at the end of the test and not how this state was obtained. (state-based test)
  • Mock concern test itself. We have expectations that are verified during the execution of the test. The mock decide that the test fails or succeeded. (Interactions Testing)

Mock/Stub

Code Coverage

you can't go into production with less than 87% coverage

Code Coverage

Metric uses to determine code covered by unit tests
Useful to find untested code
Do not guarantee test quality and use cases coverage

 

We can have 100% code coverage without any assertions

Code Coverage

Code Coverage

PHPSpec

Behavioral Driven Development Oriented
Describing the software behavior


TDD focus on testing your application, BDD is more about describing its behavior

PHPSpec

Using a BDD approach will force you to constantly consider the actual requirements and desired behavior of the software you're building.

No code coverage

BDD

  • Behat that describe the external behavior of your application, using the readable Gherkin language
  • PHPSpec that describe the internal behavior of your application
Made with Slides.com