Я видел, как Hierarchical Context Runner работает в JUnit, и это довольно круто.
Это позволяет вам организовать несколько настроек перед группами методов в одном тестовом классе. Это замечательно, когда вы тестируете несколько сценариев; это больше похоже на выполнение BDD.
Иерархический бегун Объяснение
Было бы неплохо иметь что-то подобное в PHPUnit, но я просто не могу этого добиться.
Я пытался использовать @before
аннотации над пользовательскими методами, в надежде прописать порядок. Кроме того, я пытался объявить внутренние классы, но потом обнаружил, что это не разрешено в PHP 5. Я также пробовал многие другие вещи без успеха.
Можно ли добиться этого с помощью PHPUnit?
Ты не можешь сделать именно так что делает JUnit Heirarchical Context Runner, потому что, как вы обнаружили, вы не можете вкладывать классы в PHP. Heirarchical Context Runner зависит от вложенных классов. Однако, вы можете быть очень близки к тому же. В конечном счете, подумав над тем, как назвать свои методы тестирования, вы можете создать более чистый код, по которому легко ориентироваться и понимать, с меньшим риском случайного введения глобального состояния или скрытых зависимостей, чем если бы вы могли использовать вложенные классы.
Прежде чем мы погрузимся, пожалуйста, обратите внимание, что ты вообще не хочу делить приборы или другое состояние между тестами. Весь смысл модульного тестирования заключается в тестировании отдельных блоков кода, что сложно сделать, когда вы также создаете связи между этими модулями, имея постоянные (или, что еще хуже, переменные) данные в тестах. Как объяснено в документах PHPUnit,
Есть несколько веских причин для того, чтобы разделить результаты между тестами, но в большинстве случаев необходимость совместного использования прибора между тестами является следствием нерешенной проблемы проектирования. [выделение добавлено]
Хорошим примером устройства, которое имеет смысл разделять между несколькими тестами, является соединение с базой данных: вы входите в базу данных один раз и повторно используете соединение с базой данных вместо создания нового соединения для каждого теста. Это заставляет ваши тесты работать быстрее.
Если у вас есть код, который должен быть запущен один раз все ваших тестов во всех ваших тестовых классах, использовать файл начальной загрузки. Например, если ваш код зависит от автозагрузчика или константы, такой как путь, содержащий конкретный файл, или если вам просто нужно запустить серию include
или же require
заявления для загрузки некоторых функций, используйте файл начальной загрузки.
Вы можете сделать это с --bootstrap
опция командной строки, вот так:
phpunit --bootstrap src/autoload.php tests
Вы также можете указать файл начальной загрузки в файле конфигурации XML, например, так:
<phpunit bootstrap="src/autoload.php">
<!-- other configuration stuff here -->
</phpunit>
Вы также можете указать метод настройки для запуска перед запуском любых других тестов в определенном классе. Здесь вы должны поместить весь код, который должен быть запущен до любой тесты в классе. Однако он будет запущен только один раз, поэтому его нельзя использовать для запуска. между тесты.
Например, вы могли бы сделать это, чтобы заполнить данные для одного или нескольких сценариев перед выполнением каких-либо тестов:
<?php
class NameValidatorTest extends PHPUnit_Framework_TestCase
{
protected static $nameForEnglishScenario;
protected static $nameForFrenchScenario;
/**
* Runs before any tests and sets up data for the test
*/
public static function setUpBeforeClass()
{
self::$nameForEnglishScenario = 'John Smith';
self::$nameForFrenchScenario = 'Séverin Lemaître';
}
public function testEnglishName()
{
$this->assertRegExp('/^[a-z -]+$/i', self::$nameForEnglishScenario);
}
public function testFrenchName()
{
$this->assertRegExp('/^[a-zàâçéèêëîïôûùüÿñæœ -]+$/i', self::$nameForFrenchScenario);
}
}
(Не обращайте внимания на реальную логику в этом примере; тесты здесь неэффективны и на самом деле не тестируют класс. Основное внимание уделяется настройке.)
Типичным способом тестирования нескольких сценариев является создание нескольких методов с именами, отражающими их условия. Например, если я тестирую класс валидатора имени, я мог бы сделать что-то вроде этого:
<?php
class NameValidatorTest extends PHPUnit_Framework_TestCase
{
public function testIsValid_Invalid_English_Actually_French()
{
$validator = new NameValidator();
$validator->setName('Séverin Lemaître');
$validator->setLocale('en');
$this->assertFalse($validator->isValid());
}
public function testIsValid_Invalid_French_Gibberish()
{
$validator = new NameValidator();
$validator->setName('Séverin J*E08RW)8WER Lemaître');
$validator->setLocale('fr');
$this->assertFalse($validator->isValid());
}
public function testIsValid_Valid_English()
{
$validator = new NameValidator();
$validator->setName('John Smith');
$validator->setLocale('en');
$this->assertTrue($validator->isValid());
}
public function testIsValid_Valid_French()
{
$validator = new NameValidator();
$validator->setName('Séverin Lemaître');
$validator->setLocale('fr');
$this->assertTrue($validator->isValid());
}
}
Это дает преимущество в объединении всех ваших тестов для класса в одном месте и, если вы называете их разумно, облегчает навигацию даже при большом количестве методов тестирования.
Вы также можете использовать метод провайдера данных. От руководство:
Тестовый метод может принимать произвольные аргументы. Эти аргументы должны быть предоставлены методом поставщика данных (
provider()
в Пример 2.5). Используемый метод поставщика данных указывается с помощью@dataProvider
аннотаций.Увидеть раздел под названием «Поставщики данных» Больше подробностей.
Вы можете использовать поставщики данных для запуска одного и того же тестового кода более одного раза, используя разные данные для каждого запуска для тестирования различных сценариев.
Вы также можете принудительно запускать тесты в классе тестирования, выполнив указание зависимостей между ними. Вы делаете это с помощью @depends
в докблоке. Пример из документации:
<?php
class MultipleDependenciesTest extends PHPUnit_Framework_TestCase
{
public function testProducerFirst()
{
$this->assertTrue(true);
return 'first';
}
public function testProducerSecond()
{
$this->assertTrue(true);
return 'second';
}
/**
* @depends testProducerFirst
* @depends testProducerSecond
*/
public function testConsumer()
{
$this->assertEquals(
array('first', 'second'),
func_get_args()
);
}
}
В этом примере оба testProducerFirst
а также testProducerSecond
гарантированно работать до testConsumer
, Обратите внимание, однако, что testConsumer
будет получать в качестве аргументов результаты testProducerFirst
а также testProducerSecond
и он не будет работать вообще, если один из этих тестов не пройден.
Если вы хотите запустить много тестов на очень В различных сценариях вы можете рассмотреть возможность создания более одного тестового класса для данного целевого класса. Однако, как правило, это не лучший выбор. Это означает создание и поддержание большего количества классов (и, следовательно, большего количества файлов, если вы помещаете в файл только один класс, как вы должны), и это усложняет одновременный просмотр и понимание всего вашего тестового кода. Это действительно подходит только для случаев, когда ваш целевой класс используется в очень разные манеры от одного теста к другому.
Если ты пишешь SOLID код и правильное использование шаблонов проектирования, однако, ваш код не должен быть способен работать в таких различных условиях, что это имеет смысл. Таким образом, это несколько вопрос мнения, но это, вероятно, никогда не правильный способ написания ваших тестов.
Вы также можете указать PHPUnit запустить «тестирование.» Это позволяет группировать тесты логическим способом (например, все тесты базы данных или все тесты для классов с логикой i18n). Когда вы создаете свой набор тестов с использованием файла конфигурации XML, вы можете явно указать PHPUnit запускать ваши тесты в определенном порядке. Как объяснил в документах,
Если
phpunit.xml
или жеphpunit.xml.dist
(в таком порядке) существуют в текущем рабочем каталоге и--configuration
не используется, конфигурация будет автоматически прочитана из этого файла.Порядок выполнения тестов можно сделать явным:
Пример 5.2: Составление набора тестов с использованием конфигурации XML
<phpunit bootstrap="src/autoload.php"> <testsuites> <testsuite name="money"> <file>tests/IntlFormatterTest.php</file> <file>tests/MoneyTest.php</file> <file>tests/CurrencyTest.php</file> </testsuite> </testsuites> </phpunit>
Обратной стороной этого является то, что вы снова вводите форму глобального государства. Что если в вашем реальном приложении вы используете Money
класс, прежде чем запускать некоторые важные функции из IntlFormatter
учебный класс?
Лучше всего использовать setUpBeforeClass()
метод, чтобы сделать настройку для каждого класса теста. Затем используйте несколько методов тестирования для каждого целевого метода, чтобы протестировать различные сценарии.
Существует ряд других способов заставить тесты запускаться в определенном порядке, который я изложил выше. Но все они вводят некоторую форму глобального состояния, беспорядка или обоих. Каждый раз, когда вы выполняете только один тест после завершения другого, вы рискуете ввести зависимости, не осознавая этого. Вы не действительно модульное тестирование, если ваши тесты зависят друг от друга. На каком-то уровне вместо этого вы проводите интеграционное тестирование.
Как правило, вам лучше делать правду единица измерения тесты. Протестируйте каждый публичный метод целевого класса, как будто ничего другого не существует. Когда вы можете сделать это и сдать свои тесты для всех возможных сценариев, затем у вас есть надежный код.
Наконец, я нашел способ добиться почти такого же поведения, как в Hierarchical Runner для JUnit.
Ключ использует @dataProvider
аннотаций. Для каждой подгруппы тестов, которая нуждается в конкретная настройка, Вы можете создать новый метод, содержащий логику для этой настройки и указав DataProvider на тесте.
/**
* @dataProvider provider
*/
public function testWithDataProvider($arg1, $arg2) {
//test logic
//assertion
}
public function provider() {
//custom setup before running "testWithDataProvider"return array(
array(0, 1),
array(1,2)
);
}
Логика немного другая, теперь у вас есть аргументы в вашем тесте, кроме полей в вашем testClass, и вы должны вернуть массив массивов в методе вашего провайдера.
Использование DataProviders дает больше преимуществ, и вы можете найти их в
документация