Если тест касается базы данных (или сети, файловой системы или любой другой внешней службы), это не единичный тест. Исходя из любого определения, тестирование через БД происходит медленно и подвержено ошибкам. В динамических языках слишком малое издевательство, но многие люди все еще проверяют бизнес-логику через БД. Я обычно не поклонник публикации полных решений вопроса. В этом случае я чувствую необходимость сделать это, чтобы доказать, что на самом деле это довольно легко, особенно в постановке, описанной в вопросе.
class CheckBruteTest extends PHPUnit_Framework_TestCase {
public function test_checkbrute__some_user__calls_db_and_statement_with_correct_params() {
$expected_user_id = 23;
$statement_mock = $this->getMock('StatementIface', array());
$statement_mock->expects($this->once())->method('bind_param')
->with($this->equalTo('i'), $this->equalTo($expected_user_id));
$statement_mock->expects($this->once())->method('execute');
$statement_mock->expects($this->once())->method('store_result');
$db_mock = $this->getMock('DbIface', array());
$time_ignoring_the_last_two_decimals = floor((time() - 2 * 60 * 60)/100);
$db_mock->expects($this->once())->method('prepare')
->with($this->stringStartsWith("SELECT time FROM login_attempts WHERE user_id = ? AND time > '$time_ignoring_the_last_two_decimals"))
->will($this->returnValue($statement_mock));
checkbrute($expected_user_id, $db_mock);
}
public function test_checkbrute__more_then_five__return_true() {
$statement_mock = $this->getMock('StatementIface', array());
$statement_mock->num_rows = 6;
$db_mock = $this->getMock('DbIface', array());
$db_mock->expects($this->once())->method('prepare')
->will($this->returnValue($statement_mock));
$result = checkbrute(1, $db_mock);
$this->assertTrue($result);
}
public function test_checkbrute__less_or_equal_then_five__return_false() {
$statement_mock = $this->getMock('StatementIface', array());
$statement_mock->num_rows = 5;
$db_mock = $this->getMock('DbIface', array());
$db_mock->expects($this->once())->method('prepare')
->will($this->returnValue($statement_mock));
$result = checkbrute(1, $db_mock);
$this->assertFalse($result);
}
}
interface DbIface {
public function prepare($query);
}
abstract class StatementIface {
public abstract function bind_param($i, $user_id);
public abstract function execute();
public abstract function store_result();
public $num_rows;
}
Поскольку я не знаю спецификации функции, я могу получить ее только из кода.
В первом тестовом случае я проверяю только mocks для DB и инструкцию, если код действительно вызывает эти службы, как ожидалось. Самое главное, что он проверяет, правильно ли вводится правильный идентификатор пользователя, и если используется правильное временное ограничение. Проверка времени довольно халатная, но это то, что вы получаете, если вы вызываете такие услуги, как time()
непосредственно в код вашего бизнеса.
Второй тестовый случай заставляет количество попыток входа быть 6 и утверждает, возвращает ли функция true
.
Третий тестовый случай заставляет количество попыток входа быть 5 и утверждает, возвращает ли функция false
.
Это охватывает (почти) все пути кода в функции. Только один путь кода не учитывается: если $mysqli->prepare()
возвращает значение null или любое другое значение, которое оценивается в false
, весь if-блок обходит и неявно возвращает значение null. Я не знаю, если это специально. Код должен сделать что-то вроде этого явным.
Для издевательских соображений я создал небольшой интерфейс и небольшой абстрактный класс. Они нужны только в контексте тестов. Можно также реализовать пользовательские макетные классы для параметра $mysqli
и возвращаемого значения $mysqli->prepare()
, но я предпочитаю использовать автоматические mocks.
Некоторые дополнительные примечания, которые не имеют ничего общего с решением:
- модульных тестов являются тесты для разработчиков и должны быть написаны сами разработчики, а не какой-то бедный тестером. Тестеры пишут приемные и регрессионные тесты.
- «Взлом» тестовых примеров показывает, почему писать тесты после того, как факт намного сложнее. Если бы разработчики написали код TDD, код И тест были бы намного чище.
- Конструкция функции checkbrute довольно субоптимальна:
- «checkbrute» - это плохое имя. На самом деле это не так.
- Он смешивает бизнес-код с доступом к базе данных. Расчет ограничения времени - это бизнес-код, а также проверка на наличие
>5
. Код между ними - это код DB и принадлежит его собственной функции/классу/независимо.
- Магические номера. Пожалуйста, используйте константы для магических чисел, таких как 2h-значение и максимальное количество попыток входа в систему.
Где я могу запустить этот код? И знаете ли вы какие-либо хорошие учебники или веб-сайт, которые могут помочь мне изучить это. – user2354898
Вы можете запускать этот код везде, где хотите! Проверьте эту ссылку: http://phpunit.de/manual/3.7/ru/writing-tests-for-phpunit.html. Я нашел его, выполнив поиск «php unit test» в Google ... Возможно, это поможет вам. – Alarid
Знаете ли вы, как тестировать функции, не имеющие класса? Мне был предоставлен файл с очень многими функциями, которые я бы хотел выполнить с помощью unit-test, но я не могу заставить его работать без использования классов. Любые идеи, как решить это без реализации классов? – user2354898