Qt测试框架的扩展

本文转载自Alex's Blog http://www.zshalex.com/blog/?p=127


在Qt中编写单元测试的基本流程:

  • 创建一个类,并使其从QObject类继承下来。
  • 编写测试函数。
  • 使用QTEST_MAIN宏运行测试。
但是在以上的流程中存在一个问题,在实际的项目中,我们不可能对每个测试类都使用QTEST_MAIN宏去运行。因为如果这样做,就意味着每个测试类都会有一个与它相对应的可执行程序,这显然是不合理,既不方便我们编写单元测试,也不方便之后的数据统计。

基于以上的原因,我们必须要在一个程序中运行所有的测试类,那应该如何实现这一功能呢?

既然运行单个测试是通过使用QTEST_MAIN宏来实现,那么我们就首先来看一下QTEST_MAIN宏的内容:

#define QTEST_MAIN(TestObject) \
int main(int argc, char *argv[]) \
{ \
    QApplication app(argc,argv); \
    QTEST_DISABLE_KEYPAD_NAVIGATION \
    TestObject tc; \
    return QTest::qExec(&tc,argc,argv); \
} 
将宏展开后,它里面做的其实就是创建一个我们传入的测试类的实例,并调用QTest::qExec方法。既然如此,如果我们调用两次QTest::qExec,单元测试是否会运行两次呢?

仔细阅读Qt的文档,其中对QTest::qExec是这样描述的,如果运行以下的代码,那么MyFirstTestObject和MySecondTestObject都会被运行。

MyFirstTestObject test1;
QTest::qExec(&test1);

MySecondObject test2;
QTest::qExec(&test2); 
但是QTest::qExec方法是不可重入的,同一时间只有一个测试可以被运行,就算是在不同的线程运行也是不被允许的。

OK现在我们已经确定可以在一个程序中运行多个单元测试,但是我们不可能在每次创建了一个测试类后,都手动的在main函数中添加运行测试的代码。如果手动添加,我们就会在mian函数中看到如下代码:

MyFirstTestObject test1;
QTest::qExec(&test1);

MySecondObject test2;
QTest::qExec(&test2);

MySecondObject test3;
QTest::qExec(&test3);
              .
              .
              .

MySecondObject testn;
QTest::qExec(&testn); 
很显然这种方式也是非常不合理的,但是Qt的测试框架又不像CppUnit提供了TestSuite的功能,所以我们需要对Qt的测试框架进行扩展。

扩展测试框架的需求

我们所扩展的测试框架有一下几个核心的需求:

  • 测试类的编写不能有太大的变化,不能嵌入太多与测试无关的代码,如果能做到无缝接入那是最好的。
  • 必须能够自动运行所有的测试类。
  • 可以根据使用者的要求,运行指定的单元测试,或者排除指定的单元测试。

扩展测试框架的设计与实现

运行测试主要分为两个步骤,首先要创建测试类的实例,然后调用QTest::qExec运行测试。

  要想自动运行所有的测试,我们就需要一个列表来存放所有测试类的实例。另外由于要能自由的选择或排除某些单元测试,所以需要为每个测试类提供一个名称,以便之后的查找,所以可以使用Map去保存测试类,而不是列表。因此我们首先创建一个用于管理Map的类TestSuite。它的借口定义如下:

  class TestSuite
  {
  public:
      static TestSuite* instance();
      static void release();

      TestSuite();
      ~TestSuite();

      void add(const QString &name, QObject *obj);
      int runTest(int argc, char** argv);

      const int totalTest(){return m_TotalTest;}
      const int errorCount(){return m_ErrorCount;}
      const int succCount(){return m_SuccCount;}
      const int runCount(){return m_RunCount;}
      const QStringList &errorTest(){return m_ErrorTest;}
  protected:
      void loadArg(int argc, char** argv);
  private:
      static TestSuite *m_TestSuite;
      //单元测试类的map
      QMap<QString, QObject*> map;
      //单元测试的总数
      int m_TotalTest;
      //错误单元测试的个数
      int m_ErrorCount;
      //正确单元测试的个数
      int m_SuccCount;
      //运行的单元测试个数
      int m_RunCount;
      //错误单元测试的名称列表
      QStringList m_ErrorTest;
      //需要执行的单元测试的名称列表
      QStringList m_IncludeTest;
      //不需要执行的单元测试的名称列表
      QStringList m_ExcludeTest;
      //最终需要运行的单元测试的名称列表
      QStringList m_RunTest;
      //getopt的配置
      static const char *optString;
      static const struct option longOpts[];
  };

其中最重要的两个方法是add与runTest。add会将指定的测试类的实例添加到map中,键值为name。runTest则会根据命令行参数运行指定的测试类。而命令行参数的分析使用getopt来分析,并在loadArg方法中实现。

  另外TestSuite类使用了单体模式,可以通过TestSuite::instance()直接获取TestSuite类的实例。有了TestSuite类后,main函数中就只需要执行如下的代码,就能够自动运行所有的单元测试。

  int main(int argc, char** argv)
  {
      QApplication app(argc, argv);
      int result = TestSuite::instance()->runTest(argc,argv) ;
      TestSuite::instance()->release();
      return result;
  } 

现在我们已经可以运行所有在map中的测试类,但是我们又要如何将测试类的实例添加map中呢?在这里我们参考了CppUnit的实现,在每个测试类中添加一个静态的指向当前类对象的指针,并在静态变量初始化时调用TestSuite类的add方法。

  因此我们实现了一个TestCase类,并声明了两个宏,分别是DECLARE_TEST_CASE以及INIT_TEST_CASE。他们的实现如下:

  //type表示类名
  #define DECLARE_TEST_CASE(type) \
      static type *tc;

  //type表示类名,name表示单元测试的名称
  #define INIT_TEST_CASE(type,name) \
      type *type::tc = new type(name);

  class TestCase : public QObject
  {
      Q_OBJECT
  public:
      explicit TestCase(const QString &name)
      {
          TestSuite::instance()->add(name,this);
      }
  }; 
当我们要添加一个单元测试时,首先从TestCase类下继承一个类,例如:VideoTest。然后在头文件的public段中使用DECLARE_TEST_CASE宏去声明一个静态的变量,然后在cpp文件中使用INIT_TEST_CASE宏初始化静态变量。

//videotest.h
class VideoTest : public TestCase
{
    Q_OBJECT
public:
    DECLARE_TEST_CASE(VideoTest);
    GetVideoRequestTest(const QString &name);
};

//videotest.cpp
INIT_TEST_CASE(VideoTest,"videoTest");

VideoTest::VideoTest(const QString &name):
    TestCase(name)
{
}

这样我们创建的测试类就会自动添加到TestSuite的map中,之后只需要像之前一样编写测试函数就可以了。






你可能感兴趣的:(框架,测试,单元测试,Class,扩展,qt)