Статьи

Руководство для начинающих по модульному тестированию: создание тестируемого плагина

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

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


Когда дело доходит до тестирования, обычно есть два способа сделать это:

  • Напишите свои тесты, а затем напишите код, чтобы ваши тесты прошли
  • Написать свой код, а затем написать тесты, которые проходят

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

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

Имея это в виду, мы будем строить с этой простой методологией:

  • Напишите тест и запустите его. Это очевидно потерпит неудачу.
  • Напишите код, который пытается заставить тест пройти.
  • Если тест пройден, мы переходим к следующей функции; в противном случае мы повторяем процесс, пока он не пройдет.

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


На этом этапе пришло время начать писать некоторый код; однако, в отличие от большинства проектов, мы пока не будем вдаваться в специфичный для WordPress код Вместо этого мы собираемся заглушить наш класс модульного тестирования. Если вы структурировали каталог своего плагина на основе того, что мы опубликовали в первом посте, или как мы настроили его на GitHub , то у вас должен быть файл hello_reader_tests.php, расположенный в вашем каталоге tests / wordpress-tests . Конечно, вам не обязательно следить за этой организацией, но это поможет нам в процессе реализации проекта.

Давайте заглушим класс юнит-теста:

1
2
3
4
5
require_once( ‘../../plugin.php’ );
 
class Hello_Reader_Tests extends WP_UnitTestCase {
 
} // end class

Теперь попробуйте запустить тест, используя терминал из PHP-модуля. Предполагая, что вы используете модуль PHP из локальной установки MAMP, вы должны ввести:

$ /Applications/MAMP/bin/php/php5.3.6/bin/phpunit ./hello_reader_tests.php

На этом этапе вы должны увидеть сбой:

Неудачные тесты

Это хорошо! Это означает, что PHPUnit установлен и работает, и ваша платформа WordPress Testing готова к работе. Тест не удался просто потому, что мы не написали никаких тестов. Давайте начнем делать это.

Во-первых, давайте напишем тест, чтобы убедиться, что наш плагин инициализирован, создан и готов к тестированию. Напомним ранее в первой статье, что мы сохранили ссылку на экземпляр Hello Reader в массиве PHP $GLOBALS . Вот как мы будем обращаться к этому экземпляру, используя среду тестирования. Давайте обновим наш модульный тест, чтобы он выглядел так:

Обратите внимание, что ради места, я буду опускать комментарии кода, но полностью прокомментированный плагин и тесты будут доступны на GitHub.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
require_once( ‘../../plugin.php’ );
 
class Hello_Reader_Tests extends WP_UnitTestCase {
 
    private $plugin;
 
    function setUp() {
         
        parent::setUp();
        $this->plugin = $GLOBALS[‘hello-reader’];
     
    } // end setup
     
    function testPluginInitialization() {
        $this->assertFalse( null == $this->plugin );
    } // end testPluginInitialization
 
} // end class

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

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

Проходящие тесты

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

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

Этот конкретный тест будет смотреть, чтобы увидеть, добавили ли мы определенный набор текста к первой части поста:

1
2
3
function testAddWelcomeMessage() {
    $this->assertEquals( ‘TEST CONTENT’, $this->plugin->add_welcome_message( ‘This is example post content. This simulates that WordPress would return when viewing a blog post.’ ), ‘add_welcome_message() appends welcome message to the post content.’ );
} // end testAddWelcomeMessage

Если вы запустите тест в точности так, как он есть, он даже не потерпит неудачу — вместо этого PHPUnit вернет фатальную ошибку, потому что метод не определен в плагине. Итак, давайте добавим это сейчас. Обновите плагин, чтобы он выглядел так:

01
02
03
04
05
06
07
08
09
10
11
class Hello_Reader {
     
    function __construct() {
        add_filter( ‘the_content’, array( &$this, ‘add_welcome_message’ ) );
    } // end constructor
   
    public function add_welcome_message( $content ) {
         
    } // end add_welcome_message
 
} // end class

Теперь попытайтесь запустить тест. Тест не будет бомбить, но вы должны увидеть сбой и четкое сообщение о том, почему тест не прошел:

Итак, в соответствии с нашей методологией, мы хотим, чтобы этот тест прошел. Чтобы сделать это, нам нужно убедиться, что содержимое публикации содержит строку текста — в данном случае, « TEST CONTENT », чтобы оно прошло. Итак, давайте попробуем это. Обновите соответствующую функцию в плагине, чтобы добавить строку перед содержимым:

1
2
3
public function add_welcome_message( $content ) {
    return ‘TEST CONTENT’ .
} // end add_welcome_message

И снова мы снова запускаем тест, чтобы увидеть, что он не прошел. Если вы заметили наш тест, это потому, что он смотрит, что наш контент равен строке ‘ TEST CONTENT ‘. Вместо этого нам нужно убедиться, что строка начинается с содержимого. Это означает, что нам нужно обновить наш тест. К счастью, PHPUnit имеет функцию assertContains . Итак, давайте обновим наш код, чтобы использовать его:

1
2
3
function testAddWelcomeMessage() {
    $this->assertContains( ‘TEST CONTENT’, $this->plugin->add_welcome_message( ‘This is example post content. This simulates that WordPress would return when viewing a blog post.’ ), ‘add_welcome_message() appends welcome message to the post content.’ );
} // end testAddWelcomeMessage

Еще раз, повторите тест, и вы должны увидеть, что тест теперь проходит. Потрясающие! Теперь нам нужно написать индивидуальные сообщения для людей из Твиттера и людей из Google.

Существует несколько способов проверить, как пользователь попал на данную страницу. Иногда мы можем проверить значения в массиве $_GET , иногда мы можем $_SERVER массив $_SERVER , а иногда мы можем проверить сеанс пользователя. Для целей этого примера мы будем искать ‘twitter.com’ в $_SERVER['HTTP_REQUEST'] . Я говорю это только для того, чтобы вы, ребята, могли следить за тем, что мы делаем в коде.

Таким образом, в общем случае add_welcome_message должен проверить, add_welcome_message ли запрос из Twitter, а затем соответствующим образом настроить сообщение. Поскольку мы занимаемся тестированием каждого компонента функциональности, мы можем написать функцию, которая может оценивать, поступает ли запрос из Twitter. Итак, давайте напишем новый тест:

В плагине:

1
2
3
public function is_from_twitter() {
     
} // end is_from_twitter

В тесте:

1
2
3
4
5
6
function testIsComingFromTwitter() {
 
    $_SERVER[‘HTTP_REFERER’] = ‘http://twitter.com’;
    $this->assertTrue( $this->plugin->is_from_twitter(), ‘is_from_twitter() will return true when the referring site is Twitter.’ );
     
} // end testIsComingFromTwitter

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

1
2
3
public function is_from_twitter() {
    return strpos( $_SERVER[‘HTTP_REFERER’], ‘twitter.com’ ) > 0;
} // end is_from_twitter

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

1
2
3
4
5
6
7
function testIsNotComingFromTwitter() {
 
    // Spoofing the HTTP_REFERER for purposes of this test and the companion blog post
    $_SERVER[‘HTTP_REFERER’] = ‘http://facebook.com’;
    $this->assertFalse( $this->plugin->is_from_twitter(), ‘is_from_twitter() will return true when the referring site is Twitter.’ );
     
} // end testIsNotComingFromTwitter

Обратите внимание, что мы обновили HTTP_REFERER и изменили assertTrue на assertFalse . Разрешите все остальное правильно, запустите тесты и они должны пройти.

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

Сначала мы отключаем вспомогательную функцию в плагине:

1
2
3
public function is_from_google() {
     
} // end is_from_google

Тогда мы заглушаем тест:

1
2
3
4
5
6
function testIsComingFromGoogle() {
 
    $_SERVER[‘HTTP_REFERER’] = ‘http://google.com’;
    $this->assertTrue( $this->plugin->is_from_google(), ‘is_from_google() will return true when the referring site is Google.’ );
     
} // end testIsComingFromGoogle

Выполнение теста в том виде, в каком оно сейчас, приведет к сбою. Итак, давайте реализуем is_from_google() :

1
2
3
public function is_from_google() {
    return strpos( $_SERVER[‘HTTP_REFERER’], ‘google.com’ ) > 0;
} // end is_from_twitter

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

1
2
3
4
5
6
7
function testIsNotComingFromGoogle() {
 
    // Spoofing the HTTP_REFERER for purposes of this test and the companion blog post
    $_SERVER[‘HTTP_REFERER’] = ‘http://facebook.com’;
    $this->assertFalse( $this->plugin->is_from_google(), ‘is_from_google() will return true when the referring site is Google.’ );
     
} // end testIsNotComingFromGoogle

Наконец, запустите ваши тесты. Если все остальное правильно, вы должны пройти шесть проходных тестов.

На данный момент у нас есть все, что нам нужно, чтобы начать отображать настраиваемые приветственные сообщения для наших пользователей. Единственное, что нам нужно, чтобы провести рефакторинг нашего первоначального теста, который проверяет «TEST CONTENT». Теперь нам нужно ввести тесты для следующих случаев:

  • Когда пользователь приходит из Twitter, мы говорим «Добро пожаловать из Twitter!»
  • Когда пользователь приходит из Google, мы говорим «Добро пожаловать из Google!»
  • Когда пользователь приходит откуда-либо еще, мы не будем ничего предварять.

Итак, давайте удалим тест, который мы создали ранее testAddWelcomeMessage вместо добавления трех новых тестов.

Сначала мы добавим тест, который проверяет приветственное сообщение Twitter.

В плагине мы уменьшим add_welcome_message до этого:

1
2
3
public function add_welcome_message( $content ) {
    return $content;
} // end add_welcome_message

И мы сначала добавим тест Twitter:

1
2
3
4
5
6
7
function testDisplayTwitterWelcome() {
     
    // Spoof the HTTP_REFERER for Twitter
    $_SERVER[‘HTTP_REFERER’] = ‘http://twitter.com’;
    $this->assertContains( ‘Welcome from Twitter!’, $this->plugin->add_welcome_message( ‘This is example post content. This simulates that WordPress would return when viewing a blog post.’ ), ‘add_welcome_message() appends welcome message to the post content.’ );
     
} // end testDisplayTwitterWelcome

На данный момент, это старая шляпа, верно? Запустите его, тест не пройден. add_welcome_message чтобы он выглядел так:

1
2
3
4
5
6
7
8
9
public function add_welcome_message( $content ) {
 
    if( $this->is_from_twitter() ) {
        $content = ‘Welcome from Twitter!’
    } // end if
     
    return $content;
     
} // end add_welcome_message

Запустите это снова, и это пройдет. Далее идет тест Google:

1
2
3
4
5
6
7
function testDisplayGoogleWelcome() {
     
    // Spoof the HTTP_REFERER for Google
    $_SERVER[‘HTTP_REFERER’] = ‘http://google.com’;
    $this->assertContains( ‘Welcome from Google!’, $this->plugin->add_welcome_message( ‘This is example post content. This simulates that WordPress would return when viewing a blog post.’ ), ‘add_welcome_message() appends welcome message to the post content.’ );
         
} // end testDisplayGoogleWelcome

Запустите тест, если он не add_welcome_message , затем обновите add_welcome_message в плагине, чтобы он содержал проверку, используя вспомогательную функцию, которую мы написали ранее:

01
02
03
04
05
06
07
08
09
10
11
public function add_welcome_message( $content ) {
     
    if( $this->is_from_twitter() ) {
        $content = ‘Welcome from Twitter!’
    } else if( $this->is_from_google() ) {
        $content = ‘Welcome from Google!’
    } // end if
     
    return $content;
     
} // end add_welcome_message

На этом этапе у вас должен быть полнофункциональный плагин с семью проходными юнит-тестами!


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

Наконец, вы можете найти этот плагин, тесты WordPress и модульные тесты Hello Reader, полностью прокомментированные на GitHub .