单元测试类中的断言困境

发布于 2024-11-09 10:07:23 字数 731 浏览 2 评论 0原文

我想在我的单元测试框架中使用 PHP 的断言函数。它的优点是能够在错误消息中看到正在计算的表达式(包括注释)。

问题是每个包含测试的方法可能有多个断言语句,我想跟踪已经运行了多少个实际的断言语句。断言没有给我一种方法来计算它已经运行了多少次,只计算它失败了多少次(在失败回调中)。

我尝试将assert语句抽象成一个函数,这样我就可以添加计数机制。

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

然而,这不起作用,因为表达式内的任何变量现在都超出了范围。

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

关于如何在单元测试中使用断言同时能够计算实际测试的数量有什么建议吗?

我提出的两个想法是让用户自己计数

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

或让用户在每个测试方法中只放置一个断言(然后我可以计算运行的方法的数量)。但这些解决方案都不是非常可执行,并且使编码变得更加困难。关于如何实现这一目标有什么想法吗?或者我应该从我的测试框架中删除assert()?

I would like to use PHP's assert function in my unit testing framework. It has the advantage of being able to see the expression being evaluated (including comments) within the error message.

The problem is that each method containing tests may have more than one assert statement, and I would like to keep track of how many actual assert statements have been run. assert does not give me a way to count how many times it has been run, only how many times it has failed (within the failure callback).

I tried to abstract the assert statement into a function so that I can add a counting mechanism.

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

This does not work however because any variables within the expression are now out of scope.

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

Any advice on how I can use assert in my unit testing while being able to count the number of actual tests?

The two ideas I have come up with are to make users count themselves

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

or make users put only one assert in each test method (I could then count the number of methods run). but neither of these solutions is very enforcable, and make coding more difficult. Any ideas on how to accomplish this? Or should I just strip assert() from my testing framework?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

浪荡不羁 2024-11-16 10:07:23

在 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.');

In PHPUnit, all of the assert*() methods take an additional $message parameter, which you can take advantage of:

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

If the assertion fails, the message is output with the failure in the post-test report.

This is more useful generally than outputting the actual expression because then you can comment on the significance of the failure:

$this->assertTrue($var, 'Expected result of x to be true when y and z.');
So尛奶瓶 2024-11-16 10:07:23

这里的答案有点厚颜无耻,但是打开 vim 并输入:(

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

原则上,用 assert() 替换所有 assert() 调用? $success++ : $fail++;

更严重的是,提供一种对测试进行计数的机制确实超出了 assert() 函数的范围。大概您希望将其用于“X/Y 测试成功”类型指示器。您应该在测试框架中执行此操作,记录每个测试的内容、其结果以及任何其他调试信息。

A bit of a cheeky answer here, but open vim and type:

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

(In principle, replace all assert() calls with assert() ? $success++ : $fail++;)

More seriously, providing a mechanism to count tests is really a responsibility a bit beyond the scope of the assert() function. Presumably you want this for an "X/Y tests succeeded" type indicator. You should be doing this in a testing framework, recording what each test is, its outcome and any other debug information.

∝单色的世界 2024-11-16 10:07:23

您受到以下事实的限制:assert() 必须在您正在测试的变量所在的同一范围内调用。据我所知,剩下的解决方案需要额外的代码,在运行时修改源代码(预处理),或者在 C 级别扩展 PHP 的解决方案。这是我提出的解决方案,涉及额外的代码。

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() 发出的警告不会报告调用 $this->assert() 的行号。

为了获得更高的一致性,您可以将assert()方法移至变量持有者类,这样您就可以考虑在测试台上运行的每个单元测试,而不是进行某种神奇的断言调用。

You are restricted by the fact assert() must be called in the same scope the variables you are testing lie. That leaves -- as far as I can tell -- solutions that require extra code, modify the source before runtime (preprocessing), or a solution that extends PHP at the C-level. This is my proposed solution that involves extra code.

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

Downfalls to this approach: all variables used in unit testing must be declared as $v->* rather than $*, whereas variables written in the assert statement are still written as $*. Secondly, the warning emitted by assert() will not report the line number at which $this->assert() was called.

For more consistency you could move the assert() method to the variable holder class, as that way you could think about each unit test operating on a test bed, rather than having some sort of magical assert call.

风吹雨成花 2024-11-16 10:07:23

这不是的东西的目的是(记住它起源于编译语言)。
PHP 的语义对您想要做的事情也没有多大帮助。

但是您仍然可以通过一些语法开销来完成它:

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

甚至:

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

要获取成功条件的断言字符串,您只能使用 < code>debug_backtrace 和一些启发式字符串提取。

这也没有太多强制执行(缺少在测试脚本上运行预编译器/正则表达式)。但我会从好的方面来看待这个问题:并不是每一项检查都足够重要,值得记录。因此,包装方法允许选择退出。

That's not something which is intended to do (remember it originated in compiled langs).
And PHPs semantics do not help much with what you are trying to do either.

But you could accomplish it with some syntactic overhead still:

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

Or even:

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

To get the assertion string for succeeded conditions still, you could only utilize debug_backtrace and some heuristic string extraction.

This is not enforced much either (short of running a precompiler/regex over the test scripts). But I would look at this from the upside: not every check might be significant enough to warrant recording. A wrapper method thus allows opting out.

仅此而已 2024-11-16 10:07:23

如果不知道你的框架是如何构建的,很难给出答案,但我会尝试一下。

您可以使用 PHP 的神奇方法 __call()。使用这个,你可以在每次调用assertTrue()方法时增加一个内部计数器。实际上,每次调用任何方法时,您都可以做任何您想做的事情。
请记住,如果您尝试调用不存在的方法,则会调用 __call()。因此,您必须更改所有方法名称,并从 __call() 内部调用它们。例如,您有一个名为 fAssertTrue() 的方法,但单元测试类将使用 assertTrue()。因此,由于未定义assertTrue(),因此将调用__call()方法,然后调用fAssertTrue()。

It's hard to give an answer without knowing how your framework has been built, but I'll give it a shot.

Instead of directly call the methods of your unit testing class ( methods like assertTrue() ), you could use the magic method of PHP __call(). Using this, you could increase an internal counter everytime assertTrue() method is called. Actually, you can do whatever you want, every time any method is called.
Remember that __call() is invoked if you try to call a method that does not exist. So you would've to change all your methods names, and call them internally from __call(). For instance, you'd have a method called fAssertTrue(), but the unit testing class would use assertTrue(). So since assertTrue() is not defined, __call() method would be invoked, and there you would call fAssertTrue().

素年丶 2024-11-16 10:07:23

既然你已经传递了表达式(这可能会导致引用地狱,如果我错了,请纠正我):

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

为什么不添加一点额外的复杂性,并通过使用闭包来避免引用地狱呢?

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

Since you're passing the expression already (which might lead, correct me if I'm wrong, to quoting hell):

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

Why not add a tiny extra layer of complexity, and avoid the quoting hell, by using a closure instead?

$this->assertTrue(function() use ($var) {
  return $var == true;
}); // succeeds with asset($expression());
随心而道 2024-11-16 10:07:23

简单:(

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

不带引号!)

它将在调用者空间中进行评估,因此assertTrue() 将仅传递 false 或 true。

正如其他人指出的那样,这可能不是最好的测试方法,但这完全是另一个问题......;)

Simple:

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

(without quotes!)

It will be evaluated in caller space, so assertTrue() will be passed just false or true.

As others have pointed out, this might not be the best way of testing, but that's another question entirely... ;)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文