Юнит тестирование в 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-объектами.
Браво! Отличный материал, спасибо!