Как написать качественный код? Как протестировать максимальное количество вариантов и не сойти с ума?
На эти вопросы есть ответы: PHPUnit и TDD. Я пойду от простого сложного, стараясь не перегружать ваш мозг.
А теперь познакомимся с ними поближе при решении реальной проблемы.
Описание проблемы
На сайте используется стандартный механизм авторизации плюс авторизация внутренних пользователей через Active Directory (AD).
Это сделано через собственные обработчики событий "OnBeforeUserLogin" и "OnUserLoginExternal".
Проблема заключается в особой специфике:
Что я сделал:
Тут все просто: что приходит, и что мы ожидаем увидеть в результате выполнения тестируемой функции. Например, "iek\user" должен стать "user".
Для описания я использовалпровайдер данных , принимающий список в виде "входное значение", "выходной значение, которое ожидается".
Посмотрите, тут ничего сложного:
Проверяем авторизацию по AD? Доступ должен быть только из локальной сети. Ну и пароль должен быть правильным. Вот провайдер данных для проверки другого метода, в качестве ключа я использую небольшое описание:
В качестве ожидаемого значения идет последний элемент массива. Тут используется "1" - ID пользователя на сайте, "0" - такого пользователя нет.
Как же все это использовать? Рассмотрим как взаимодействует провайдер данных и тестируемый метод.
Использование TDD
Теперь проверим как все это работает на уровне знакомого многим методаСUser::Login() , для чего служит "testLogin()":
Как это запустить? Переходим в консоль и вводим:
Запуск тестов успешен:
В случае возникновения ошибки выводится расширенно сообщение:
Таким способом можно вести разработку: дорабатываешь код, запускаешь тест, смотришь результаты.
Тестирование кода
Так зачем я заморочился с PHPUnit? А это позволило:
Ссылка на исходный код
Если будет интерес, продолжу делиться своими ноу-хау.
На эти вопросы есть ответы: PHPUnit и TDD. Я пойду от простого сложного, стараясь не перегружать ваш мозг.
А теперь познакомимся с ними поближе при решении реальной проблемы.
Описание проблемы
На сайте используется стандартный механизм авторизации плюс авторизация внутренних пользователей через Active Directory (AD).
Это сделано через собственные обработчики событий "OnBeforeUserLogin" и "OnUserLoginExternal".
Проблема заключается в особой специфике:
- нужно, чтобы можно было войти как "user", "iek\user", "
user@iek.ru " (логин), "user@iek.ru " (почта как логин) - люди путают пароль от сайта iek.ru и от AD
- куча других не интересных проблем
Что я сделал:
- описал кейсы
- использовал TDD
- с помощью PHPUnit протестировал все кейсы
Тут все просто: что приходит, и что мы ожидаем увидеть в результате выполнения тестируемой функции. Например, "iek\user" должен стать "user".
Для описания я использовал
Посмотрите, тут ничего сложного:
public function canonizeProvider() // данные для проверки канонизации логина { return [ ['iek\\user', 'user'], ['user', 'user'], ['user@iek.ru', 'user'], ['user@iek.com.ua', 'user'], ['user@mail.ru', false] // это не из AD, поэтому false ]; } |
public function userLdapLoginProvider() // данные для проверки авторизации по AD { return [ 'LDAP success LAN 192.168' => ['192.168.58.122', ['LOGIN' => 'user', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], 1], 'LDAP success iek\\* LAN 192.168' => ['192.168.58.122', ['LOGIN' => 'iek\\user', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], 1], 'LDAP success LAN 10.0' => ['10.0.1.18', ['LOGIN' => 'user', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], 1], 'LDAP success iek\\* LAN 10.0' => ['10.0.1.18', ['LOGIN' => 'iek\\user', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], 1], 'LDAP wrong password LAN 192.168' => ['192.168.58.122', ['LOGIN' => 'user', 'PASSWORD' => 'wrongPassword', 'PASSWORD_ORIGINAL' => 'Y'], 0], 'LDAP denied WAN' => ['8.8.8.8', ['LOGIN' => 'user', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], 0], 'LDAP denied iek\\* WAN' => ['8.8.8.8', ['LOGIN' => 'iek\\user', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], 0], ]; } |
Как же все это использовать? Рассмотрим как взаимодействует провайдер данных и тестируемый метод.
Использование TDD
Теперь проверим как все это работает на уровне знакомого многим метода
<?php use PHPUnit\Framework\TestCase; final class AuthorizationTest extends TestCase { // Черный список глобальных переменных, которые восстанавливаются после каждого теста // @see https://phpunit.readthedocs.io/ru/latest/fixtures.html protected $backupGlobalsBlacklist = ['DB']; /** * @dataProvider userAllLoginProvider */ public function testLogin($ip, $arFields, $expected) { // Устанавливаем IP $_SERVER['REMOTE_ADDR'] = $ip; // Проверяем $res = $GLOBALS['USER']->Login($arFields['LOGIN'], $arFields['PASSWORD'], 'N', $arFields['PASSWORD_ORIGINAL']); // Сравниваем результат выполнения метода с тем, что ожидаем увидеть $this->assertSame($res, $expected); } // Эти данные PHPUnit автоматически подставит в testLogin() public function userAllLoginProvider() { // Сообщение о неверном логине-пароле $incorrectLoginPass = [ 'MESSAGE' => 'Incorrect login or password<br>', 'TYPE' => 'ERROR', 'ERROR_TYPE' => 'LOGIN', ]; // Сообщение о заблокированном логине $loginIsBlocked = [ 'MESSAGE' => 'Your login is blocked<br>', 'TYPE' => 'ERROR' ]; // Часть кейсов. Чтобы не утомлять, я их сократил. Всего их было 43 return [ 'Bitrix by mail success WAN' => ['8.8.8.8', ['LOGIN' => 'user@iek.ru', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], true], 'Bitrix by mail wrong password WAN' => ['8.8.8.8', ['LOGIN' => 'user@iek.ru', 'PASSWORD' => 'wrongPassword', 'PASSWORD_ORIGINAL' => 'Y'], $incorrectLoginPass], 'Bitrix by login success LAN' => ['10.0.1.18', ['LOGIN' => 'admin', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], true], 'Bitrix by login wrong password LAN' => ['10.0.1.18', ['LOGIN' => 'admin', 'PASSWORD' => 'wrongPassword', 'PASSWORD_ORIGINAL' => 'Y'], $incorrectLoginPass], 'Bitrix partner by mail disabled WAN' => ['8.8.8.8', ['LOGIN' => 'blocked-login@mail.ru', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], $loginIsBlocked], 'Bitrix partner by mail disabled LAN' => ['10.0.1.18', ['LOGIN' => 'blocked-login@mail.ru', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], $loginIsBlocked], 'LDAP success @iek LAN 192.168' => ['192.168.58.122', ['LOGIN' => 'user@iek.ru', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], true], 'LDAP success LAN 192.168' => ['192.168.58.122', ['LOGIN' => 'user', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], true], 'LDAP wrong password LAN 192.168' => ['192.168.58.122', ['LOGIN' => 'user', 'PASSWORD' => 'wrongPassword', 'PASSWORD_ORIGINAL' => 'Y'], $incorrectLoginPass], 'LDAP denied @iek WAN' => ['8.8.8.8', ['LOGIN' => 'user@iek.ru', 'PASSWORD' => 'password', 'PASSWORD_ORIGINAL' => 'Y'], $incorrectLoginPass], ]; } } |
phpunit --bootstrap local/phpunit/bootstrap.php local/phpunit/tests/AuthorizationTest.php |
PHPUnit 5.7.27 by Sebastian Bergmann and contributors. .......................................................... 58 / 58 (100%) Time: 5.72 seconds, Memory: 59.75MB OK (58 tests, 58 assertions) |
1) AuthorizationTest::testLogin with data set "Bitrix by mail success WAN" ('8.8.8.8', array('user@iek.ru', 'password', 'Y'), false) Failed asserting that false matches expected true. |
Тестирование кода
Так зачем я заморочился с PHPUnit? А это позволило:
- использовать разработку через тестирование
- не запутаться, и не "исправил два бага, в результате появился еще один"
- получить уверенность, что ничего не сломается
Если будет интерес, продолжу делиться своими ноу-хау.