Все тесты — это юнит тесты :o

abb78667fc972a315f7b070aa697540a.png

Немного веселья на серьезную тему правильного нейминга тестов и 100500 их типов и видов.

Определение юнит теста

Пару определений с англицкой вики:

Unit testing, a.k.a. component / module testing, is a form of software testing by which isolated source code is tested to validate expected behavior.

Integration testing, is a form of software testing in which multiple parts of a software system are tested as a group.

Если упростить, то:

  • юниты — изолированный тестинг исходного кода;

  • интеграционные — тестинг нескольких частей как единой группы;

Юнит тест для функции

Допустим у нас есть функция myFunction, которая принимает на вход два числа, а потом возвращает их сумму.
Её юнит тест может выглядеть таким образом:

public function testCool(int $a, int $b, int $c): void
{
    $this->assertEquals($c, myFunction($a, $b));
}

Юнит тест для класса

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

public function testCool(int $a, int $b, int $c): void
{
    $object = new MyClass($a, $b);
    $this->assertEquals($c, $object->calc());
}

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

Фрай - начал что-то подозревать

Фрай — начал что-то подозревать

Но благо соблюдается условие юнита isolated source code и не соблюдается условие интеграционного теста multiple parts of a software system (свойства класса, это всё же не части системы).
Фух, пока держимся.

Юнит тест для контроллера

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

public function testCool(int $a, int $b, int $c): void
{
    $request = new Request([
        'a' => $a,
        'b' => $b,
    ]);
    $response = (new MyController())->actionCalc($request);
    $this->assertEquals($c, $response->value);
}

Ну теперь, то уже точно не юнит. Ведь да?
Это теперь тест шрёдингера, т.к. юнит он или интеграционный зависит напрямую от содержимого контроллера:

  1. если внутри не используется БД (т.е. мы все решаем на уровне исходного кода) — это юнит;

  2. если внутри используется БД — это интеграционный;

Для обоих случаев — код теста никак не меняется. Вот это класс!

Сильвестр - оценил

Сильвестр — оценил

НО даже если мы используем БД внутри контроллера, мы можем её спокойно замокать, и наш интеграционный тест становиться юнитом ;-)

Юнит тест для одной страницы

Допустим у нас есть страница на сайте. Страница это же часть системы (сайта). Т.е. мы и для него можем написать юнит?
Ну давайте попробуем:

public function testCool(Tester $I, int $a, int $b, int $c): void
{
    $I->amOnPage('/tested-page');
    $I->submitForm('#my-form', [
        'a' => $a,
        'b' => $b,
    ]);
    $I->see('#result', $c);
}

Ну всё, фиаско. Какой же это юнит, это приемочный/системный тест, вон я захожу на страницу, заполняю форму и потом проверяю результат.
Ведь, да?
Неа :)

Это опять тест Шрёдингера:

  1. если класс Tester выполняет запрос к какому-то серверу, то это действительно приемочный;

  2. если класс Tester не выполняет никаких запросов, а все делает на уровне исходного кода, то он интеграционный;

На примере codeception все эта история решается на уровне конфига:

  1. если мы конфигурируем WebDriver, то вышеуказанный тест будет приемочным: будет отправляться запрос куда-то на физический сервер;

  2. если мы конфигурируем PhpBrowser, то вышеуказанный тест будет интеграционным (в нейминге CE «функциональный»): никакой запрос не будет отправляться, а просто окружение сэмитирует его и останется в рамках исходного кода;

И опять же, для обоих случаев — код теста никак не меняется.

Ольга - которая ничего не понимает

Ольга — которая ничего не понимает

Да, можно заметить что по итогу у нас не юнит, а интеграционный.
Но мы же помним, что если замокать все лишнее взаимодействие с другими и тестировать только изолированный код.
Сделать это можно через например DI/ServiceLocator:

public function testCool(Tester $I, int $a, int $b, int $c): void
{
    // вариант DI зависит от фреймворка ;)
    ServiceLocator::get()->set('my-service', $this->getMyServiceMock());
    
    $I->amOnPage('/tested-page');
    $I->submitForm('#my-form', [
        'a' => $a,
        'b' => $b,
    ]);
    $I->see('#result', $c);
}

Теперь юнит: тестируем изолированный кусок

Резюме

Было огромное желание написать примеры тестов для целого сайта (на компонентной диаграмме C4 его вполне можно назвать «частью» проекта), а также тест всего интернета.

Но опускаться в такой абсурд я уже не стал, т.к. сложнее было подвязывать условия юнитов :)

В чём собственно посыл этого чтива:

  1. во-первых, не грусти и улыбнись ;)

  2. во-вторых, забей уже как называются тесты: юниты, интеграционные, приемочные и т.д. Просто пиши ТЕСТЫ!!!

© Habrahabr.ru