Поиск…


Синтаксис

замечания

Unit тесты используются для тестирования исходного кода, чтобы увидеть, содержит ли он транзакции со входами, как мы ожидаем. Unit тесты поддерживаются большинством инфраструктур. Существует несколько разных тестов PHPUnit, и они могут отличаться в синтаксисе. В этом примере мы используем PHPUnit .

Тестирование правил класса

Скажем, у нас есть простой класс LoginForm с методом rules () (используемый на странице входа в качестве шаблона рамки):

class LoginForm {
    public $email;
    public $rememberMe;
    public $password;

    /* rules() method returns an array with what each field has as a requirement.
     * Login form uses email and password to authenticate user.
     */
    public function rules() {
        return [
            // Email and Password are both required
            [['email', 'password'], 'required'],

            // Email must be in email format
            ['email', 'email'],

            // rememberMe must be a boolean value
            ['rememberMe', 'boolean'],

            // Password must match this pattern (must contain only letters and numbers)
            ['password', 'match', 'pattern' => '/^[a-z0-9]+$/i'],
        ];
    }

    /** the validate function checks for correctness of the passed rules */
    public function validate($rule) {
        $success = true;
        list($var, $type) = $rule;
        foreach ((array) $var as $var) {
            switch ($type) {
                case "required":
                    $success = $success && $this->$var != "";
                    break;
                case "email":
                    $success = $success && filter_var($this->$var, FILTER_VALIDATE_EMAIL);
                    break;
                case "boolean":
                    $success = $success && filter_var($this->$var, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE) !== null;
                    break;
                case "match":
                    $success = $success && preg_match($rule["pattern"], $this->$var);
                    break;
                default:
                    throw new \InvalidArgumentException("Invalid filter type passed")
            }
        }
        return $success;
    }
}

Чтобы выполнить тесты этого класса, мы используем модульные тесты (проверяя исходный код, чтобы убедиться, что он соответствует нашим ожиданиям):

class LoginFormTest extends TestCase {
    protected $loginForm;

    // Executing code on the start of the test
    public function setUp() {
        $this->loginForm = new LoginForm;
    }

    // To validate our rules, we should use the validate() method

    /**
     * This method belongs to Unit test class LoginFormTest and
     * it's testing rules that are described above.
     */
    public function testRuleValidation() {
        $rules = $this->loginForm->rules();

        // Initialize to valid and test this
        $this->loginForm->email = "[email protected]";
        $this->loginForm->password = "password";
        $this->loginForm->rememberMe = true;
        $this->assertTrue($this->loginForm->validate($rules), "Should be valid as nothing is invalid");

        // Test email validation
        // Since we made email to be in email format, it cannot be empty
        $this->loginForm->email = '';
        $this->assertFalse($this->loginForm->validate($rules), "Email should not be valid (empty)");

        // It does not contain "@" in string so it's invalid
        $this->loginForm->email = 'invalid.email.com';
        $this->assertFalse($this->loginForm->validate($rules), "Email should not be valid (invalid format)");

        // Revert email to valid for next test
        $this->loginForm->email = '[email protected]';

        // Test password validation
        // Password cannot be empty (since it's required)
        $this->loginForm->password = '';
        $this->assertFalse($this->loginForm->validate($rules), "Password should not be valid (empty)");

        // Revert password to valid for next test
        $this->loginForm->password = 'ThisIsMyPassword';

        // Test rememberMe validation
        $this->loginForm->rememberMe = 999;
        $this->assertFalse($this->loginForm->validate($rules), "RememberMe should not be valid (integer type)");

        // Revert remeberMe to valid for next test
        $this->loginForm->rememberMe = true;
    }
}

Как именно Unit тесты могут помочь (без общих примеров) здесь? Например, он очень хорошо подходит, когда мы получаем неожиданные результаты. Например, допустим это правило раньше:

['password', 'match', 'pattern' => '/^[a-z0-9]+$/i'],

Вместо этого, если мы пропустили одну важную вещь и написали это:

['password', 'match', 'pattern' => '/^[a-z0-9]$/i'],

С десятками разных правил (при условии, что мы используем не только электронную почту и пароль), трудно обнаружить ошибки. Этот модульный тест:

// Initialize to valid and test this
$this->loginForm->email = "[email protected]";
$this->loginForm->password = "password";
$this->loginForm->rememberMe = true;
$this->assertTrue($this->loginForm->validate($rules), "Should be valid as nothing is invalid");

Пройдет наш первый пример, но не второй . Зачем? Потому что в 2-м примере мы написали шаблон с опечаткой (пропущенный + знак), что означает, что он принимает только одну букву / число.

Модульные тесты можно запускать в консоли с помощью команды: phpunit [path_to_file] . Если все в порядке, мы должны быть в состоянии видеть , что все тесты в OK состоянии, иначе мы увидим либо Error (ошибки синтаксиса) или Fail (по крайней мере одна строка в этом методе не прошел).

С дополнительными параметрами, такими как --coverage мы также можем визуально видеть, сколько строк в коде backend было проверено и которое прошло / не удалось. Это относится к любой инфраструктуре, в которой установлен PHPUnit .

Пример того, как выглядит тест PHPUnit в консоли (общий вид, а не в соответствии с этим примером):

введите описание изображения здесь

Поставщики данных PHPUnit

Методам тестирования часто требуются данные для тестирования. Чтобы полностью протестировать некоторые методы, вам необходимо предоставить различные наборы данных для каждого возможного условия тестирования. Конечно, вы можете сделать это вручную, используя петли, например:

...
public function testSomething()
{
    $data = [...];
    foreach($data as $dataSet) {
       $this->assertSomething($dataSet);
    }
}
... 

И кто-то может найти это удобным. Но есть некоторые недостатки такого подхода. Во-первых, вам придется выполнять дополнительные действия для извлечения данных, если ваша тестовая функция принимает несколько параметров. Во-вторых, при отказе было бы трудно отличить неудачный набор данных без дополнительных сообщений и отладки. В-третьих, PHPUnit обеспечивает автоматический способ работы с наборами тестовых данных с использованием поставщиков данных .

Поставщик данных - это функция, которая должна возвращать данные для конкретного тестового примера.

Метод поставщика данных должен быть общедоступным и либо возвращать массив массивов, либо объект, реализующий интерфейс Iterator, и выводит массив для каждого шага итерации. Для каждого массива, который является частью коллекции, в качестве аргументов будет вызываться тестовый метод с содержимым массива.

Чтобы использовать поставщика данных с вашим тестом, используйте аннотацию @dataProvider с @dataProvider указанной функции поставщика данных:

/**
* @dataProvider dataProviderForTest
*/
public function testEquals($a, $b)
{
    $this->assertEquals($a, $b);
}

public function dataProviderForTest()
{
    return [
        [1,1],
        [2,2],
        [3,2] //this will fail
    ];
}

Массив массивов

Обратите внимание, что dataProviderForTest() возвращает массив массивов. Каждый вложенный массив имеет два элемента, и они будут заполнять необходимые параметры для testEquals() один за другим. Ошибка, подобная этой, будет сброшена. Missing argument 2 for Test::testEquals() если элементов недостаточно. PHPUnit автоматически проверит данные и запустит тесты:

public function dataProviderForTest()
{
    return [
        [1,1], // [0] testEquals($a = 1, $b = 1)
        [2,2], // [1] testEquals($a = 2, $b = 2)
        [3,2]  // [2] There was 1 failure: 1) Test::testEquals with data set #2 (3, 4)
    ];
}

Каждый набор данных можно назвать для удобства. Будет легче обнаружить данные, не соответствующие данным:

public function dataProviderForTest()
{
    return [
        'Test 1' => [1,1], // [0] testEquals($a = 1, $b = 1)
        'Test 2' => [2,2], // [1] testEquals($a = 2, $b = 2)
        'Test 3' => [3,2]  // [2] There was 1 failure: 
                           //     1) Test::testEquals with data set "Test 3" (3, 4)
    ];
}

итераторы

class MyIterator implements Iterator {
    protected $array = [];

    public function __construct($array) {
        $this->array = $array;
    }

    function rewind() {
        return reset($this->array);
    }

    function current() {
        return current($this->array);
    }

    function key() {
        return key($this->array);
    }

    function next() {
        return next($this->array);
    }

    function valid() {
        return key($this->array) !== null;
    }
}
...

class Test extends TestCase
{
    /**
     * @dataProvider dataProviderForTest
     */
    public function testEquals($a)
    {
        $toCompare = 0;

        $this->assertEquals($a, $toCompare);
    }

    public function dataProviderForTest()
    {
        return new MyIterator([
            'Test 1' => [0],
            'Test 2' => [false],
            'Test 3' => [null]
        ]);
    }
}

Как вы можете видеть, простой итератор также работает.

Обратите внимание, что даже для одного параметра поставщик данных должен возвращать массив [$parameter]

Потому что, если мы изменим наш метод current() (который фактически возвращает данные на каждой итерации):

function current() {
    return current($this->array)[0];
}

Или изменить фактические данные:

return new MyIterator([
            'Test 1' => 0,
            'Test 2' => false,
            'Test 3' => null
        ]);

Мы получим сообщение об ошибке:

There was 1 warning:

1) Warning
The data provider specified for Test::testEquals is invalid.

Разумеется, использовать объект Iterator над простым массивом нецелесообразно. Он должен реализовать определенную логику для вашего дела.

Генераторы

Это явно не указано и показано в руководстве, но вы также можете использовать генератор в качестве поставщика данных. Обратите внимание, что класс Generator фактически реализует интерфейс Iterator .

Итак, вот пример использования DirectoryIterator сочетании с generator :

/**
 * @param string $file
 *
 * @dataProvider fileDataProvider
 */
public function testSomethingWithFiles($fileName)
{
    //$fileName is available here
    
    //do test here
}

public function fileDataProvider()
{
    $directory = new DirectoryIterator('path-to-the-directory');

    foreach ($directory as $file) {
        if ($file->isFile() && $file->isReadable()) {
            yield [$file->getPathname()]; // invoke generator here.
        }
    }
}

Учтите, что поставщик провайдера yield массив. Вместо этого вы получите предупреждение неверного поставщика данных.

Исключения для тестирования

Предположим, вы хотите протестировать метод, который выдает исключение

class Car
{
    /**
     * @throws \Exception
     */
    public function drive()
    {
        throw new \Exception('Useful message', 1);
    }
}

Вы можете сделать это, включив вызов метода в блок try / catch и сделав утверждения о свойствах объекта execption, но более удобно использовать методы утверждения исключений. Начиная с PHPUnit 5.2 вы можете ожидать, что методы X () будут доступны для утверждения типа исключения, сообщения и кода

class DriveTest extends PHPUnit_Framework_TestCase
{
    public function testDrive()
    {
        // prepare
        $car = new \Car();
        $expectedClass = \Exception::class;
        $expectedMessage = 'Useful message';
        $expectedCode = 1;

        // test
        $this->expectException($expectedClass);
        $this->expectMessage($expectedMessage);
        $this->expectCode($expectedCode);

        // invoke
        $car->drive();
    }
}

Если вы используете более раннюю версию PHPUnit, метод setExpectedException может использоваться вместо методов expectX (), но имейте в виду, что он устарел и будет удален в версии 6.

class DriveTest extends PHPUnit_Framework_TestCase
{
    public function testDrive()
    {
        // prepare
        $car = new \Car();
        $expectedClass = \Exception::class;
        $expectedMessage = 'Useful message';
        $expectedCode = 1;

        // test
        $this->setExpectedException($expectedClass, $expectedMessage, $expectedCode);

        // invoke
        $car->drive();
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow