Как мне написать модульные тесты в PHP с процедурной кодовой базой? - PullRequest
29 голосов
/ 22 мая 2009

Я в основном убежден в преимуществах модульного тестирования, и я хотел бы начать применять эту концепцию к большой существующей базе кода, написанной на PHP. Менее 10% этого кода является объектно-ориентированным.

Я рассмотрел несколько структур модульного тестирования (PHPUnit, SimpleTest и phpt). Однако я не нашел примеров ни для одного из них, которые тестируют процедурный код. Какова лучшая среда для моей ситуации, и есть ли примеры модульного тестирования PHP с использованием не-ООП кода?

Ответы [ 3 ]

35 голосов
/ 18 июня 2009

Вы можете провести модульное тестирование процедурного PHP, без проблем. И вам определенно не повезло, если ваш код смешан с HTML.

На уровне приложения или приемочного теста ваш процедурный PHP, вероятно, зависит от значения суперглобальных переменных ($_POST, $_GET, $_COOKIE и т. Д.) Для определения поведения и заканчивается включением файла шаблона и выпуском вывода.

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

public function setUp()
{
    if (isset($_POST['foo'])) {
        unset($_POST['foo']);
    }
}

public function testSomeKindOfAcceptanceTest()
{
    $_POST['foo'] = 'bar';
    ob_start();
    include('fileToTest.php');
    $output = ob_get_flush();
    $this->assertContains($someExpectedString, $output);
}

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

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

Переменные передаются явно, как параметры или массивы параметров между функциями? Или переменные установлены в разных местах и ​​неявно переданы как глобальные переменные? Если это (хороший) явный случай, вы можете выполнить модульное тестирование функции путем (1) включения файла, содержащего функцию, затем (2) прямой подачи значений функциональных тестов и (3) захвата выходных данных и утверждения против них. Если вы используете глобальные переменные, вам просто нужно быть очень осторожным (как указано выше в примере с $ _POST), чтобы тщательно обнулить все глобальные переменные между тестами. Также особенно полезно сохранять очень маленькие тесты (5-10 строк, 1-2 утверждения) при работе с функцией, которая выталкивает и вытягивает множество глобальных переменных.

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

// assuming you required the file of interest at the top of the test file
public function testShouldConcatenateTwoStringsAndReturnResult()
{
  $stringOne = 'foo';
  $stringTwo = 'bar';
  $expectedOutput = 'foobar';
  $output = myCustomCatFunction($stringOne, $stringTwo);
  $this->assertEquals($expectedOutput, $output);
}

В плохом случае, когда ваш код работает с побочными эффектами и возвращает true или false, вы все равно можете довольно легко протестировать:

/* suppose your cat function stupidly 
 * overwrites the first parameter
 * with the result of concatenation, 
 * as an admittedly contrived example 
 */
public function testShouldConcatenateTwoStringsAndReturnTrue()
    {
      $stringOne = 'foo';
      $stringTwo = 'bar';
      $expectedOutput = 'foobar';
      $output = myCustomCatFunction($stringOne, $stringTwo);
      $this->assertTrue($output);
      $this->Equals($expectedOutput, $stringOne);
    }

Надеюсь, это поможет.

6 голосов
/ 23 мая 2009

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

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

require 'my-libraries.php';
class SomeTest extends SomeBaseTestFromSomeFramework {
    public function testSetup() {
        $this->assertTrue(true);
    }

    public function testMyFunction() {
        $output = my_function('foo',3);

        $this->assertEquals('expected output',$output);
    }
}

Этот трюк с основами PHP-кода заключается в том, что часто код вашей библиотеки будет мешать работе вашей тестовой среды, поскольку ваша кодовая база и тестовые среды будут иметь много кода, связанного с настройкой среды приложения в веб-браузере сеанс, общие глобальные переменные и т. д.). Ожидайте потратить некоторое время, чтобы добраться до точки, где вы можете включить свой библиотечный код и запустить простой тест (функция testSetup выше).

Если ваш код не имеет функций и представляет собой просто последовательность файлов PHP, которые выводят HTML-страницы, вам отчасти не повезло. Ваш код не может быть разделен на отдельные блоки, что означает, что модульное тестирование не принесет вам особой пользы. Вам лучше провести время на уровне «приемочных испытаний» с такими продуктами, как Selenium и Watir . Это позволит вам автоматизировать браузер, а затем проверять страницы на содержание в определенных местах / в формах.

1 голос
/ 22 мая 2009

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

require_once 'your_non_oop_file.php' # Contains fct_to_test()

И с помощью phpUnit вы определяете свою тестовую функцию:

testfct_to_test() {
   assertEquals( result_expected, fct_to_test(), 'Fail with fct_to_test' );
}
...