Статьи

Будьте более внимательны: знакомство с утверждениями PHPUnit +

В моей предыдущей статье я познакомил вас с некоторыми основами модульного тестирования с помощью PHPUnit. Я познакомил вас с тем, что значит тестировать ваш код, и с реальными преимуществами, которые он может принести вам и вашему коду. На этот раз я хочу погрузиться немного глубже и познакомиться с PHPUnit более подробно.

Тесты на маркировку пропущены или не завершены

PHPUnit включает в себя два метода, которые при правильном использовании могут немного упростить вам тестирование. Два метода, markTestSkipped и markTestIncomplete , позволяют вашим тестам иметь разные результаты, чем просто 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 перехватит исключение, которое указывает на то, что база данных недоступна, и, следовательно, весь смысл теста спорный. Вместо того чтобы потерпеть неудачу, он просто пропускает тест «Пропущенный» результат все равно будет отображаться во всех ваших тестовых прогонах, поэтому вы все равно будете знать, что в конечном итоге проблему нужно будет решить.

Метод markTestIncomplete предоставляет нам аналогичную функциональность, но с другим поворотом. Он предназначен для того, чтобы дать вам возможность пометить тест как большой сюрприз как неполный. Это означает, что если вы начнете писать тест и намереваетесь вернуться к нему позже, чтобы закончить его, вы можете ясно указать в результатах теста, что это не ошибка кода приложения, а неполный тест. Вы даже можете определить собственное сообщение, чтобы вы знали, почему тест был пропущен:

 <?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); } ?> 

Важно отметить, что эти два метода предназначены только для удобства и не должны служить оправданием для написания небрежных тестов. Ваше приложение работает так же хорошо, как и тесты, которые его поддерживают, поэтому обязательно пересмотрите все неполные или пропущенные тесты, как только вы закончите работу над основной функциональностью, которую вы тестируете.

Утверждения, Утверждения, Утверждения

В прошлой статье я рассмотрел ряд часто используемых утверждений, предоставляемых 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; } } ?> 

Давайте также настроим наш тестовый класс. Это будет контейнер, в который мы Testable остальные тесты, чтобы они могли работать с нашим классом Testable :

 <?php class TestableTest extends PHPUnit_Framework_TestCase { private $_testable = null; public function setUp() { $this->_testable = new Testable(); } public function tearDown() { $this->_testable = null; } /** test methods will go here */ } ?> 

Мы используем методы setUp и tearDown для подготовки к нашему тестовому запуску. Я кратко упомянул эти методы в прошлой статье, но теперь пришло время использовать их. Метод setUp будет запущен до всех тестов в классе теста, а tearDown будет запущен после всех методов тестирования. В этом случае мы просто создаем новый экземпляр нашего класса Testable и сохраняем его, чтобы мы могли легко получить к нему доступ в каждом методе тестирования.

Правда или ложь

Теперь, когда у нас есть экземпляр нашего класса для тестирования, давайте перейдем к нашим утверждениям. PHPUnit упрощает тестирование вашего приложения по одному. Помните, что целью модульных тестов является тестирование «блоков» кода, а не всего потока приложения. Если вы отслеживаете пути через ваше приложение, вы, вероятно, делаете это неправильно. Есть, конечно, исключения из правила, но это удобное для подражания. Давайте assertTrue с некоторых самых простых утверждений — 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 по умолчанию, который иногда может быть немного загадочным. «Неудачное утверждение, что это ложь» не поможет вам понять, что не так с кодом вашего приложения, но сообщение типа «не удалось создать пользователя» гораздо полезнее!

Mathemagic

Далее мы рассмотрим некоторые из более математически ориентированных утверждений. Они позволяют вам оценивать передаваемые переменные, чтобы увидеть, как они связаны. Давайте бросим их всех в один большой тест для иллюстрации:

 <?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 : у вас есть доступ к таким методам, как assertGreaterThan , assertLessThan , assertGreaterThanOrEqual , assertLessThanOrEqual и assertEquals .

Первый вызов assertEquals демонстрирует, как вы можете использовать метод из класса, который вы тестируете, для предоставления значений для тестирования.

Второй вызов assertEquals демонстрирует очень интересное использование этого утверждения. Он не только может использоваться для сравнения числовых значений, но также может сравнивать и другие вещи — например, объекты. Видите этот объект testTwo мы создали в тесте? Мы изменили свойство resetMe на false вместо true. The assertEquals рассматривает объекты в целом, чтобы увидеть, совпадают ли они. Из-за измененного значения resetMe эти два объекта не совпадают, поэтому утверждение не выполняется. assertEquals также есть несколько других хитростей: он может сравнивать объекты или массивы DOMDocument. Для получения более подробной информации о том, как работают эти сравнения, ознакомьтесь с соответствующим разделом документации PHPUnit .

Нанизывая тебя

В дополнение к математическим методам, которые входят в состав PHPUnit, есть также несколько, специально предназначенных для работы со строками. Имейте в виду, конечно, что сам PHP поставляется с широким набором методов обработки строк, которые вы можете использовать в своих тестах. Эти утверждения 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 немного интереснее: он проверяет файл, который вы передаете, и проверяет, равно ли его содержимое заданной строке. Это избавит вас от необходимости вызывать file_get_contents чтобы получить данные и проверить их самостоятельно. В приведенном выше примере тест прошел бы, если бы у вас был файл по указанному пути, содержащий строку "foo" .

Следующие два метода, assertStringMatchesFormat и assertStringMatchesFormatFile, позволяют вам быть немного более детализированным в вашей технике сопоставления. Они позволяют вам определять шаблоны для сопоставления, используя набор заполнителей, которые вы можете найти на сайте PHPUnit . Таким образом, вы можете проверить, соответствуют ли ваши значения заданному формату, который вы можете предоставить либо в виде строки, либо в виде файла.

Больше того же самого?

Теперь давайте посмотрим на два других полезных утверждения, которые, кажется, постоянно пригодятся мне в моем тестировании: 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 , но это строка. Преимущество assertNull в том, что в подобных случаях это будет более понятным и читаемым, чем попытка выполнить ту же проверку с помощью assertTrue или assertFalse .

Теперь для assertSame . Как вы, вероятно, знаете, PHP типизирован слабо, поэтому он будет считать число 1234 и строку "1234" равными. В результате assertEquals(1234, "1234") пройдет. Однако, будучи добросовестным программистом, вы очень осторожно относитесь к своим типам данных и хотите убедиться, что данная переменная точно соответствует другой, как по содержанию, так и по типу. Ну, вам повезло, потому что это именно то, что делает assertSame . Из-за этого утверждение в приведенном выше примере потерпит неудачу, поскольку 1234 не the same с "1234" .

Смешанная сумка

Я собираюсь коснуться некоторых из менее распространенных утверждений, доступных в 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" допустимым ключом массива для него. Мы дали нашему классу Testable массив с этим ключом, так что этот тест проходит с плавающими цветами. Затем, благодаря методу assertClassHasAttribute , мы можем проверить, есть ли у нашего класса заданное свойство. Опять же, это утверждение успешно проходит, потому что наш тестовый класс имеет свойство resetMe . Обратите внимание, однако, что это не для проверки, существует ли свойство для данного объекта, а только для того, чтобы увидеть, определено ли оно в классе.

assertFileExists — еще один простой assertFileExists — он просто просматривает локальную файловую систему, чтобы увидеть, есть ли файл там. Это следует тем же правилам, что и методы файловой системы PHP — если вы не можете получить к нему доступ, утверждение не будет выполнено. Наконец, есть assertInstanceOf , ярлык для определения, является ли объект, с которым вы работаете, экземпляром данного класса. Конечно, это ничем не отличается от assertTrue($this->_testable instanceof OtherClass) , но оно более лаконично и проще для чтения.

Для окончательного утверждения в этой статье мы рассмотрим одно из самых гибких:

 <?php public function testDoesMatchRegex() { $testString = $this->_testable->getTestString(); $this->assertRegExp('/[az]+/',$testString); } ?> 

Это простой пример (я уверен, что вы видели в свое время несколько регулярных выражений монстров), но он все понял.

В заключение

Я попытался перечислить большинство наиболее полезных утверждений в этой второй статье, чтобы вы могли лучше понять, что происходит, прежде чем начинать тестирование. Всегда есть несколько способов сделать что-то, но эти встроенные утверждения могут сэкономить вам много времени и хлопот. Помните, смысл написания тестов для вашего приложения заключается не просто в том, чтобы сказать, что у вас есть, или в том, чтобы достичь 100% покрытия кода. Написание хороших тестов позволяет вам работать над своим кодом без страха, что вы можете вводить новые ошибки (или повторно вводить старые). В конечном счете, лучшие тесты приведут к лучшему коду.