Как мне написать модульные тесты на PHP с процедурной кодовой базой?


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

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

Author: Travis Beale, 2009-05-22

3 answers

Вы можете модульно протестировать процедурный 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);
}

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

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

Передаются ли переменные явно, как параметры или массивы параметров между функциями? Или переменные заданы во многих разных местах и неявно передаются как глобальные? Если это (хорошее) явное в этом случае вы можете модульно протестировать функцию, (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);
    }

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

 35
Author: jared, 2009-06-18 17:07:26

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

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

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

 6
Author: Alan Storm, 2009-05-22 20:09:09

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

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' );
}
 1
Author: Luc M, 2009-05-22 18:58:53