Qt测试框架的扩展

来源:互联网 发布:手机淘宝怎么更新 编辑:程序博客网 时间:2024/04/29 21:54

本文转载自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.hclass VideoTest : public TestCase{    Q_OBJECTpublic:    DECLARE_TEST_CASE(VideoTest);    GetVideoRequestTest(const QString &name);};//videotest.cppINIT_TEST_CASE(VideoTest,"videoTest");VideoTest::VideoTest(const QString &name):    TestCase(name){}

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






原创粉丝点击