Тестирование
CodeIgniter был разработан, чтобы сделать тестирование как фреймворка, так и вашего приложения максимально простым. Поддержка PHPUnit встроена, и фреймворк предоставляет ряд удобных вспомогательных методов, чтобы сделать тестирование всех аспектов вашего приложения максимально безболезненным.
Настройка системы
Установка PHPUnit
CodeIgniter использует PHPUnit в качестве основы для всех тестов. Существует два способа установить PHPUnit для использования в вашей системе.
Composer
Рекомендованный метод — установить его в вашем проекте с помощью Composer. Хотя глобальная установка возможна, мы не рекомендуем её, так как со временем это может привести к проблемам совместимости с другими проектами на вашей системе.
Убедитесь, что Composer установлен на вашей системе. Из корня проекта (каталога, содержащего каталоги application и system) введите следующее из командной строки:
> composer require --dev phpunit/phpunit
Это установит правильную версию для вашей текущей версии PHP. После этого вы можете запустить все тесты для этого проекта, набрав:
> ./vendor/bin/phpunit
Phar
Другой вариант — скачать файл .phar с сайта PHPUnit. Это автономный файл, который следует поместить в корень вашего проекта.
Тестирование вашего приложения
Настройка PHPUnit
В корне проекта находится файл phpunit.xml.dist. Он управляет модульным тестированием самого фреймворка. Если вы предоставите свой собственный phpunit.xml, он переопределит этот файл.
Ваш phpunit.xml должен исключать папку system, а также любые папки vendor или ThirdParty, если вы тестируете своё приложение.
Класс теста
Для использования дополнительных инструментов ваши тесты должны расширять CIUnitTestCase. Все тесты по умолчанию должны находиться в каталоге tests/app.
Для тестирования новой библиотеки Foo вы создадите новый файл в tests/app/Libraries/FooTest.php:
<?php
namespace App\Libraries;
use CodeIgniter\Test\CIUnitTestCase;
class FooTest extends CIUnitTestCase
{
public function testFooNotBar()
{
// ...
}
}
Для тестирования одного из ваших моделей вы можете получить что-то подобное в tests/app/Models/OneOfMyModelsTest.php:
<?php
namespace App\Models;
use CodeIgniter\Test\CIUnitTestCase;
class OneOfMyModelsTest extends CIUnitTestCase
{
public function testFooNotBar()
{
// ...
}
}
Вы можете создать любую структуру каталогов, которая соответствует вашему стилю/потребностям тестирования. При именовании классов тестов помните, что каталог app является корнем пространства имён App, поэтому все используемые вами классы должны иметь правильное пространство имён относительно App.
Примечание
Пространства имён не строго обязательны для классов тестов, но они полезны для предотвращения конфликтов имён классов.
При тестировании результатов из базы данных необходимо использовать DatabaseTestTrait в вашем классе.
Подготовительные действия
Большинство тестов требуют некоторой подготовки для корректной работы. PHPUnit’s TestCase предоставляет четыре метода для подготовки и очистки:
public static function setUpBeforeClass(): void public static function tearDownAfterClass(): void public function setUp(): void public function tearDown(): void
Статические методы выполняются до и после всего тестового случая, а локальные методы выполняются между каждым тестом. Если вы реализуете какие-либо из этих специальных функций, убедитесь, что вы также выполняете их родительские функции, чтобы расширенные тестовые случаи не мешали подготовительным действиям:
public function setUp(): void
{
parent::setUp();
helper('text');
}
В дополнение к этим методам, CIUnitTestCase также поставляется с удобным свойством для методов без параметров, которые вы хотите выполнить во время подготовки и завершения:
protected $setUpMethods = [
'mockEmail',
'mockSession',
];
protected $tearDownMethods = [];
Можно заметить, что по умолчанию они обрабатывают моделирование навязчивых сервисов, но ваш класс может переопределить это или предоставить свои собственные:
class OneOfMyModelsTest extends CIUnitTestCase
{
protected $tearDownMethods = [
'purgeRows',
];
protected function purgeRows()
{
$this->model->purgeDeleted()
}
Трейты
Распространённый способ улучшения тестов — использование трейтов для консолидации подготовительных действий в разных тестовых случаях. CIUnitTestCase обнаружит любые классовые трейты и будет искать методы подготовительных действий, имеющие название, совпадающее с именем самого трейта. Например, если вам нужно добавить аутентификацию в некоторые из ваших тестовых случаев, вы можете создать трейт аутентификации с методом подготовки для имитации вошедшего пользователя:
trait AuthTrait
{
protected setUpAuthTrait()
{
$user = $this->createFakeUser();
$this->logInUser($user);
}
...
class AuthenticationFeatureTest
{
use AuthTrait;
...
Дополнительные утверждения
CIUnitTestCase предоставляет дополнительные модульные утверждения, которые могут быть полезны.
assertLogged($level, $expectedMessage)
Убедитесь, что ожидаемое вами сообщение было записано в журнал:
$config = new LoggerConfig();
$logger = new Logger($config);
... do something that you expect a log entry from
$logger->log('error', "That's no moon");
$this->assertLogged('error', "That's no moon");
assertEventTriggered($eventName)
Убедитесь, что ожидаемое вами событие было запущено:
Events::on('foo', function ($arg) use(&$result) {
$result = $arg;
});
Events::trigger('foo', 'bar');
$this->assertEventTriggered('foo');
assertHeaderEmitted($header, $ignoreCase = false)
Убедитесь, что заголовок или куки были фактически выведены:
$response->setCookie('foo', 'bar');
ob_start();
$this->response->send();
$output = ob_get_clean(); // in case you want to check the actual body
$this->assertHeaderEmitted("Set-Cookie: foo=bar");
Примечание: тестовый случай с этим должен быть запущен как отдельный процесс в PHPunit.
assertHeaderNotEmitted($header, $ignoreCase = false)
Убедитесь, что заголовок или куки не были выведены:
$response->setCookie('foo', 'bar');
ob_start();
$this->response->send();
$output = ob_get_clean(); // in case you want to check the actual body
$this->assertHeaderNotEmitted("Set-Cookie: banana");
Примечание: тестовый случай с этим должен быть запущен как отдельный процесс в PHPunit.
assertCloseEnough($expected, $actual, $message = ‘’, $tolerance = 1)
Для тестирования с продолжительным временем выполнения тесты проверяют, что абсолютная разница между ожидаемым и фактическим временем находится в заданном допуске:
$timer = new Timer();
$timer->start('longjohn', strtotime('-11 minutes'));
$this->assertCloseEnough(11 * 60, $timer->getElapsedTime('longjohn'));
Вышеуказанный тест позволит фактическому времени быть либо 660, либо 661 секундой.
assertCloseEnoughString($expected, $actual, $message = ‘’, $tolerance = 1)
Для тестирования с продолжительным временем выполнения тесты проверяют, что абсолютная разница между ожидаемым и фактическим временем, отформатированными как строки, находится в заданном допуске:
$timer = new Timer();
$timer->start('longjohn', strtotime('-11 minutes'));
$this->assertCloseEnoughString(11 * 60, $timer->getElapsedTime('longjohn'));
Вышеуказанный тест позволит фактическому времени быть либо 660, либо 661 секундой.
Доступ к защищенным/частным свойствам
При тестировании можно использовать следующие методы установки и получения для доступа к защищенным и частным методам и свойствам в классах, которые вы тестируете.
getPrivateMethodInvoker($instance, $method)
Позволяет вызывать частные методы извне класса. Возвращает функцию, которую можно вызвать. Первый параметр — экземпляр класса для тестирования. Второй параметр — имя метода, который нужно вызвать.
// Create an instance of the class to test
$obj = new Foo();
// Get the invoker for the 'privateMethod' method.
$method = $this->getPrivateMethodInvoker($obj, 'privateMethod');
// Test the results
$this->assertEquals('bar', $method('param1', 'param2'));
getPrivateProperty($instance, $property)
Возвращает значение частного/защищенного свойства класса из экземпляра класса. Первый параметр — экземпляр класса для тестирования. Второй параметр — имя свойства.
// Create an instance of the class to test
$obj = new Foo();
// Test the value
$this->assertEquals('bar', $this->getPrivateProperty($obj, 'baz'));
setPrivateProperty($instance, $property, $value)
Устанавливает защищенное значение внутри экземпляра класса. Первый параметр — экземпляр класса для тестирования. Второй параметр — имя свойства, значение которого нужно установить. Третий параметр — значение, на которое нужно установить свойство:
// Create an instance of the class to test $obj = new Foo(); // Set the value $this->setPrivateProperty($obj, 'baz', 'oops!'); // Do normal testing...
Моделирование сервисов
Часто вам нужно будет смоделировать один из сервисов, определённых в app/Config/Services.php, чтобы ограничить тесты только нужным кодом, имитируя различные ответы от сервисов. Это особенно актуально при тестировании контроллеров и других интеграционных тестах. Класс Services предоставляет следующие методы для упрощения этой задачи.
injectMock()
Этот метод позволяет определить точный экземпляр, который будет возвращён классом Services. Вы можете использовать его для установки свойств сервиса, чтобы он вел себя определённым образом, или заменить сервис смоделированным классом.
public function testSomething()
{
$curlrequest = $this->getMockBuilder('CodeIgniter\HTTP\CURLRequest')
->setMethods(['request'])
->getMock();
Services::injectMock('curlrequest', $curlrequest);
// Do normal testing here....
}
Первый параметр — сервис, который вы заменяете. Имя должно точно соответствовать имени функции в классе Services. Второй параметр — экземпляр, которым нужно заменить сервис.
reset()
Удаляет все смоделированные классы из класса Services, возвращая его к исходному состоянию.
resetSingle(string $name)
Удаляет любой смоделированный и общий экземпляры для одного сервиса по его имени.
Примечание
Сервисы Cache, Email и Session моделируются по умолчанию для предотвращения навязчивого поведения при тестировании. Чтобы предотвратить моделирование, удалите их обратный вызов метода из свойства класса: $setUpMethods = ['mockEmail', 'mockSession'];
Моделирование экземпляров фабрики
Аналогично сервисам, вы можете столкнуться с необходимостью предоставления предварительно настроенного экземпляра класса во время тестирования, который будет использоваться с Factories. Используйте те же статические методы injectMock() и reset() что и в классе Services, но они принимают дополнительный параметр для имени компонента:
protected function setUp()
{
parent::setUp();
$model = new MockUserModel();
Factories::injectMock('models', 'App\Models\UserModel', $model);
}
Примечание
Все фабрики компонентов сбрасываются по умолчанию между каждым тестом. Измените $setUpMethods своего тестового случая, если вам нужны сохраняющиеся экземпляры.
Потоковые фильтры
CITestStreamFilter предоставляет альтернативу этим вспомогательным методам.
Возможно, вам нужно протестировать сложные вещи. Иногда захват потока, например, собственного STDOUT или STDERR PHP, может быть полезен. CITestStreamFilter поможет вам захватить вывод из выбранного вами потока.
Пример, демонстрирующий это внутри одного из ваших тестовых случаев:
public function setUp()
{
CITestStreamFilter::$buffer = '';
$this->stream_filter = stream_filter_append(STDOUT, 'CITestStreamFilter');
}
public function tearDown()
{
stream_filter_remove($this->stream_filter);
}
public function testSomeOutput()
{
CLI::write('first.');
$expected = "first.\n";
$this->assertSame($expected, CITestStreamFilter::$buffer);
}
© 2014–2020 British Columbia Institute of Technology
Licensed under the MIT License.
https://codeigniter.com/user_guide/testing/overview.html