С помощью модульных тестов вы можете проверить, что ваш код ведет себя так, как вы ожидаете. При написании модульных тестов вам не нужно беспокоиться о том, работает ли какая-либо другая область приложения правильно.
Преимущества модульного тестирования:
- Разъединяет ваш код
- Напишите больше модульных классов
- Функции меньше и более сфокусированы
- Ваши функции более оборонительны
- Качество кода становится выше
- Вам будет проще повторно использовать код.
При написании модульных тестов вам просто нужно протестировать этот единственный метод вашего приложения, если ваш метод основан на другом классе / переменной, должен быть способ, которым вы можете внедрить это в метод. Вот где внедрение кода в ваш код пригодится, это позволит вам внедрять объекты в ваши классы для изменения вывода класса.
Есть несколько вещей, которые вам нужно сделать, чтобы сделать метод тестируемым, методам потребуется ввод данных из параметра или переменной класса, а также потребуется возврат или установка переменной класса в методе. Если метод не имеет этих вещей, то метод не может быть модульно тестируемым. Если нет возврата метода, то нет способа узнать, как работает метод.
Внедрение зависимости
Внедрение зависимостей — это когда ваш объект зависит от другого объекта. Самая простая форма, чтобы понять, что такое внедрение зависимостей, это думать о методе сеттера. Метод сеттера примет один параметр и установит переменную класса из этого параметра. Это использует внедрение кода для передачи параметра, который будет использоваться в качестве значения переменной класса.
public function setValue( $val ) { $this->val = $val; }
Без внедрения зависимостей этот метод будет выглядеть следующим образом.
public function setValue() { $this->val = 10; }
Для модульного тестирования вы должны знать о любых классах, от которых зависит ваш класс. Например, если у вас есть класс входа, который будет подключаться к базе данных.
class login { private $db = false; public function __construct() { $this->db = new Database(); } public function loginUser( $user, $password ) { $this->db->checkLogin( $user, $password ); } }
Этот класс входа в систему имеет зависимость класса Database в конструкторе, что означает, что мы не можем правильно выполнить модульное тестирование. Если мы хотим выполнить это модульное тестирование, тогда класс базы данных должен быть разработан и протестирован. Если класс базы данных не работает, и мы пытаемся выполнить модульное тестирование метода loginUser (), тест всегда будет неудачным, и мы не будем знать, что это сломанный класс базы данных или метод loginUser ().
Если класс базы данных закончил разработку, протестирован и данные в базе данных, то мы можем использовать это для функции loginUser (). Но теперь наши тесты зависят от правильности данных в базе данных. Если мы передадим имя пользователя и пароль, они должны быть в базе данных, чтобы наш тест прошел. Наш код может быть правильным, но если данных там нет, то наши модульные тесты не пройдут. Это неправильное использование модульных тестов и больше подходит для интеграционных тестов.
Чтобы решить эту проблему, мы можем использовать внедрение зависимостей для передачи коннектора базы данных, который установит переменную класса базы данных. Есть 2 способа, которыми мы можем внедрить переменную в класс, это может быть либо в конструкторе класса, либо с помощью метода setter. Я склонен использовать конструктор для всех необходимых зависимостей и использовать метод setter, если для переменной класса есть значение по умолчанию.
class login { private $db = false; public function __construct( $db ) { $this->db = $db; } public function loginUser( $user, $password ) { $this->db->checkLogin( $user, $password ); } }
Теперь этот класс не зависит от определенного класса базы данных, который мы можем передать в классе базы данных, используя параметр в конструкторе класса входа в систему.
Мы можем модульно протестировать этот метод loginUser (), сначала установив переменную класса $ this-> db . Мы не хотим полагаться на реальную базу данных, поскольку данные могут изменяться, поэтому мы можем либо создать класс базы данных тестового комплекта, либо вы можете смоделировать класс базы данных.
Тестовый класс позволяет вам создать класс базы данных и жестко закодировать любые данные, которые вам нужны. В приведенном выше примере мы можем создать метод checkLogin (), в нашем тестовом жгуте мы затем можем жестко закодировать успешное имя пользователя и пароль для входа в систему, чтобы метод loginUser () прошел. Или вы можете использовать фреймворк PHP для макетирования класса / метода / возвращаемого значения.
Оба метода имеют свои преимущества, но, как правило, макетирование выполняется быстрее, но бывают случаи, когда вы хотите жестко закодировать определенные переменные в классе.
Пересмешивание объектов в TDD с помощью PHP
Пересмешивание объектов в разработке, управляемой тестами, позволяет создавать объекты, выступающие в качестве определенного класса. Если ваш тест зависит от другого метода, возвращающего значение, вы можете смоделировать этот метод и заставить его возвращать любое значение, которое вы хотите.
В примере, который мы использовали выше, вы можете смоделировать класс базы данных и выбрать, какое значение мы ожидаем от метода checkLogin (). При макете метода вы можете выбрать, что вы хотите вернуть из этого метода, поэтому мы можем написать тесты, чтобы увидеть, что произойдет, когда checkLogin () вернет TRUE, а затем мы можем написать еще один тест, чтобы увидеть, что происходит, когда checkLogin () возвращает FALSE.
Пересмешивание объектов означает, что вы можете запускать свои модульные тесты, не завися от другого класса, возвращающего ожидаемые значения, так что вы можете протестировать только свой код в этом одном методе.
Вот некоторые из самых популярных фреймворков на PHP:
- Насмешка с помощью PHPUnit — http://www.phpunit.de/manual/3.0/en/mock-objects.html
- Насмешка с Фэйком — http://phake.digitalsandwich.com/docs/html/
- Насмешка с издевательством — https://github.com/padraic/mockery
- Дразнить с Enchane PHP — https://github.com/Enhance-PHP/Enhance-PHP
- Дразнить с FBMock — https://github.com/facebook/FBMock
Внедрение зависимостей с интерфейсами
Если мы собираемся передать соединитель базы данных в конструктор класса входа в систему, то у этого соединителя базы данных всегда должен быть метод checkLogin (). Вот почему мы должны кодировать наши зависимости, используя интерфейсы, чтобы убедиться, что мы всегда передаем правильный тип класса .
class login { private $db = false; public function __construct( IDatabase $db ) { $this->db = $db; } } class database implements IDatabase { public function checkLogin( $username, $password ) { // check the login credentials } } interface IDatabase { public function checkLogin( $username, $password ); }
Это гарантирует, что класс, который мы передаем в конструктор, является типом IDatabase, поэтому, если наш класс базы данных не реализует IDatabase, то код завершится ошибкой, и поэтому наши модульные тесты не пройдут. Это означает, что что бы мы ни передавали в конструктор, мы знаем, что этот класс сможет запускать методы, необходимые для запуска модульных тестов.