Working With PHP Unit Test

Summary: Learning about Assertions, Annotations, Fixtures, Data Providers, Test Double ( Fakes, Mocks, Stubs) in PHP Unit Test.

Unit Testing

Unit testing is a method where individual units or components of software are tested in isolation from the rest of the application to ensure they work as expected. By testing each part of the code independently, developers can detect and fix bugs early, leading to more reliable software. This method also helps in identifying the root cause of bugs more easily due to the smaller scope of the test.

PHPUnit

PHPUnit is a framework-independent library for unit testing in PHP. It provides tools to write and run tests, ensuring that individual units of code perform as expected. This framework supports the practice of writing tests for each component of an application, which helps in maintaining code quality and reliability.

Importance of Unit Testing

Unit testing helps in detecting bugs at the development stage, preventing them from progressing to later stages and into the released product. By focusing on essential business logic, features, and behaviors, unit tests ensure that any unintentional changes during development are caught early. This saves costs associated with fixing bugs later and benefits end-users by providing a more stable product. Unit testing also improves design, facilitates refactoring, and, when integrated with the build process, ensures the quality of the build. It prevents unnoticed malfunctioning behavior, especially in edge cases.

Standard Unit Testing vs. WordPress Unit Testing

Standard unit testing involves testing code in perfect isolation from other parts of the application. In contrast, WordPress unit tests involve testing with the WordPress environment, using the database and WordPress API functions. This type of testing, often referred to as integration testing, checks the integration of code within the WordPress ecosystem. Although it is not pure isolation, it ensures that the code works correctly within the specific environment it is intended for.

Basic Test Class

Unit tests in PHPUnit are written in classes that inherit from PHPUnit\Framework\TestCase. This base class provides a variety of utilities for testing, such as assertions and annotations. The test class file should have the same name as the class.

Example:

<?php

class ExampleTestClass extends PHPUnit\Framework\TestCase {
    public function exampleTestCase() {
        $this->assertTrue($variable);
    }
}

Assertions

Assertions are checks that compare the actual outcome of a code segment with the expected outcome. They are fundamental in verifying that the code behaves as intended.

Example:

<?php

use PHPUnit\Framework\TestCase;

class Test extends TestCase {
    public function testSumPassing(): void {
        $a = 2;
        $b = 3;
        $result = $a + $b;
        $expected = 5;
        $this->assertEquals($expected, $result);
    }

    public function testSumFailing(): void {
        $a = 2;
        $b = 3;
        $result = $a + $b + 1;
        $expected = 5;
        $this->assertEquals($expected, $result);
    }
}

Common assertions include assertArrayHasKey(), assertContains(), assertEmpty(), assertEquals(), assertIsArray(), assertNotNull(), assertSame(), and assertTrue().

Annotations

Annotations in PHPUnit are metadata that control test execution. They are added using special comments.

Example:

<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class MyTest extends TestCase {
    /**
     * @group specification
     */
    public function testSomething(): void {
    }

    /**
     * @group regression
     * @group bug2204
     */
    public function testSomethingElse(): void {
    }
}

Common annotations include @group, @covers, @depends, and @dataProvider.

Fixtures

Fixtures set up the environment for tests to run in a known state and clean it up afterward. The setUp() and tearDown() methods handle this.

Example:

<?php

use PHPUnit\Framework\TestCase;

class StackTest extends TestCase {
    protected $stack;

    protected function setUp(): void {
        $this->stack = [];
    }

    public function testEmpty() {
        $this->assertTrue(empty($this->stack));
    }

    public function testPush() {
        array_push($this->stack, 'foo');
        $this->assertSame('foo', $this->stack[count($this->stack)-1]);
        $this->assertFalse(empty($this->stack));
    }

    public function testPop() {
        array_push($this->stack, 'foo');
        $this->assertSame('foo', array_pop($this->stack));
        $this->assertTrue(empty($this->stack));
    }
}

Data Providers

Data providers supply different sets of data to a test method, allowing it to run multiple times with different inputs.

Example:

<?php

use PHPUnit\Framework\TestCase;

class DataProviderExampleTest extends TestCase {  
    /**
     * @dataProvider exampleDataProvider
     */
    public function testAdd($a, $b, $result) {
        $this->assertSame($result, $a + $b );
    }

    public function exampleDataProvider() {
        return [
            [1, 2, 3], // Test 1
            [3, 6, 9], // Test 2
            [4, 3, 7]  // Test 3
        ];
    }
}

Test Doubles

Test doubles are objects that stand in for real objects in a test. They help isolate the unit of code being tested by providing controlled behavior.

Types of Test Doubles:

  • Fakes: Implement the same interface as real objects but with shortcuts.
  • Mocks: Pre-programmed with expectations and behaviors.
  • Stubs: Return predefined values without behavior.

Mock Example:

<?php

use PHPUnit\Framework\TestCase;

class TestDoubleExample extends TestCase {
    public function testStub() {
        $stub = $this->createMock(SomeClass::class);
        $stub->method('someMethod')->willReturn('foo');
        $this->assertSame('foo', $stub->someMethod());
    }
}

Code Coverage Analysis

Code coverage measures the degree to which the source code is tested. Higher coverage indicates thorough testing, reducing the chance of bugs.

Metrics:

  • Line Coverage: Measures executed lines.
  • Branch Coverage: Measures true/false evaluations.
  • Path Coverage: Measures unique execution paths.
  • Function and Method Coverage: Measures invoked functions/methods.
  • Class and Trait Coverage: Measures method coverage within classes/traits.
  • CRAP Index: Combines cyclomatic complexity and code coverage.

Running Code Coverage:

phpunit --coverage-html <dir>

Setting Expectations for Tests

Expectations ensure that dependencies in the code are used correctly. They define how many times a method is expected to be called.

Example:

<?php

use PHPUnit\Framework\TestCase;

class Bill {
    public function getTotal() {
        return 200;
    }
}

class Tax {
    public function getTax(Bill $bill) {
        $total = $bill->getTotal();
        $tax = $total % 10;
        return $tax;
    }
}

class TestTax extends TestCase {
    public function testGetTax() {
        $testee = new Tax();
        $bill = $this->createMock(Bill::class);
        $bill->expects($this->once())->method('getTotal');
        $testee->getTax($bill);
    }
}

In this example, the expectation is set that getTotal will be called exactly once.

«
»

Leave a Reply

Your email address will not be published. Required fields are marked *