Утверждать дилемму в модульном тестировании

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

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

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

private function assertTrue($expression) {
    $this->testCount++;
    assert($expression);
}

Это не работает, потому что любые переменные в выражении теперь выходят за рамки.

$var = true;
$this->assertTrue('$var == true'); // fails

Любые советы о том, как я могу использовать assert в своем модульном тестировании, имея возможность подсчитать количество фактических тестов?

Две идеи, которые я придумал, - это заставить пользователей считать себя

$this->testCount++;
assert('$foo');
$this->testCount++;
assert('$bar');

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

Ответ 1

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

class UnitTest {
    // controller that runs the tests
    public function runTests() {
        // the unit test is called, creating a new variable holder
        // and passing it to the unit test.
        $this->testAbc($this->newVarScope());
    }

    // keeps an active reference to the variable holder
    private $var_scope;

    // refreshes and returns the variable holder
    private function newVarScope() {
        $this->var_scope = new stdClass;
        return $this->var_scope;
    }

    // number of times $this->assert was called
    public $assert_count = 0;

    // our assert wrapper
    private function assert($__expr) {
        ++$this->assert_count;
        extract(get_object_vars($this->var_scope));
        assert($__expr);
    }

    // an example unit test
    private function testAbc($v) {
        $v->foo = true;
        $this->assert('$foo == true');
    }
}

Влияния на этот подход: все переменные, используемые в модульном тестировании, должны быть объявлены как $v->*, а не $*, тогда как переменные, записанные в инструкции assert, все еще записываются как $*. Во-вторых, предупреждение, исходящее от assert(), не сообщит номер строки, на который был вызван $this->assert().

Для большей согласованности вы можете переместить метод assert() в класс держателя переменных, так как вы могли бы думать о каждом unit test, работающем на тестовом поле, вместо того, чтобы иметь какой-то магический вызов assert.

Ответ 2

В PHPUnit все методы assert*() принимают дополнительный параметр $message, который вы можете использовать:

$this->assertTrue($var, 'Expected $var to be true.');

Если утверждение не выполняется, сообщение выводится с сообщением об ошибке в отчете после тестирования.

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

$this->assertTrue($var, 'Expected result of x to be true when y and z.');

Ответ 3

Немного наглый ответ здесь, но откройте vim и введите:

:%s/assert(\(.+\));/assert(\1) ? $assertSuccesses++ : $assertFailures++;/g

(В принципе, замените все вызовы assert() на assert() ? $success++ : $fail++;)

Более серьезно, предоставление механизма для подсчета тестов действительно является ответственностью, которая выходит за рамки функции assert(). Предположительно, вы хотите это для индикатора типа "X/Y tests". Вы должны делать это в рамках тестирования, записывая, что каждый тест, его результат и любая другая информация об отладке.

Ответ 4

Это не то, что (помните, что это произошло в скомпилированных языках). И семантика PHP не очень помогает в том, что вы пытаетесь сделать.

Но вы могли бы выполнить это с некоторыми синтаксическими накладными расходами:

 assert('$what == "ever"') and $your->assertCount();

Или даже:

 $this->assertCount(assert('...'));

Чтобы получить строку утверждения для преуспевающих условий, вы можете использовать debug_backtrace и некоторые эвристические извлечения строки.

Это также не выполняется слишком сильно (за исключением выполнения прекомпилятора/регулярного выражения над тестовыми скриптами). Но я бы посмотрел на это с высоты: не каждый чек может быть достаточно значительным, чтобы гарантировать запись. Таким образом, метод обёртки позволяет отказаться.

Ответ 5

Трудно дать ответ, не зная, как построена ваша фреймворк, но я дам ему шанс.

Вместо прямого вызова методов вашего модульного тестового класса (например, assertTrue()) вы можете использовать магический метод PHP __call(). Используя это, вы можете увеличить внутренний счетчик каждый раз, когда вызывается метод assertTrue(). Фактически, вы можете делать все, что хотите, каждый раз, когда вызывается какой-либо метод. Помните, что __call() вызывается, если вы пытаетесь вызвать метод, который не существует. Таким образом, вы должны были бы изменить все имена своих методов и называть их внутренне из __call(). Например, у вас будет метод под названием fAssertTrue(), но в модульном тестировании будет использоваться assertTrue(). Так как assertTrue() не определен, будет вызван метод __call(), и там вы вызовете fAssertTrue().

Ответ 6

Поскольку вы уже передаете выражение (которое может привести, исправьте меня, если я ошибаюсь, процитирую ад):

$this->assertTrue('$var == true'); // fails with asset($expression);

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

$this->assertTrue(function() use ($var) {
  return $var == true;
}); // succeeds with asset($expression());

Ответ 7

Простой:

$this->assertTrue($var == true);

(без кавычек!)

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

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