Основы Unit тестирования в PHP с помощью PHPUnit

13-12-31 Php, Разное PHPUnit, Tests 1

Знакомая ситуация: вы разрабатываете приложение, решаете проблемы и, иногда, возникает ощущение, что вы ходите по кругу. Правите один баг и сразу появляется другой. Иногда это тот, который вы поправили 30 минут назад, иногда — новый. Отладка становится очень сложной, но есть хороший и простой выход из этой ситуации. Юнит тесты могут не только уменьшить боль при разработке, но и помогут писать код, который легче сопровождать и легче изменять.

Для понимания того, что такое модульное тестирование, необходимо определить понятие «модуля». Модуль (или unit) — это часть функционала приложения результат работы которой мы можем проверить (или протестировать). Модульное тестирование — это, собственно проверка, что данный модуль работает именно так как ожидается.Написав один раз тесты, всякий раз когда вы внесете изменения в код, вам останется только запустить тесты, для проверки, что всё правильно. Таким образом вы всегда будете уверены, что своими изменениями вы не сломаете систему.

Мифы о юнит тестировании

Не смотря на всю пользу юнит тестирования, не все разработчики им пользуются. Почему? Есть несколько ответов на этот вопрос, но все они — не слишком хорошие оправдания. Рассмотрим распространенные причины и попытаемся разобраться почему они не оправданы.

Написание тестов занимает слишком много времени

Самая распространенная причина: написание тестов — это долго. Конечно, некоторые среды разработки сгенерируют для вас набор простейших тестов, но написание качественных, нетривиальных тестов для вашего кода занимает время. Это нормальная практика — уделить некоторое врмя написанию юнит тестов, в результате это поможет сэкономить гораздо больше времени при сопровождении проекта. Если вы разрабатывали сайт, то наверняка, при добавлении нового функционала, вы тестировали его просто кликая по всевозможным ссылкам на сайте. Простой запуск набора тестов будет гораздо быстрее, нежели ручная проверка всех функций.

Не надо тестов — код и так работает!

Еще одно оправдание разработчиков: приложение работает — нет необходимости в тестировании. Они знают приложение и знают его слабые места, и смогут поправить все, что надо, иногда за несколько секунд. Но представьте, что для разработки приложения привлеки нового разработчика, который понятия не имеет как устроен код. Новичок может сделать какие-либо изменения, которые могут сломать что угодно. Юнит тесты помогут избежать подобных ситуаций.

Это неинтересно

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

Пример

Приступим к практике. В наших примерах мы будем использовать библиотеку PHPUnit. Самый простой способ установить PHPUnit — затянуть с PEAR канала.

pear config-set auto_discover 1
pear install pear.phpunit.de/PHPUnit

Если все пройдет хорошо, то все необходимые инструменты будут установлены. Если вы хотите установить PHPUnit вручную — инструкцию вы найдете здесь.

Первый тест

Используя PHPUnit, вы будете писать тестовые классы, содержащие тестовые методы и всё это должно удовлетворять следующим соглашениям:

  • В большинстве случаев вы будете наследовать класс PHPUnit_Framework_TestCase, что предоставит вам доступ к встроенным методам, например, setUp() и tearDown().
  • Имя тестирующего класса образуется добавлением слова Test к имени тестируемого класса. Например, вы тестируете класс RemoteConnect, значит имя тестирующего — RemoteConnectTest.
  • Имена тестирующих методов всегда должны начинаться с “test” (например, testDoesLikeWaffles()). Методы должны быть публичными. Вы можете использовать приватные методы в своих тестах, но они не будут запускаться как тесты через PHPUnit.
  • Тестирующие методы не принимают параметров. Вы должны писать тестирующие методы максимально независимыми и самодостаточными. иногда это неудобно, но вы получите более чистые и эффективные тесты.

Напишем небольшой класс для тестирования RemoteConnect.php:

<?php
class RemoteConnect
{
  public function connectToServer($serverName=null)
  {
    if($serverName==null){
      throw new Exception(“That's not a server name!”);
    }
    $fp = fsockopen($serverName,80);
    return ($fp) ? true : false;
  }

  public function returnSampleObject()
  {
    return $this;
  }
}
?>

Если мы хотим протестировать функционал для соединения с удаленным сервером, то мы должны написать подобный тест:

<?php

require_once('RemoteConnect.php');

class RemoteConnectTest extends PHPUnit_Framework_TestCase
{
  public function setUp(){ }
  public function tearDown(){ }

  public function testConnectionIsValid()
  {
    // проверка валидности соединения с сервером
    $connObj = new RemoteConnect();
    $serverName = 'www.google.com';
    $this->assertTrue($connObj->connectToServer($serverName) !== false);
  }
}
?>

Тестирующий класс наследует базовый PHPUnit класс, а значит и всю необходимую функциональность. Первые два метода — setUp и tearDown — пример этой встроенной функциональности. Это вспомогательные функции, которые являются частью каждого теста. Они выполняются до запуска всех тестов и после соответственно. Но сейчас нас интересует метод testConnectionIsValid. Этот метод создает объект типа RemoteConnect, и вызывает метод connectToServer.

Мы вызываем еще одну вспомогательную функцию assertTrue в нашем тесте. Эта функция определяет простейшее утверждение (assertion): она проверяет является ли переданное значение истиной. Другие вспомогательные функции выполняют проверки свойств объектов, существования файлов, наличия ключей в массиве, или соответствия регулярному выражению. В нашем случае мы хотим убедиться в правильности подключения к удаленному серверу, т.е. в том, что функция connectToServer возвращает true.

Запуск тестов

Запускаются тесты простым вызовом команды phpunit с указанием вашего php файла с тестами:

phpunit /path/to/tests/RemoteConnectTest.php

PHPUnit запускает все тесты и собирает немного статистики: успешно завершился тест или нет, количество тестов и утверждений (assertions) и выводит все это. Пример вывода:

PHPUnit 3.4 by Sebastian Bergmann
.
Time: 1 second
Tests: 1, Assertions: 1, Failures 0

Для каждого выполненного теста будет выведен результат: «.» если тест завершился успешно, “F” если тест не пройден, “I” если тест невозможно завершить и “S” если тест был пропущен.

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

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

AssertTrue/AssertFalse Проверка переданных значений на равенство true/false
AssertEquals Проверка переданных значений на равенство
AssertGreaterThan Сравнивает две переменные (есть так же LessThan, GreaterThanOrEqual, and LessThanOrEqual)
AssertContains Содержит ли переданная переменная заданное значение
AssertType Проверка типа переменной
AssertNull Проверка на равенство null
AssertFileExists Проверка существования файла
AssertRegExp Провка по регулярному выражению

Например есть функция, которая возвращает объект (returnSampleObject) и мы хотим убедиться в том, что возвращаемый объект будет нужного нам типа:

<?php

function testIsRightObject() {
  $connObj = new RemoteConnect();
  $returnedObject = $connObj->returnSampleObject();
  $this->assertType('remoteConnect', $returnedObject);
}

?>

Один тест — одно утверждение (assert)

Как и во всех областях разработки программного обеспечения, в тестировании есть лучшие практики. Одна из них — «один тест — одно утверждение» (one test, one assertion). Это правило поможет писать небольшие и легко читаемые тесты. Но иногда возникают мысли: «Раз уж мы здесь проверяем это, то и кое-что другое заодно проверим!». Например:

<?php

public function testIsMyString(){
  $string = “Mostly Harmless”;
  $this->assertGreaterThan(0,strlen($string));
  $this->assertContains(“42”,$string);
}

?>

Наш тест testIsMyString проводит два разных теста. Сначала тест на пустую строку (длина должна быть > 0), затем тест на содержание в строке подстроки «42». Но этот тест может провалиться как в первом так и во втором случае, а сообщение об ошибке в обоих случаях будет одинаковым. Поэтому стоит придерживаться принципа «один тест — одно утверждение».

Test-driven Development (разработка через тестирование)

Было бы нехорошо, говоря о тестировании не упомянуть о распространенной технике разработки — разработке через тестирование (test driven development). TDD — это техника, используемая при разработке программного обеспечения. Основная идея этой техники заключается в том, что сначала пишутся тесты и только после написания тестов пишется код приложения, который пройдет эти тесты.

На этом все! Следующая статья.

Хочешь получать статьи на почту?

Подпишись на обновления!
* Ваш email не будет разглашен/продан. Вы сможете отписаться в любое время.

1 Комментарий

  1. Дмитрий:

    Большое спасибо за материал

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *