Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

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. Они позволят вам автоматизировать браузер, а затем проверить страницы для контента как конкретные местоположения/в формах.

Ответ 3

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

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