Юнит-тестирование для чайников
Содержание:
- Глобальное состояние
- Saída de Erro
- API утверждений базы данных
- 对输出进行测试
- Testing Exceptions
- Composing a Test Suite Using XML Configuration
- Testando Erros PHP
- Testing PHP Errors, Warnings, and Notices
- Testando Saídas
- Error output
- TestRunner の拡張
- Testing Exceptions
- Testing Output
- Типы тестов
- Определение покрытых методов
- Testando Exceções
- Что мы будем проверять?
- Четыре этапа теста базы данных
Глобальное состояние
Трудно тестировать код, который использует синглтоны.
То же самое относится и к коду, использующему глобальные переменные. Обычно код,
который вы хотите протестировать, сильно связан с глобальной переменной, и вы не можете
управлять её созданием. Ещё одна проблема заключается в том, что одно изменение в тесте,
использующим глобальную переменную, может сломать другой тест.
В PHP глобальные переменные работают следующим образом:
- Глобальная переменная сохраняется как .
- Переменная — это так называемая суперглобальная переменная.
- Суперглобальные переменные — это встроенные переменные, доступные во всех областях видимости.
- В области видимости функции или метода вы можете получить доступ к либо напрямую через или используя
для создания локальной переменной в текущей области видимости, ссылающиеся на глобальную переменную.
Помимо глобальных переменных, статические атрибуты классов также являются частью
глобального состояния.
До версии 6, PHPUnit по умолчанию запускал тесты таким образом,
что изменения в глобальных и суперглобальных переменных (,
, ,
, ,
, ,
) не влияли на другие тесты.
Начиная с версии 6, PHPUnit больше не делает операции резервного копирования и восстановления
глобальных и суперглобальных переменных по умолчанию.
Это можно включить, используя опцию
или настройку в конфигурационном XML-файле.
Используя опцию или настройку
в конфигурационном
XML-файле, данная изоляция выше может быть расширена до статических атрибутов классов.
Примечание
Операции резервного копирования и восстановления глобальных переменных и статических
атрибутов классов используют и
.
Объекты некоторых классов (например, ) не могут быть
сериализованы, и операция резервного копирования будет прервана,
когда подобный объект будет сохраняться, например, в массив .
Аннотация , которая обсуждается в
, может использоваться для
управления операциями резервного копирования и восстановления глобальных переменных.
Кроме этого, вы можете предоставить чёрный список глобальных переменных, которые должны быть
исключены при выполнении операций резервного копирования и восстановления, как показано ниже:
class MyTest extends TestCase { protected $backupGlobalsBlacklist = 'globalVariable']; // ... }
Примечание
Установка свойства внутри, например,
метода , не даст никакого эффекта.
Аннотацию , обсуждаемая в
, можно использовать
для резервного копирования всех статических значений свойств во всех объявленных классах
перед каждым тестом с последующим их восстановлением.
Она обрабатывает все классы, объявленные в момент запуска теста, а не
только сам тестовый класс. Она применяется только к статическим свойствам класса,
а не к статическим переменным внутри функций.
Примечание
Операция выполняется перед каждым тестовым методом,
но только если она включена. Если статическое значение было
изменено ранее выполненным тестом с отключенным
, тогда это значение будет скопировано
и восстановлено, но не к первоначальному значению по умолчанию.
PHP не записывает первоначально объявленное значение по умолчанию любой
статической переменной.
То же самое относительно и к статическим свойствам классов, которые недавно были
загружены или объявлены внутри теста. Они не могут быть сброшены к первоначально
объявленному значению по умолчанию после теста, так как это значение неизвестно.
Независимо установленного значения, произойдёт утечка памяти в последующие тесты.
Для модульных тестов рекомендуется явно сбросить значения статических свойств
в методе теста (и в идеале также в методе ,
чтобы не повлиять на последующие выполняемые тесты).
Вы можете предоставить чёрный список статических атрибутов, которые должны быть исключены из операций резервного копирования и восстановления:
class MyTest extends TestCase { protected $backupStaticAttributesBlacklist = 'className' => 'attributeName' ]; // ... }
Примечание
Установка свойства внутри,
например, метода , не даст никакого эффекта.
Saída de Erro
Sempre que um teste falha o PHPUnit faz o melhor para fornecer a você o
máximo possível de conteúdo que possa ajudar a identificar o problema.
Example 2.15 Saída de erro gerada quando uma comparação de vetores falha
<?php use PHPUnit\Framework\TestCase; class ArrayDiffTest extends TestCase { public function testEquality() { $this->assertEquals( 1, 2, 3, 4, 5, 6], 1, 2, 33, 4, 5, 6 ); } } ?>
$ phpunit ArrayDiffTest PHPUnit 7.0.0 by Sebastian Bergmann and contributors. F Time: seconds, Memory: 5.25Mb There was 1 failure: 1) ArrayDiffTest::testEquality Failed asserting that two arrays are equal. --- Expected +++ Actual @@ @@ Array ( => 1 1 => 2 - 2 => 3 + 2 => 33 3 => 4 4 => 5 5 => 6 ) /home/sb/ArrayDiffTest.php:7 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
Neste exemplo apenas um dos valores dos vetores diferem e os outros valores
são exibidos para fornecer o contexto onde o erro ocorreu.
Quando a saída gerada for longa demais para ler o PHPUnit vai quebrá-la
e fornecer algumas linhas de contexto ao redor de cada diferença.
Example 2.16 Saída de erro quando uma comparação de um vetor longo falha
<?php use PHPUnit\Framework\TestCase; class LongArrayDiffTest extends TestCase { public function testEquality() { $this->assertEquals( , , , , , , , , , , , , 1, 2, 3, 4, 5, 6], , , , , , , , , , , , , 1, 2, 33, 4, 5, 6 ); } } ?>
$ phpunit LongArrayDiffTest PHPUnit 7.0.0 by Sebastian Bergmann and contributors. F Time: seconds, Memory: 5.25Mb There was 1 failure: 1) LongArrayDiffTest::testEquality Failed asserting that two arrays are equal. --- Expected +++ Actual @@ @@ 13 => 2 - 14 => 3 + 14 => 33 15 => 4 16 => 5 17 => 6 ) /home/sb/LongArrayDiffTest.php:7 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
Casos Extremos
Quando uma comparação falha o PHPUnit cria uma representação textual da
entrada de valores e as compara. Devido a essa implementação uma diferenciação
pode mostrar mais problemas do que realmente existem.
Isso só acontece quando se usa assertEquals ou outra função de comparação ‘fraca’
em vetores ou objetos.
Example 2.17 Caso extremo na geração de diferenciação quando se usa uma comparação fraca
<?php use PHPUnit\Framework\TestCase; class ArrayWeakComparisonTest extends TestCase { public function testEquality() { $this->assertEquals( 1, 2, 3, 4, 5, 6], '1', 2, 33, 4, 5, 6 ); } } ?>
$ phpunit ArrayWeakComparisonTest PHPUnit 7.0.0 by Sebastian Bergmann and contributors. F Time: seconds, Memory: 5.25Mb There was 1 failure: 1) ArrayWeakComparisonTest::testEquality Failed asserting that two arrays are equal. --- Expected +++ Actual @@ @@ Array ( - => 1 + => '1' 1 => 2 - 2 => 3 + 2 => 33 3 => 4 4 => 5 5 => 6 ) /home/sb/ArrayWeakComparisonTest.php:7 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
Neste exemplo a diferença no primeiro índice entre
e é
relatada ainda que o assertEquals considere os valores como uma combinação.
API утверждений базы данных
Инструмент тестирования расширения базы данных, безусловно, содержит
утверждения, которые вы можете использовать для проверки текущего состояния базы данных,
таблиц и подсчёта строк таблиц. В этом разделе подробно описывается
эта функциональность:
Утверждение количество строк таблицы
Часто бывает полезно проверить, содержит ли таблица определённое количество строк.
Вы можете легко достичь этого без дополнительного кода, используя
API Connection. Предположим, мы хотим проверить, что после вставки
строк в нашу гостевую книгу мы имеем не только две первоначальные записи,
которые были во всех предыдущих примерах, но а также третью, только что добавленную:
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class GuestbookTest extends TestCase { use TestCaseTrait; public function testAddEntry() { $this->assertSame(2, $this->getConnection()->getRowCount('guestbook'), "Pre-Condition"); $guestbook = new Guestbook(); $guestbook->addEntry("suzy", "Hello world!"); $this->assertSame(3, $this->getConnection()->getRowCount('guestbook'), "Inserting failed"); } }
Утверждение состояния таблицы
Предыдущее утверждение полезно, но мы обязательно хотим проверить
фактическое содержимое таблицы, чтобы убедиться, что все значения были
записаны в соответствующие столбцы. Это может быть достигнуто с помощью утверждения
таблицы.
Для этого нам нужно определить экземпляр таблицы запроса (Query Table), который выводит
содержимое по имени таблицы и SQL-запроса и сравнивает его с набором данных на основе файлов/массивов:
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class GuestbookTest extends TestCase { use TestCaseTrait; public function testAddEntry() { $guestbook = new Guestbook(); $guestbook->addEntry("suzy", "Hello world!"); $queryTable = $this->getConnection()->createQueryTable( 'guestbook', 'SELECT * FROM guestbook' ); $expectedTable = $this->createFlatXmlDataSet("expectedBook.xml") ->getTable("guestbook"); $this->assertTablesEqual($expectedTable, $queryTable); } }
Теперь для этого утверждения мы должны создать обычный XML-файл expectedBook.xml:
<?xml version="1.0" ?> <dataset> <guestbook id="1" content="Привет, дружище!" user="joe" created="2010-04-24 17:15:23" /> <guestbook id="2" content="Мне нравится это!" user="nancy" created="2010-04-26 12:14:20" /> <guestbook id="3" content="Привет, мир!" user="suzy" created="2010-05-01 21:47:08" /> </dataset>
Это утверждение будет успешным только в том случае, если оно будет запущено точно в 2010–05–01 21:47:08.
Даты представляют собой особую проблему при тестировании с использованием базы данных, и мы может обойти
эту ошибку, опуская столбец «created» в утверждении.
Скорректированный файл Flat XML expectedBook.xml, вероятно, теперь
должен выглядеть следующим образом для прохождения утверждения:
<?xml version="1.0" ?> <dataset> <guestbook id="1" content="Привет, дружище!" user="joe" /> <guestbook id="2" content="Мне нравится это!" user="nancy" /> <guestbook id="3" content="Привет, мир!" user="suzy" /> </dataset>
Мы должны исправить вызов таблицы запроса (Query Table):
<?php $queryTable = $this->getConnection()->createQueryTable( 'guestbook', 'SELECT id, content, user FROM guestbook' );
Утверждение результата запроса
Вы также можете утверждать результат сложных запросов с помощью подхода Query
Table, просто указав имя результата с запросом и сравнивая его с набором данным:
<?php use PHPUnit\Framework\TestCase; use PHPUnit\DbUnit\TestCaseTrait; class ComplexQueryTest extends TestCase { use TestCaseTrait; public function testComplexQuery() { $queryTable = $this->getConnection()->createQueryTable( 'myComplexQuery', 'SELECT complexQuery...' ); $expectedTable = $this->createFlatXmlDataSet("complexQueryAssertion.xml") ->getTable("myComplexQuery"); $this->assertTablesEqual($expectedTable, $queryTable); } }
对输出进行测试
有时候,想要断言(比如说)某方法的运行过程中生成了预期的输出(例如,通过 或 )。 类使用 PHP 的 输出缓冲 特性来为此提供必要的功能支持。
展示了如何用 方法来设定所预期的输出。如果没有产生预期的输出,测试将计为失败。
Example 2.13 对函数或方法的输出进行测试
<?php use PHPUnit\Framework\TestCase; class OutputTest extends TestCase { public function testExpectFooActualFoo() { $this->expectOutputString('foo'); print 'foo'; } public function testExpectBarActualBaz() { $this->expectOutputString('bar'); print 'baz'; } } ?>
$ phpunit OutputTest PHPUnit 7.0.0 by Sebastian Bergmann and contributors. .F Time: seconds, Memory: 5.75Mb There was 1 failure: 1) OutputTest::testExpectBarActualBaz Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'bar' +'baz' FAILURES! Tests: 2, Assertions: 2, Failures: 1.
中列举了用于对输出进行测试的各种方法。
方法 | 含义 |
---|---|
设置输出预期为输出应当匹配正则表达式 。 | |
设置输出预期为输出应当与 字符串相等。 | |
设置回调函数,用来做诸如将实际输出规范化之类的动作。 | |
获取实际输出。 |
Testing Exceptions
shows how to use the method to test
whether an exception is thrown by the code under test.
Example 2.11 Using the expectException() method
<?php use PHPUnit\Framework\TestCase; class ExceptionTest extends TestCase { public function testException() { $this->expectException(InvalidArgumentException::class); } }
$ phpunit ExceptionTest PHPUnit 9.1.0 by Sebastian Bergmann and contributors. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ExceptionTest::testException Failed asserting that exception of type "InvalidArgumentException" is thrown. FAILURES! Tests: 1, Assertions: 1, Failures: 1.
In addition to the method the
,
, and
methods exist to set up
expectations for exceptions raised by the code under test.
Composing a Test Suite Using XML Configuration
PHPUnit’s XML configuration file ()
can also be used to compose a test suite.
shows a minimal file that will add all
classes that are found in
files when the
directory is recursively traversed.
Example 5.1 Composing a Test Suite Using XML Configuration
<phpunit bootstrap="src/autoload.php"> <testsuites> <testsuite name="money"> <directory>tests</directory> </testsuite> </testsuites> </phpunit>
To run the test suite, use the the option:
$ phpunit --bootstrap src/autoload.php --testsuite money PHPUnit 9.1.0 by Sebastian Bergmann and contributors. .. Time: 167 ms, Memory: 3.00Mb OK (2 test, 2 assertions)
If or
(in that order) exist in the
current working directory and is
not used, the configuration will be automatically
read from that file.
The order in which tests are executed can be made explicit:
Example 5.2 Composing a Test Suite Using XML Configuration
<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>
Testando Erros PHP
Por padrão, o PHPUnit converte os erros, avisos e notificações do PHP que são
disparados durante a execução de um teste para uma exceção. Usando essas
exceções, você pode, por exemplo, esperar que um teste dispare um erro PHP como
mostrado no .
Note
A configuração em tempo de execução do PHP pode
limitar quais erros o PHPUnit irá converter para exceções. Se você
está tendo problemas com essa funcionalidade, certifique-se que o PHP não está configurado para
suprimir os tipos de erros que você esta testando.
Example 2.12 Esperando um erro PHP usando @expectedException
<?php use PHPUnit\Framework\TestCase; class ExpectedErrorTest extends TestCase { /** * @expectedException PHPUnit\Framework\Error */ public function testFailingInclude() { include 'not_existing_file.php'; } } ?>
$ phpunit -d error_reporting=2 ExpectedErrorTest PHPUnit 7.0.0 by Sebastian Bergmann and contributors. . Time: seconds, Memory: 5.25Mb OK (1 test, 1 assertion)
e
representam notificações
e avisos do PHP, respectivamente.
Note
Você deve ser o mais específico possível quando testar exceções. Testar
por classes que são muito genéricas pode causar efeitos colaterais
indesejáveis. Da mesma forma, testar para a classe
com ou
não é mais permitido.
Ao testar algo que dependa de funções php que disparam erros como
pode ser útil algumas vezes usar a supressão de
erros enquanto testa. Isso permite a você verificar os valores retornados por
suprimir notificações que levariam a uma
phpunit.
Example 2.13 Testando valores de retorno de código que utiliza PHP Errors
<?php use PHPUnit\Framework\TestCase; class ErrorSuppressionTest extends TestCase { public function testFileWriting() { $writer = new FileWriter; $this->assertFalse(@$writer->write('/is-not-writeable/file', 'stuff')); } } class FileWriter { public function write($file, $content) { $file = fopen($file, 'w'); if($file == false) { return false; } // ... } } ?>
$ phpunit ErrorSuppressionTest PHPUnit 7.0.0 by Sebastian Bergmann and contributors. . Time: 1 seconds, Memory: 5.25Mb OK (1 test, 1 assertion)
Sem a supressão de erros o teste teria relatado uma falha
fopen(/is-not-writeable/file): failed to open stream:
Testing PHP Errors, Warnings, and Notices
By default, PHPUnit converts PHP errors, warnings, and notices that are
triggered during the execution of a test to an exception. Among other benefits,
this makes it possible to expect that a PHP error, warning, or notice is
triggered in a test as shown in
.
Note
PHP’s runtime configuration can
limit which errors PHPUnit will convert to exceptions. If you are
having issues with this feature, be sure PHP is not configured to
suppress the type of error you are interested in.
Example 2.12 Expecting PHP errors, warnings, and notices
<?php declare(strict_types=1); use PHPUnit\Framework\TestCase; final class ErrorTest extends TestCase { public function testDeprecationCanBeExpected() void { $this->expectDeprecation(); // Optionally test that the message is equal to a string $this->expectDeprecationMessage('foo'); // Or optionally test that the message matches a regular expression $this->expectDeprecationMessageMatches('/foo/'); \trigger_error('foo', \E_USER_DEPRECATED); } public function testNoticeCanBeExpected() void { $this->expectNotice(); // Optionally test that the message is equal to a string $this->expectNoticeMessage('foo'); // Or optionally test that the message matches a regular expression $this->expectNoticeMessageMatches('/foo/'); \trigger_error('foo', \E_USER_NOTICE); } public function testWarningCanBeExpected() void { $this->expectWarning(); // Optionally test that the message is equal to a string $this->expectWarningMessage('foo'); // Or optionally test that the message matches a regular expression $this->expectWarningMessageMatches('/foo/'); \trigger_error('foo', \E_USER_WARNING); } public function testErrorCanBeExpected() void { $this->expectError(); // Optionally test that the message is equal to a string $this->expectErrorMessage('foo'); // Or optionally test that the message matches a regular expression $this->expectErrorMessageMatches('/foo/'); \trigger_error('foo', \E_USER_ERROR); } }
When testing code that uses PHP built-in functions such as that
may trigger errors it can sometimes be useful to use error suppression while
testing. This allows you to check the return values by suppressing notices
that would lead to an exception raised by PHPUnit’s error handler.
Example 2.13 Testing return values of code that uses PHP Errors
<?php use PHPUnit\Framework\TestCase; class ErrorSuppressionTest extends TestCase { public function testFileWriting() { $writer = new FileWriter; $this->assertFalse(@$writer->write('/is-not-writeable/file', 'stuff')); } } class FileWriter { public function write($file, $content) { $file = fopen($file, 'w'); if ($file == false) { return false; } // ... } }
$ phpunit ErrorSuppressionTest PHPUnit 9.2.0 by Sebastian Bergmann and contributors. . Time: 1 seconds, Memory: 5.25Mb OK (1 test, 1 assertion)
Testando Saídas
Às vezes você quer assegurar que a execução de um método, por
exemplo, gere uma saída esperada (via ou
, por exemplo). A
classe usa a funcionalidade
Output
Buffering do PHP para fornecer a funcionalidade que é
necessária para isso.
mostra como usar o método para
definir a saída esperada. Se essa saída esperada não for gerada, o
teste será contado como uma falha.
Example 2.14 Testando a saída de uma função ou método
<?php use PHPUnit\Framework\TestCase; class OutputTest extends TestCase { public function testExpectFooActualFoo() { $this->expectOutputString('foo'); print 'foo'; } public function testExpectBarActualBaz() { $this->expectOutputString('bar'); print 'baz'; } } ?>
$ phpunit OutputTest PHPUnit 7.0.0 by Sebastian Bergmann and contributors. .F Time: seconds, Memory: 5.75Mb There was 1 failure: 1) OutputTest::testExpectBarActualBaz Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'bar' +'baz' FAILURES! Tests: 2, Assertions: 2, Failures: 1.
mostra os métodos fornecidos para testar saídas.
Método | Significado |
---|---|
Configura a expectativa de que a saída combine com uma . | |
Configura a expectativa de que a saída é igual a uma . | |
Define um callback que é usado, por exemplo, para normalizar a saída real. | |
Recupera a saída real. |
Error output
Whenever a test fails PHPUnit tries its best to provide you with as much
context as possible that can help to identify the problem.
Example 2.15 Error output generated when an array comparison fails
<?php use PHPUnit\Framework\TestCase; class ArrayDiffTest extends TestCase { public function testEquality() { $this->assertSame( 1, 2, 3, 4, 5, 6], 1, 2, 33, 4, 5, 6 ); } }
$ phpunit ArrayDiffTest PHPUnit 9.0.0 by Sebastian Bergmann and contributors. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) ArrayDiffTest::testEquality Failed asserting that two arrays are identical. --- Expected +++ Actual @@ @@ Array ( 0 => 1 1 => 2 - 2 => 3 + 2 => 33 3 => 4 4 => 5 5 => 6 ) /home/sb/ArrayDiffTest.php:7 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
In this example only one of the array values differs and the other values
are shown to provide context on where the error occurred.
When the generated output would be long to read PHPUnit will split it up
and provide a few lines of context around every difference.
Example 2.16 Error output when an array comparison of an long array fails
<?php use PHPUnit\Framework\TestCase; class LongArrayDiffTest extends TestCase { public function testEquality() { $this->assertSame( , , , , , , , , , , , , 1, 2, 3, 4, 5, 6], , , , , , , , , , , , , 1, 2, 33, 4, 5, 6 ); } }
$ phpunit LongArrayDiffTest PHPUnit 9.0.0 by Sebastian Bergmann and contributors. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) LongArrayDiffTest::testEquality Failed asserting that two arrays are identical. --- Expected +++ Actual @@ @@ 11 => 0 12 => 1 13 => 2 - 14 => 3 + 14 => 33 15 => 4 16 => 5 17 => 6 ) /home/sb/LongArrayDiffTest.php:7 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
Edge cases
When a comparison fails PHPUnit creates textual representations of the
input values and compares those. Due to that implementation a diff
might show more problems than actually exist.
This only happens when using or other ‘weak’ comparison
functions on arrays or objects.
Example 2.17 Edge case in the diff generation when using weak comparison
<?php use PHPUnit\Framework\TestCase; class ArrayWeakComparisonTest extends TestCase { public function testEquality() { $this->assertEquals( 1, 2, 3, 4, 5, 6], '1', 2, 33, 4, 5, 6 ); } }
$ phpunit ArrayWeakComparisonTest PHPUnit 9.0.0 by Sebastian Bergmann and contributors. F Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) ArrayWeakComparisonTest::testEquality Failed asserting that two arrays are equal. --- Expected +++ Actual @@ @@ Array ( - 0 => 1 + 0 => '1' 1 => 2 - 2 => 3 + 2 => 33 3 => 4 4 => 5 5 => 6 ) /home/sb/ArrayWeakComparisonTest.php:7 FAILURES! Tests: 1, Assertions: 1, Failures: 1.
In this example the difference in the first index between
and is
reported even though considers the values as a match.
TestRunner の拡張
PHPUnit latest は TestRunner エクステンションをサポートしており、
テストの実行中のさまざまなイベントにフックを組み込めます。
PHPUnit の XML 設定ファイルでエクステンションを組み込む方法については
を参照ください。
エクステンションでフックを組み込めるイベントはインターフェイスとして航海されており、
これをエクステンションが実装する必要があります。
PHPUnit latest で使えるイベントの一覧を
に示します。
利用可能なフックインターフェイス
に、
と
を実装したエクステンションの例を示します。
Example 11.6 TestRunner エクステンションの例
<?php namespace Vendor; use PHPUnit\Runner\AfterLastTestHook; use PHPUnit\Runner\BeforeFirstTestHook; final class MyExtension implements BeforeFirstTestHook, AfterLastTestHook { public function executeAfterLastTest() void { // 最後のテストの実行後にコールされます } public function executeBeforeFirstTest() void { // 最初のテストの実行前にコールされます } }
Testing Exceptions
shows how to use the method to test
whether an exception is thrown by the code under test.
Example 2.11 Using the expectException() method
<?php use PHPUnit\Framework\TestCase; class ExceptionTest extends TestCase { public function testException() { $this->expectException(InvalidArgumentException::class); } }
$ phpunit ExceptionTest PHPUnit 9.0.0 by Sebastian Bergmann and contributors. F Time: 0 seconds, Memory: 4.75Mb There was 1 failure: 1) ExceptionTest::testException Failed asserting that exception of type "InvalidArgumentException" is thrown. FAILURES! Tests: 1, Assertions: 1, Failures: 1.
In addition to the method the
,
, and
methods exist to set up
expectations for exceptions raised by the code under test.
Testing Output
Sometimes you want to assert that the execution of a method, for
instance, generates an expected output (via or
, for example). The
class uses PHP’s
Output
Buffering feature to provide the functionality that is
necessary for this.
shows how to use the method to
set the expected output. If this expected output is not generated, the
test will be counted as a failure.
Example 2.14 Testing the output of a function or method
<?php use PHPUnit\Framework\TestCase; class OutputTest extends TestCase { public function testExpectFooActualFoo() { $this->expectOutputString('foo'); print 'foo'; } public function testExpectBarActualBaz() { $this->expectOutputString('bar'); print 'baz'; } }
$ phpunit OutputTest PHPUnit 9.2.0 by Sebastian Bergmann and contributors. .F Time: 0 seconds, Memory: 5.75Mb There was 1 failure: 1) OutputTest::testExpectBarActualBaz Failed asserting that two strings are equal. --- Expected +++ Actual @@ @@ -'bar' +'baz' FAILURES! Tests: 2, Assertions: 2, Failures: 1.
shows the methods provided for testing output
Method | Meaning |
---|---|
Set up the expectation that the output matches a . | |
Set up the expectation that the output is equal to an . | |
Sets up a callback that is used to, for instance, normalize the actual output. | |
Get the actual output. |
Типы тестов
Прежде чем мы погрузимся в PHPUnit давайте разберём различные типы тестов. В зависимости от того, как вы хотите категоризировать их, в PHPUnit применяются любые типы тестов для разработки ПО.
Давайте разделим тесты на категории по уровню их специфичности. По данным . В целом существует 4 признанных уровня тестов:
-
Unit-тестирование (модульное): этот уровень тестирует наименьшую единицу функциональности.
С точки зрения разработчика его задачей является убедиться, что тестируемая функция выполняет именно то, для чего она реализована. Таким образом, она должна быть минимально зависима или совершенно независима от другой функции или класса. Она должна быть написана таким образом, чтобы она полностью выполнялась в памяти, т.е. она не должна коннектиться к БД, не должна обращаться к сети или использовать ФС и т.д. Unit-тестирование должно быть как можно более простым.
- Интеграционное тестирование: этот уровень «соединяет» разные единицы кода и тестирует правильно ли работают их комбинации. Он надстраивается сверху над unit-тестированием и способен отловить баги, которые нельзя выявить с помощью unit-тестирования, т.к. интеграционное тестирование проверяет, работает ли класс А с классом Б.
- Системное тестирование: оно создано для воспроизведения работы сценариев в условиях, приближенных к боевым. Оно, в свою очередь, надстраивается сверху над интеграционным тестированием. В то время как интеграционное тестирование обеспечивает слаженную работу различных частей системы. Системное тестирование отвечает за то, что система работает так, как предполагает пользователь, прежде чем отправить её следующий уровень.
- Приёмочное тестирование: в то время как выше приведённые тесты предназначены для разработчиков на стадии разработки, приёмочное тестирование фактически выполняется пользователями ПО. Пользователей не интересуют внутренние особенности ПО, их интересует только как работает это ПО.
Если мы поместим типы тестов в пирамиду, выглядеть это будет так:
Определение покрытых методов
Аннотация (см.
appendixes.annotations.covers.tables.annotations) может
использоваться в тестовом коде для указания, какие методы тестовый метод
хочет протестировать. Если она указана, то в информации о покрытии кода будут будут
только эти указанные методы.
показывает это на примере.
Пример 10.2 Тесты, в которых указывается, какой метод они хотят покрыть
<?php use PHPUnit\Framework\TestCase; class BankAccountTest extends TestCase { protected $ba; protected function setUp() void { $this->ba = new BankAccount; } /** * @covers BankAccount::getBalance */ public function testBalanceIsInitiallyZero() { $this->assertSame(, $this->ba->getBalance()); } /** * @covers BankAccount::withdrawMoney */ public function testBalanceCannotBecomeNegative() { try { $this->ba->withdrawMoney(1); } catch (BankAccountException $e) { $this->assertSame(, $this->ba->getBalance()); return; } $this->fail(); } /** * @covers BankAccount::depositMoney */ public function testBalanceCannotBecomeNegative2() { try { $this->ba->depositMoney(-1); } catch (BankAccountException $e) { $this->assertSame(, $this->ba->getBalance()); return; } $this->fail(); } /** * @covers BankAccount::getBalance * @covers BankAccount::depositMoney * @covers BankAccount::withdrawMoney */ public function testDepositWithdrawMoney() { $this->assertSame(, $this->ba->getBalance()); $this->ba->depositMoney(1); $this->assertSame(1, $this->ba->getBalance()); $this->ba->withdrawMoney(1); $this->assertSame(, $this->ba->getBalance()); } }
Также можно указать, что тест не должен покрывать
какой-либо метод, используя аннотацию
(см.
). Это может быть
полезно при написании интеграционных тестов, чтобы убедиться, что вы
только генерируете покрытие кода с помощью модульных тестов.
Testando Exceções
mostra como usar a anotação para
testar se uma exceção é lançada dentro do código de teste.
Example 2.10 Usando o método expectException()
<?php use PHPUnit\Framework\TestCase; class ExceptionTest extends TestCase { public function testException() { $this->expectException(InvalidArgumentException::class); } } ?>
$ phpunit ExceptionTest PHPUnit 7.0.0 by Sebastian Bergmann and contributors. F Time: seconds, Memory: 4.75Mb There was 1 failure: 1) ExceptionTest::testException Expected exception InvalidArgumentException FAILURES! Tests: 1, Assertions: 1, Failures: 1.
Além do método os métodos
,
, e
existem para configurar
expectativas de exceções lançadas pelo código sob teste.
Alternativamente, você pode usar as anotações ,
,
, e
para configurar
expectativas de exceções lançadas pelo código sob teste.
mostra um exemplo.
Example 2.11 Usando a anotação @expectedException
<?php use PHPUnit\Framework\TestCase; class ExceptionTest extends TestCase { /** * @expectedException InvalidArgumentException */ public function testException() { } } ?>
Что мы будем проверять?
В коротком примере мы создадим класс PHP для конвертации времени из формата, который обычно используется в базе данных (“15-10-2011 22:32″) в строку с относительным временем (“1 month ago”). Подобные классы нуждаются в тщательной объемной проверке, которая послужит отличной иллюстрацией использования Testify.
Класс RelativeTime
Существует несколько способов для вычисления относительного времени. Можно создать набор выражений if/else, который будет работать. Но такой код очень трудно поддерживать и для него очень высока вероятность появления ошибок. Мы будем использовать более элегантный способ. В его основе лежит факт, что каждый период времени состоит из четко определенного количества меньших периодов. То есть, один день состоит из 24 часов, один час — из 60 минут, а каждая минута содержит 60 секунд.
Здесь представлен скелет нашего класса:
class RelativeTime{ // Названия периодов private $names = array('second','minute','hour','day','week','month','year'); // Как много периодов содержится один в другом private $divisions = array(1,60,60,24,7,4.34,12); private $time = NULL; public function __construct($timestr = NULL){ // Можно передавать время при конструировании объекта } public function getOffsetFrom($timestr = NULL){ // Данный метод вычисляет строку относительного времени } public function __toString(){ // Подходящий метод магии PHP } private function timestampFromString($time){ // Данный метод преобразует строку в корректное время в секундах } }
А теперь перейдем к тестированию кода.
Четыре этапа теста базы данных
В своей книге «Шаблоны тестирования xUnit» (xUnit Test Patterns) Джерард Месарош (Gerard Meszaros) перечисляет четыре
этапа (стадии) модульного тестирования:
-
Настройка фикстуры
-
Выполнение системы тестирования (System Under Test)
-
Проверка результата
-
Очистка (teardown)
Тестирование базы данных требует, по крайней мере, установки и очистки,
чтобы очистить и записать необходимые данные фикстуры в ваши таблицы.
Тем не менее, у расширения базы данных есть веские основания для возврата
к четырём этапам при тестировании, использующем базу данных для формирования рабочего процесса,
выполняемого для каждого из тестов:
1. Очистка базы данных
Поскольку всегда есть первый тест, который работает с базой данных,
вы точно не знаете, есть ли в таблицах уже какие-нибудь данные.
PHPUnit выполнит операцию TRUNCATE для всех таблиц, чтобы вернуть их в пустое состояние.