Юнит тестирование в PHP с помощью PHPUnit. Вникаем в assert-методы.
В предыдущей статье, мы рассмотрели основы юнит тестирования с помощью PHPUnit, рассмотрели какие преимущества дает вам тестирование вашего кода. В этот раз мы окунемся немного глубже и рассмотрим этот фреймворк подробнее.
В PHPUnit есть два метода, которые, при правильном использовании, могут сделать тестирование немного проще. Два метода, markTestSkipped и markTestIncomplete, позволяют получать результаты тестирования отличные от обычных: прошел/не прошел. Метод markTestSkipped будет полезен, например если в ходе выполнения теста произойдет ошибка.
Например, вы тестируете существование записи в базе. В идеале вы всегда можете подключиться к базе, но что если не можете? Ваш тест не будет пройден, что будет означать несуществование записи. С помощью метода markTestSkipped, вы просто можете пропустить данный тест при обнаружении ошибки:
<?php
public function testThisMightHaveADb()
{
$myObject->createObject();
try {
$db = new Database();
$this->assertTrue($db->rowExists());
} catch (DatabseException $e) {
$this->markTestSkipped('This test was skipped because there was a database problem');
}
}
?>
В этом примере возможно три варианта исхода событий. Все хорошо и тест завершился успешно. Тест assertTrue может провалиться, если метод rowExists вернет false.
И наконец, объект для связи с бд может возбудить исключение DatabaseException. Блок try/catch поймает исключение, что будет означать, что база недоступна и выполнение теста невозможно. Но вместо получения сообщения о неуспешном тестировании, мы просто его пропустим. Результат “skipped” будет показан среди остальных и вы будете знать, что следует вернуться к этому тесту и решить проблему.
Метод markTestIncomplete предоставляет похожий функционал. Он дает возможность пометить тест как незаконченный. Это значит, что вы начали писать тест, но не закончили и намерены вернуться к нему позже. Или, например, для TDD (test driven development или разработка через тестирование) возможна ситуация, когда тест написан для кода, которого еще нет и тогда можно пометить этот тест как незавершенный. Вы даже можете указать сообщение с указанием того почему тест пропущен:
<?php
public function testAreNotEnoughHours()
{
$this->markTestIncomplete("There aren't enough hours in the day to have my tests go green");
$trueVariable = true;
$this->assertTrue($trueVariable);
}
?>
Следует отметить, что это только вспомогательные методы и не должны быть оправданием плохих тестов. Убедитесь в том, что вы не оставили пропущенные или незавершенные без внимания.
Assertions, Assertions, Assertions
В прошлой статье мы рассмотрели некоторые базовые assert-методы в PHPUnit. Теперь мы рассмотрим примеры их использования.
Напишем класс, который будем тестировать:
<?php
class Testable
{
public $trueProperty = true;
public $resetMe = true;
public $testArray = array(
'first key' => 1,
'second key' => 2
);
private $testString = "I do love me some strings";
public function __construct()
{
}
public function addValues($valueOne,$valueTwo) {
return $valueOne+$valueTwo;
}
public function getTestString()
{
return $this->testString;
}
}
?>
Напишем также тестирующий класс, в котором будут содержаться все тестирующие методы:
<?php
class TestableTest extends PHPUnit_Framework_TestCase
{
private $_testable = null;
public function setUp()
{
$this->_testable = new Testable();
}
public function tearDown()
{
$this->_testable = null;
}
/** здесь будут тестирующие методы */
}
?>
Мы используем методы setUp и tearDown для подготовки тестов. Метод setUp выполняется перед запуском всех тестов, а tearDown — после того как все тесты отработали. Поэтому в функции setUp мы создаем объект класса Testable, к которому будем иметь доступ во всех тестах.
True или False
Теперь, когда у нас есть инстанс класса для тестирования, можно начать его тестирование 🙂 Помните, что модульное тестирование — это тестирование небольших модулей вашего кода, а не всего приложения сразу. Конечно, есть исключения, но нужно стараться делать небольшие тесты. Начнем с простейших проверок — assertTrue и assertFalse.
<?php
public function testTruePropertyIsTrue()
{
$this->assertTrue($this->_testable->trueProperty,"trueProperty isn't true");
}
public function testTruePropertyIsFalse()
{
$this->assertFalse($this->_testable->trueProperty, "trueProperty isn't false");
}
?>
Мы рассматривали методы assertTrue и assertFalse в прошлой статье, но здесь мы внесли некоторые изменения: указали сообщение, которое выведет PHPUnit если тест провалится, взамен стандартного “Failed asserting that is false”, которое не очень то помогает разобраться в проблеме.
Матемагия
Следующие тесты будут более математическими 🙂 Эти тесты помогают выяснить как соотносятся переданные переменные:
<?php
public function testValueEquals()
{
$valueOne = 4;
$valueTwo = 2;
$this->assertEquals($this->_testable->addValues($valueOne,$valueTwo),6);
}
public function testValueGreaterThan()
{
$valueOne = 4;
$valueTwo = 2;
$this->assertGreaterThan($valueTwo,$valueOne);
}
public function testLessThanOrEqual()
{
$valueOne = 4;
$valueTwo = 2;
$this->assertLessThanOrEqual($valueTwo,$valueOne);
}
public function testAreObjectsEqual()
{
$testTwo = new Testable();
$this->_testable->resetMe = false;
$this->assertEquals($this->_testable,$testTwo);
}
?>
Благодаря понятным названиям методов этот код практически не нуждается в объяснениях. Вы можете использовать такие методы как: assertGreaterThan, assertLessThan, assertGreaterThanOrEqual,assertLessThanOrEqual, и assertEquals.
Первый — assertEquals демонстрирует как вы можете использовать метод тестируемого класса для тестирования возвращаемых значений.
В этом примере есть еще один интересный пример использования метода assertEquals. С помощью этого метода мы можем сравнивать не только числовые значения, но и объекты, массивы и даже элементы DOM документов. Функция сравнивает все атрибуты объектов, но так как мы изменили свойство resetMe, объекты будут отличаться и assertEquals выдаст соответствующее сообщение: «Failed asserting that two objects are equal». Больше информации по использованию этого (и других тоже) метода можно посмотреть в документации.
Проверка строк
В PHPUnit есть также очень полезные методы для работы со строками. Эти функции могут сделать ваше тестирование проще, а код чище и более читаемым:
<?php
public function testStringEnding()
{
$testString = $this->_testable->getTestString();
$this->assertStringEndsWith('frood',$testString);
}
public function testStringStarts()
{
$testString = $this->_testable->getTestString();
$this->assertStringStartsWith('hoopy',$testString);
}
public function testEqualFileContents()
{
$this->assertStringEqualsFile('/path/to/textfile.txt','foo');
}
public function testDoesStringMatchFormat()
{
$testString = $this->_testable->getTestString();
$this->assertStringMatchesFormat('%s',$testString);
}
public function testDoesStringFileFormat()
{
$testString = $this->_testable->getTestString();
$this->assertStringMatchesFormatFile('/path/to/textfie.txt','foo');
}
?>
Также как и с математическими методами, имена функций дают понять, что они проверяют. Первые два метода убеждаются в том, что строки заканчиваются или начинаются с указанных строк. Метод assertStringEqualsFile проверяет на равенство содержимое файла и переданной строки. Этот метод избавляет вас от необходимости самостоятельно считывать содержимое файла. Данный тест завершится успешно, если файл будет содержать строку "foo".
Следующие два метода, assertStringMatchesFormat и assertStringMatchesFormatFile, дают возможность проверять строки на соответствие заданному формату. Вы можете использовать предопределенные шаблоны (%e, %s, %S, %a, %A и т.д.), полный список которых вы можете найти в документации.
Рассмотрим еще два метода: assertNull и assertSame. И, как всегда, их имена довольно хорошо описывают то, что они делают. Пример:
<?php
public function testStringIsNotNull()
{
$notANull = “i'm not a null!”;
$this->assertNull($notANull);
}
public function testStringIsSame()
{
$numberAsString = '1234';
$this->assertSame(1234,$numberAsString);
}
?>
Первая проверка будет не успешна. Этот метод проверит значение переменной $notANull на равенство NULL, но в этой переменной у нас хранится строка.
Теперь поговорим о методе assertSame. Как вы знаете, PHP — язык с динамической типизацией, поэтому простая проверка на равенство числа 1234 и строки "1234" завершится успешно. Но метод assertEquals об этом знает и учитывает не только значение переменных, но и их тип. Поэтому второй тест тоже провалится.
Рассмотрим еще несколько полезных assert-методов, которые предоставляет PHPUnit:
<?php
public function testArrayKeyExists()
{
$this->assertArrayHasKey('first key',$this->_testable->testArray);
}
public function testAttributeExists()
{
$this->assertClassHasAttribute('resetMe',get_class($this->_testable));
}
public function testFileIsReal()
{
$this->assertFileExists('/path/to/file.txt');
}
public function testIsInstance()
{
$this->assertInstanceOf('OtherClass',$this->_testable);
}
?>
Эти методы позволяют проверять ключи в массивах, свойства классов, существование файлов и типы объектов. Первый тест просто проверит есть ли в нашем тестовом массиве ключ "first key". В следующем, благодаря методу assertClassHasAttribute, мы можем проверить имеет ли инстанс класса атрибут resetMe. Обратите внимание, что метод проверяет не существование атрибута у объекта, а его объявление в классе.
Метод assertFileExists — просто проверяет существует ли файл в файловой системе. Работа метода проходит по тем же правилам, что и работа стандартных файловых PHP функций: если к файлу нет доступа — тест провалится. И, наконец, assertInstanceOf, проверяет тип переменной на соответствие переданному значению. Конечно, нет отличий от такой конструкции: assertTrue($this->_testable instanceof OtherClass), но как я уже говорил использование встроенных функций PHPUnit сделает ваш код проще.
И последний тест, который мы сегодня рассмотрим:
<?php
public function testDoesMatchRegex()
{
$testString = $this->_testable->getTestString();
$this->assertRegExp('/[a-z]+/',$testString);
}
?>
Тест на соответствие регулярному выражению.
На этом все! В следующей статье мы рассмотрим работу с аннотациями и mock-объектами.






Браво! Отличный материал, спасибо!