有没有一种简单的方法可以反转测试或测试组?

Is there a simple way to invert a test or test group?

提问人:klutt 提问时间:9/2/2023 最后编辑:klutt 更新时间:9/5/2023 访问量:84

问:

假设您编写了一个失败的测试来利用错误。您希望将其推送到上游,然后创建一个关于修复代码以便测试通过的故事。但是,运行所有测试的管道会阻止这种情况。

我提出了一个解决方案:将测试添加到一个组,然后编辑以排除该组,使管道跳过该测试。解决 bug 后,只需从排除的组中删除测试即可。phpunit.xml

这里的一个问题是最后一步可能会被遗忘。无论测试是否通过,管道都将通过。

我认为解决这个问题的一个方法是,我们不希望它失败,而不是排除测试。有没有办法以这样一种方式进行配置,即属于特定组的所有测试都至少有一个断言失败?我查看了文档,但没有找到任何东西。注释也是如此。一个解决方案是,如果有一个像这样的注释,但我也没有找到类似的东西。phpunit.xml@expectToFail

也许反转测试不是正确的方法。但是要写一个我想做的事情的简短总结,而不假设任何最好的方法。

  1. 我应该能够编写一个失败的测试,该测试不得使管道失败。
  2. 如果测试测试的代码被修改,以便 (1) 中的测试通过而不对项目进行任何其他更改,则管道必须失败。
  3. 当 (2) 发生时,必须有一种非常简单的方法来反转它,以便管道在测试失败时必须失败,并且在测试通过时不得失败。

如果可能的话,我希望在不更改测试代码的情况下执行此操作,例如手动更改为 .assertFalseassertTrue

我可能会补充一点,它是 bitbucket 管道

澄清一下,这基本上是我想要的。假设我们有以下代码:

class Foo {
    // Function with obvious bug
    public function sum(a, b) {
        return a-b;
    }
}

显然,该函数有一个错误。所以我们写了一个单元测试:sum

class FooTest {

    /**
     *  @test
     */
    public function bugExploit() {
        $foo = new Foo();
        static::assertEqual(8, $foo->sum(3,5));
    }
}

现在我们提交这个测试并推送它。管道应在此推送后通过,前提是它之前通过。然后我们写一个关于解决这个错误的故事。

是时候下一位开发人员了。他们拿起了“修改 Foo:sum 以便测试 FooTest::bugExploit 通过”的故事。阅读此内容后,开发人员将更改为 ,而不进行其他任何更改。他们推送代码。现在我预计管道会失败。return a-breturn a+b

一个可行的解决方案是,如果存在一个名为注解的注解,第一个开发人员将其添加到文档注释中,以预期测试会失败,然后第二个开发人员删除该行。@fail

php 单元测试 phpunit tdd

评论

0赞 Alex Howansky 9/2/2023
“我应该能够编写一个失败的测试,它不能使管道失败。”这是违反直觉的。如果不希望管道失败,请不要提交失败的测试。试图人为地区分“实际上没有失败的失败测试”和“真正失败的失败测试”只会令人困惑和有问题。让处理即将到来的故事的开发人员编写失败的测试。或者提交到一个单独的分支,并在故事工单中引用该分支,以便开发人员可以在修复时合并。
0赞 klutt 9/3/2023
@AlexHowansky 一个问题是,如果推送的测试失败,推送就会被拒绝。
0赞 Alex Howansky 9/3/2023
“我想在测试失败时拒绝提交”和“我想提交失败的测试”是相互排斥的。选择一个。你已经明确地建立了一个系统,以便在出现问题时向你发出警告,但现在你正试图让这些笨拙的回旋来解决该系统,这样你就可以故意创建一个不知何故不会产生警告的问题。要么停止将测试与修复分开提交,要么允许在非主分支上提交失败的测试。
0赞 klutt 9/3/2023
@AlexHowansky 好吧,更准确地说。当 phpunit 报告测试失败时,推送被拒绝。
0赞 Alex Howansky 9/3/2023
是的,我明白了。您正在询问如何解决无法推动失败的测试的问题。我是说只需更改配置,以便您可以推送失败的测试。

答:

0赞 Alberto Fecchi 9/2/2023 #1

更新

不确定你要做什么会被认为是一个好的做法,顺便说一句,我会试着逐点回答你:

  1. 这种行为可以通过多种方式获得:使用 、 使用(和排除)等或使用 try/catch 语句。请注意,如果对多个断言使用单个 try/catch,则第一次失败将阻止整个测试。为了确保测试所有断言,您需要进行多个 try/catch 语句:markTestIncomplete()groups/levels/paths
function testPassWithFailures() {
    $failures = [];
    try {
        // First assertion
        $this->assertTrue(false);
    } catch(PHPUnit_Framework_ExpectationFailedException $e) {
        $failures[] = $e->getMessage();
    }
    try {
        // Second assertion
        $this->assertTrue(false);
    } catch(\PHPUnit\Framework\ExpectationFailedException $e) {
        $failures[] = $e->getMessage();
    }

    // ...

    if(!empty($failures))
    {
        // Here you can put your logic and decide if failures should make the test fail
        // or pass

        // Throw a new exception to make it fail
        throw new \PHPUnit\Framework\ExpectationFailedException (...);
    }
}

正如你所看到的,这是一种不太好的方法,但也许它可以帮助你思考不同的解决方案。

2/3. 这是最难的部分。“不对项目进行任何其他更改”是什么意思?您只想考虑对当前测试文件进行编辑吗?执行变基或合并时会发生什么情况?假设您已经知道此方法的问题,您可以尝试以下特定方法:

首先,创建一个基于文件后缀(例如。testsuiteTestDemoIncomplete.php)

<testsuites>
    <testsuite name="incomplete-tests">
        <directory suffix="Incomplete.php">test</directory>
    </testsuite>
</testsuites>

然后,您可以创建一个脚本来运行测试并执行 ,以了解哪些文件被修改,然后在 上运行。然后,您必须在脚本中加入一些逻辑,以便在测试通过时删除“不完整”后缀。这样,当您的测试首次通过时,其“不完整”标记将被删除,这将被视为标准测试。git diffphpunitincomplete-tests

您可以构建此脚本以在 Git Hooks 上运行或直接在管道中运行(但是,由于您将更改文件名,因此可能需要新的提交)。

我不是在向你推荐一个开箱即用的解决方案,我是试图给你一些关于你想要实现的目标的提示。在我看来,这是一种糟糕的方法,我会尝试找到一种解决方案,因为它适用于这种情况(此外,您可以解析脚本的输出以获取警告,然后使用此信息来阻止您的管道或简单地警告开发人员)。markTestIncomplete

旧答案

您可以将测试标记为未完成 (https://docs.phpunit.de/en/10.3/writing-tests-for-phpunit.html#incomplete-tests):

$this->markTestIncomplete(
    'This test has not been implemented yet.',
);

测试将通过,但输出中将生成警告:

...

There was 1 incomplete test:

1) WorkInProgressTest::testSomething
This test has not been implemented yet.

/path/to/tests/WorkInProgressTest.php:12

OK, but there were issues!
Tests: 1, Assertions: 1, Incomplete: 1.

评论

0赞 klutt 9/2/2023
我已经考虑过了,但它并没有实现我想做的事情,这并不奇怪。 用于不完整的测试。我的用例与已完成的测试有关。markTestIncomplete
0赞 Alberto Fecchi 9/3/2023
@klutt查看我更新的答案
0赞 klutt 9/3/2023
非常感谢您的奉献。然而,有一个建议。不要在答案中提问。在写答案之前,这就是评论的用途。但是要直接回答“没有对项目进行任何其他更改”?我指的是测试要测试的代码。我将编写一个示例并更新问题。
0赞 klutt 9/4/2023 #2

更新

这个答案不起作用。我正在研究解决方案。问题在于,只要至少一个标记的测试失败,管道就会通过。我希望它要求它们都失败才能通过。@failing

我设法解决了它。

首先,我配置为排除带有注释的测试,正如我在问题中已经提到的。我用:phpunit.xml@group failing

<groups>
    <exclude>
        <group>failing</group>
    </exclude>
</groups>

然后,我在管道中复制了测试命令。它在表格上.我的想法是简单地否定返回代码。我尝试了几种在终端中工作的方法,但我从未让 bitbucket 管道理解否定。docker run testuser ./vendor/bin/phpunit

因此,我编辑了该项目,并添加了以下内容:Makefile

failing-tests:
    ! docker run testuser ./vendor/bin/phpunit --group failing

请注意命令前面的 。这就是魔力。!

在那之后,我只是简单地添加了make failing-testsbitbucket-pipelines.yml

通过对表单的简单测试进行了尝试

class MyTest extends TestCase{
    /**
     * @test
     * @failing
     */
     public function aFailingTest(): void {
         static::assertTrue(false);
     }
}

由于注解,主测试套件忽略了此测试,并且管道通过了。当我更改为管道失败时。正是我想要的。assertTrue(true)