【问题标题】:Re-run last failed test in PHPUnit在 PHPUnit 中重新运行上次失败的测试
【发布时间】:2023-10-08 07:54:01
【问题描述】:

当其中一个测试失败时,您可以使用--stop-on-failure 标志来中断单元测试。

有什么方法可以快速告诉 PHPUnit 重新运行这个失败的测试,而不是手动提供完整路径?

【问题讨论】:

    标签: php unit-testing phpunit


    【解决方案1】:

    Since PHPUnit 7.3,您可以缓存测试结果,然后按缺陷对测试进行排序。

    在phpunit.xml中,启用cacheResults:

    <?xml version="1.0" encoding="UTF-8"?>
    <phpunit cacheResult="true"
             ...>
    

    如果您不想编辑 phpunit.xml,也可以使用 --cache-result 标志运行测试。

    当缓存结果时,PHPUnit 会在运行测试后创建一个.phpunit.result.cache 文件。确保将此文件添加到您的全局 gitignore 文件中。

    您可以像这样运行测试以先运行以前失败的测试:

    phpunit --order-by=defects --stop-on-failure
    

    【讨论】:

      【解决方案2】:

      看看--filter cli 选项。您可以在organisation docsCLI Docs 中找到示例。

      --过滤

      仅运行名称与给定模式匹配的测试。模式可以是单个测试的名称,也可以是匹配多个测试名称的正则表达式。

      假设您的运行 phpunit Tests/Tests/Stuff/ThatOneTestClassAgain::testThisWorks 失败:

      您的选择是:

      phpunit --filter ThatOneTestClassAgain

      phpunit --filter testThisWorks

      或大多数其他有意义的字符串

      【讨论】:

      • 如果您为测试设置了 phpunit.xml,我创建了一个简短的 bash 脚本,该脚本将使用您上次测试运行的 phpunit 日志记录来填充此 --filter 与包含失败测试的类。只需阅读上面的 cmets gist
      【解决方案3】:

      我发现实现它的方式相当简单,但需要实现日志记录。您将 phpunit 设置为记录到 json 文件。然后将 phpunit 命令更改为类似于:

      cd /home/vagrant/tests && php -d auto_prepend_file=./tests-prepend.php /usr/local/bin/phpunit
      

      它的作用是在执行 phpunit 之前自动添加一个 php 文件。这样我们就可以捕获 $argsv 并将所需的过滤命令自动提供给 phpunit。

      tests-prepend.php (一定要修改json日志的文件路径)

      <?php
      
      global $argv, $argc;
      if(empty($argv) === false) {
          // are we re-running?
          $has_rerun = false;
          foreach ($argv as $key => $value) {
              if($value === '--rerun-failures') {
                  $has_rerun = true;
                  unset($argv[$key]);
                  break;
              }
          }
          if($has_rerun === true) {
              // validate the path exists and if so then capture the json data.
              $path = realpath(dirname(__FILE__).'/../logs/report.json');
              if(is_file($path) === true) {
                  // special consideration taken here as phpunit does not store the report as a json array.
                  $data = json_decode('['.str_replace('}{'.PHP_EOL, '},{'.PHP_EOL, file_get_contents($path).']'), true);
                  $failed = array();
                  // capture the failures as well as errors but taking care not to capture skipped tests.
                  foreach ($data as $event) {
                      if($event['event'] === 'test') {
                          if($event['status'] === 'fail') {
                              $failed[] = array($event['test'], 'failed');
                          }
                          elseif($event['status'] === 'error' && $event['trace'][0]['function'] !== 'markTestIncomplete') {
                              $failed[] = array($event['test'], 'error\'d');
                          }
                      }
                  }
                  if(empty($failed) === true) {
                      echo 'There are no failed tests to re-run.'.PHP_EOL.PHP_EOL;
                      exit;
                  }
                  else{
                      echo '--------------------------------------------------------------------'.PHP_EOL;
                      echo 'Re-running the following tests: '.PHP_EOL;
                      foreach ($failed as $key => $test_data) {
                          echo ' - '.$test_data[0].' ('.$test_data[1].')'.PHP_EOL;
                          // important to escapre the namespace backslashes.
                          $failed[$key] = addslashes($test_data[0]);
                      }
                      echo '--------------------------------------------------------------------'.PHP_EOL.PHP_EOL;
                  }
                  $argv[] = '--filter';
                  $argv[] = '/('.implode('|', $failed).')/';
                  // important to update the globals in every location.
                  $_SERVER['argv'] = $GLOBALS['_SERVER']['argv'] = $GLOBALS['argv'] = $argv = array_values($argv);
                  $_SERVER['argc'] = $GLOBALS['_SERVER']['argc'] = $GLOBALS['argc'] = $argc = count($argv);
              }
              else{
                  echo 'The last run report log at '.$path.' does not exist so it is not possible to re-run the failed tests. Please re-run the test suite without the --rerun-failures command.'.PHP_EOL.PHP_EOL;
                  exit;
              }
          }
      }
      

      【讨论】: