Как написать качественный код? Как протестировать максимальное количество вариантов и не сойти с ума?
На эти вопросы есть ответы: PHPUnit и TDD. Я пойду от простого сложного, стараясь не перегружать ваш мозг.
А теперь познакомимся с ними поближе при решении реальной проблемы.
Описание проблемы
На сайте используется стандартный механизм авторизации плюс авторизация внутренних пользователей через Active Directory (AD).
Это сделано через собственные обработчики событий "OnBeforeUserLogin" и "OnUserLoginExternal".
Проблема заключается в особой специфике:
Что я сделал:
Тут все просто: что приходит, и что мы ожидаем увидеть в результате выполнения тестируемой функции. Например, "iek\user" должен стать "user".
Для описания я использовал , принимающий список в виде "входное значение", "выходной значение, которое ожидается".
Посмотрите, тут ничего сложного:
Проверяем авторизацию по AD? Доступ должен быть только из локальной сети. Ну и пароль должен быть правильным. Вот провайдер данных для проверки другого метода, в качестве ключа я использую небольшое описание:
В качестве ожидаемого значения идет последний элемент массива. Тут используется "1" - ID пользователя на сайте, "0" - такого пользователя нет.
Как же все это использовать? Рассмотрим как взаимодействует провайдер данных и тестируемый метод.
Использование TDD
Теперь проверим как все это работает на уровне знакомого многим метода , для чего служит "testLogin()":
Как это запустить? Переходим в консоль и вводим:
Запуск тестов успешен:
В случае возникновения ошибки выводится расширенно сообщение:
Таким способом можно вести разработку: дорабатываешь код, запускаешь тест, смотришь результаты.
Тестирование кода
Так зачем я заморочился с PHPUnit? А это позволило:
Если будет интерес, продолжу делиться своими ноу-хау.
На эти вопросы есть ответы: PHPUnit и TDD. Я пойду от простого сложного, стараясь не перегружать ваш мозг.
А теперь познакомимся с ними поближе при решении реальной проблемы.
Описание проблемы
На сайте используется стандартный механизм авторизации плюс авторизация внутренних пользователей через Active Directory (AD).
Это сделано через собственные обработчики событий "OnBeforeUserLogin" и "OnUserLoginExternal".
Проблема заключается в особой специфике:
- нужно, чтобы можно было войти как "user", "iek\user", "" (логин), "" (почта как логин)
- люди путают пароль от сайта 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
Теперь проверим как все это работает на уровне знакомого многим метода , для чего служит "testLogin()":
<?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? А это позволило:
- использовать разработку через тестирование
- не запутаться, и не "исправил два бага, в результате появился еще один"
- получить уверенность, что ничего не сломается
Если будет интерес, продолжу делиться своими ноу-хау.