如何在使用单个测试项目时将所有QtTestLib单元测试的结果组合在一个文件中?

How to compose all QtTestLib unit tests' results in a single file while using a single test project?

本文关键字:组合 结果 文件 一个 单元测试 QtTestLib 单个 测试 项目      更新时间:2023-10-16

在我们的项目中,我们使用QtTestLib进行单元测试。原因是整个项目已经尽可能使用Qt,而且它是一个GUI应用程序,所以我们希望能够测试GUI接口。

我们的项目是由MSVC编译的,所以我们不想为每个测试都有一个单独的项目文件,因为这会打乱解决方案。因此,我们为所有测试创建了一个单独的项目。所有测试都应该在CIS(连续集成)上实现自动化,因此我们试图通过使用一些XSLT转换的XML格式的输出文件将测试插入Hudson。

但测试的输出似乎存在问题。如果您对所有测试使用单个main(),并且只向每个测试传输cmd行参数:

#include "MyFirstTest.h"
#include "MySecondTest.h"
int main(int argc, char **argv)
{
  int result = 0;
  MyFirstTest test1;
  result |= QTest::qExec(&test1, argc, argv);
  MySecondTest test2;
  result |= QTest::qExec(&test2, argc, argv);
  return result;
}

然后您将得到一个多次重写的结果文件。因此,如果您想使用输出文件(例如xml)在某种程度上实现自动化,您将只得到其中的最后一个结果。所有其他结果都将被覆盖。

我们已经尝试过这种方法,它不能让您使用像Hudson这样的连续集成系统。所以我的问题是:是否有机会将结果附加到一个输出文件中?当然,我们可以使用一些变通方法,比如通过修改参数的QTest::qExec()运行每个测试,将结果写入单独的文件,但这似乎不是最好的方法。理想情况下,我希望有一个单独的结果文件来与CIS一起使用。

使用此技巧,您可以将单个测试xml报告收集到临时缓冲区/文件中;全部来自单个测试二进制文件。让我们使用QProcess从一个二进制文件中收集单独的测试输出;测试使用修改后的参数调用自己。首先,我们引入了一个特殊的命令行参数,它适当地利用了子测试——所有子测试仍然在您的测试可执行文件中。为了方便起见,我们使用了接受QStringList的重载qExec函数。然后我们可以插入/移除我们的"-子测试";辩论更容易。

// Source code of "Test"
int
main( int argc, char** argv )
{
  int result = 0;
  // The trick is to remove that argument before qExec can see it; As qExec could be
  // picky about an unknown argument, we have to filter the helper 
  // argument (below called -subtest) from argc/argc; 
  QStringList args;
  for( int i=0; i < argc; i++ )
  {
     args << argv[i];
  }
  // Only call tests when -subtest argument is given; that will usually
  // only happen through callSubtestAndStoreStdout
  // find and filter our -subtest argument
  size_t pos = args.indexOf( "-subtest" );
  QString subtestName;
  if( (-1 != pos) && (pos + 1 < args.length()) )
  {
    subtestName = args.at( pos+1 );
    // remove our special arg, as qExec likely confuses them with test methods
    args.removeAt( pos );
    args.removeAt( pos );
    if( subtestName == "test1" )
    {
      MyFirstTest test1;
      result |= QTest::qExec(&test1, args);
    }
    if( subtestName == "test2" )
    {
      MySecondTest test2;
      result |= QTest::qExec(&test2, args);
    }
    return result;
}

然后,在您的脚本/命令行调用中:

./Test -subtest test1 -xml ... >test1.xml
./Test -subtest test2 -xml ... >test2.xml

现在,我们有了分离测试输出的方法。现在,我们可以继续使用QProcess的功能为您收集stdout。只需将这些行附加到主行即可。我们的想法是,如果没有明确的测试请求,再次调用我们的可执行文件,但使用我们的特殊参数:

bool
callSubtestAndStoreStdout(const String& subtestId, const String& fileNameTestXml, QStringList args)
{
   QProcess proc;
   args.pop_front();
   args.push_front( subtestId );
   args.push_front( "-subtest" );
   proc.setStandardOutputFile( fileNameTestXml );
   proc.start( "./Test", args );
   return proc.waitForFinished( 30000 ); // int msecs
}
int 
main( int argc, char** argv )
{
   .. copy code from main in box above..
   callSubtestAndStoreStdout("test1", "test1.xml", args);
   callSubtestAndStoreStdout("test2", "test2.xml", args);
   // ie. insert your code here to join the xml files to a single report
   return result;
}

然后在脚本/命令行中调用:

./Test -xml           # will generate test1.xml, test2.xml

事实上,希望未来的QTestLib版本能让这件事变得更容易。

我使用了这个肮脏的解决方法(与Jenkins一起使用):

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    int result = 0;
    freopen("MyAppTests_Test1.xml", "w", stdout);
    result |= QTest::qExec(new Test1, argc, argv);
    freopen("MyAppTests_Test2.xml",  "w", stdout);
    result |= QTest::qExec(new Test2, argc, argv);
    return result;
}

然后在Jenkins中,我添加了构建操作"execute shell":/path_to_MyAppTests-xml

并添加了构建后操作"发布xUnit测试结果报告"(QTestlib)。QTestlib模式:MyAppTests*.xml

由于我还不能在这里发表评论,我将在muenalan的回答后面添加它。要使其工作(至少在Qt5中),需要应用一些修复程序:

  1. callSubtestAndStoreStdout有3个错误。首先,在推送新的arg之前,必须从前面弹出第一个arg(这是arg 0)。其次,在启动进程之前,您必须重定向输出。第三,它必须返回一些值;)

    QProcess proc;
    args.pop_front();
    args.push_front(subtestId);
    args.push_front("-subtest");
    proc.setStandardOutputFile(fileNameTestXml);
    proc.start("sportSystemTest.exe", args);
    return proc.waitForFinished(30000);
    
  2. main也有一些(明显的)错误。主要是在if语句中:

    if ((-1 != pos) && (pos + 1 < args.length()))
    

因为最初的一个永远不会开火。

不管怎样,谢谢你的解决方案,它解决了我头疼的问题:)

在我看来,尝试构建一个可执行文件是个坏主意:如果你的一个测试崩溃,其他测试就不会再执行了。。。

运行具有多个测试用例的套件的另一种方法:

  • 在顶层创建一个细分项目
  • 为每个测试用例添加一个带有自己的.pro的子文件夹,并将其添加到subdirs项目中
  • 从顶层文件夹生成项目
  • 在顶层生成文件上运行make check。这将调用您的所有测试用例。您也可以传递参数,例如在MSVC环境中使用nmake -k check TESTARGS="-o result.xml,xml -v2 -maxwarnings 0"。如果一个测试失败,-k开关有助于继续
  • 以xunit-Jenkins插件为例,它允许my_build*result.xml这样的模式来搜索xml文件,这样您就可以解析所有生成的文件,而无需合并到一个文件中