Все тесты — это юнит тесты :o
Немного веселья на серьезную тему правильного нейминга тестов и 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);
}
Ну теперь, то уже точно не юнит. Ведь да?
Это теперь тест шрёдингера, т.к. юнит он или интеграционный зависит напрямую от содержимого контроллера:
если внутри не используется БД (т.е. мы все решаем на уровне исходного кода) — это юнит;
если внутри используется БД — это интеграционный;
Для обоих случаев — код теста никак не меняется. Вот это класс!
Сильвестр — оценил
НО даже если мы используем БД внутри контроллера, мы можем её спокойно замокать, и наш интеграционный тест становиться юнитом ;-)
Юнит тест для одной страницы
Допустим у нас есть страница на сайте. Страница это же часть системы (сайта). Т.е. мы и для него можем написать юнит?
Ну давайте попробуем:
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);
}
Ну всё, фиаско. Какой же это юнит, это приемочный/системный тест, вон я захожу на страницу, заполняю форму и потом проверяю результат.
Ведь, да?
Неа :)
Это опять тест Шрёдингера:
если класс
Tester
выполняет запрос к какому-то серверу, то это действительно приемочный;если класс
Tester
не выполняет никаких запросов, а все делает на уровне исходного кода, то он интеграционный;
На примере codeception
все эта история решается на уровне конфига:
если мы конфигурируем
WebDriver
, то вышеуказанный тест будет приемочным: будет отправляться запрос куда-то на физический сервер;если мы конфигурируем
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 его вполне можно назвать «частью» проекта), а также тест всего интернета.
Но опускаться в такой абсурд я уже не стал, т.к. сложнее было подвязывать условия юнитов :)
В чём собственно посыл этого чтива:
во-первых, не грусти и улыбнись ;)
во-вторых, забей уже как называются тесты: юниты, интеграционные, приемочные и т.д. Просто пиши ТЕСТЫ!!!