得益于和萱哥关于单元测试的聊天,让我开始想要了解Google的单元测试框架Google Test,(虽然以前也听勇哥提到过这个词,但是一直没有往心里去);以前基于C#反射特性Demo过一个C#的单元测试框架(http://www.cnblogs.com/salomon/archive/2012/05/30/2526746.html),所以更想明白Google Test测试框架的实现机制;这期间搞过一段Python,看Python文档时也看到相关的单元测试框架PyUnit,但没有深入研究,有时间深入研究一下。
自动化测试框架,主要目的就是自动化调用执行某些测试用例,将执行结果与目标结果进行比较,用以达到测试特定目标的目的。而测试用例的针对目标可能是函数接口,功能模块,UI等等。
自动化测试框架从功能上可以分为技术框架和执行框架。所谓技术框架一般针对于特定的测试目标,为了达到自动化测试而引入的技术或者方法,比如微软UI测试中的MSAA,UI Automation框架以及后续建立在UIAutomation上的POM/LFM(详见References中微软UI自动化测试技术演进相关链接)。而执行框架则侧重于对测试用例自动化执行的控制。这篇文章主要侧重于讨论执行框架,而自动化执行框架的设计中有以下几方面是必须考虑的:
1. 测试用例选择控制。
2. 自动化传递参数或者配置条件。
3. 测试用例执行结果比对。
4. 测试结果记录与分析。
测试用例选择控制
测试用例由多人编写,且针对模块不同,再加上历史原因,诸如此类限制情况要选择特定测试用例执行。C#中常见做法是利用反射机制建立特性标签,选择带有特定标签的测试用例执行,而C++常见做法是给测试用例加字符串参数,执行时通过字符串参数进行选择。
自动化传递参数或者配置条件
给被测试测试目标传递参数或配置文件是自动化测试框架必须有的特性,最可取的做法就是利用XML文档动态传参,好处是实现代码复用(针对同一个测试方法传递不同测试参数),以及可以动态自适应测试参数的变化(代码不改动的情况下调整参数或者配置信息适应测试条件的改变)。
测试用例执行结果比对
测试比对一般代码中实现测试结果比对,但这涉及到目标结果也就是期望结果的取得的问题,其实和上述传参和配置条件属于同一个范畴。
测试结果记录与分析
结果记录一般会将结果可视化输出到CMD窗口或者桌面窗口,XML文件,HTML文件,Excel文件等。不同测试框架实现有所不同,取决于需求。
Google Test
本来想Demo以下Google Test的实现,但是发现网上已经有人做过类似的事情,就把代码copy过来了,尊重作者意愿,将连接放至refereces。
Goolge Test架构主题设计很简单,将每一个测试用例封装到一个类TestCase的子类里。一个测试单元类UnitTest中将这些TestCase存入一个Vector的数据结构中,使用RunTestCases()方法(原著中使用Run这个方法名起的不是很好容易与TestCase中Run()方法相混,降低代码可读性)控制取出Vector中的测试用例来控制执行过程,结果比对等。
这个架构最出彩的地方是使用宏定义掩盖了繁杂的测试用例封装过程,可谓是神来之笔,详尽代码请参考连接,于作者博客下载。
class TestCase { public: TestCase(const char* case_name) : testcase_name(case_name){} // 执行测试案例的方法 virtual void Run() = 0; int nTestResult; // 测试案例的执行结果 const char* testcase_name; // 测试案例名称 };
class UnitTest { public: // 获取单例 static UnitTest* GetInstance(); // 注册测试案例 TestCase* RegisterTestCase(TestCase* testcase); // 执行单元测试 int RunTestCases(); TestCase* CurrentTestCase; // 记录当前执行的测试案例 int nTestResult; // 总的执行结果 int nPassed; // 通过案例数 int nFailed; // 失败案例数 protected: std::vector<TestCase*> testcases_; // 案例集合 };
// 以下这段宏定义掩盖了繁杂的测试用例封装过程
#define TESTCASE_NAME(testcase_name) \
testcase_name##_TEST //##的作用在于(token-pasting)符号连接操作符,即将宏定义的多个形参成一个实际参数名,在这里testcase_name_TEST
#define NANCY_TEST_(testcase_name) \
class TESTCASE_NAME(testcase_name) : public TestCase \
{ \
public: \
TESTCASE_NAME(testcase_name)(constchar* case_name) : TestCase(case_name){}; \
virtualvoid Run(); \
private: \
static TestCase*const testcase_; \
}; \
\
TestCase*const TESTCASE_NAME(testcase_name) \
::testcase_ = UnitTest::GetInstance()->RegisterTestCase( \
new TESTCASE_NAME(testcase_name)(#testcase_name)); \
//#号的作用是(stringizing)字符串化操作符。其作用是:将宏定义中的传入参数名转换成用一对双引号括起来参数名字符串。("testcase_name").
void TESTCASE_NAME(testcase_name)::Run()
/*注意Run()后边没有{},之所以这么做是宏定义将测试用例放入到Run的方法主体里。例如
NTEST(FooTest_PassDemo)
{
EXPECT_EQ(3, Foo(1, 2));
EXPECT_EQ(2, Foo(1, 1));
}
上述代码中EXPECT_EQ(2, Foo(1, 1));代码放入到Run的方法主体里。
*/
#define NTEST(testcase_name) \
NANCY_TEST_(testcase_name)
#define RUN_ALL_TESTS() \
UnitTest::GetInstance()->RunCases();
#define EXPECT_EQ(m, n) \
if (m != n) \
{ \
UnitTest::GetInstance()->CurrentTestCase->nTestResult = 0; \
std::cout << red << "Failed" << std::endl; \
std::cout << red << "Expect:" << m << std::endl; \
std::cout << red << "Actual:" << n << std::endl; \
}
例如以下测试Foo方法,NTEST(FooTest_PassDemo)就是创建一个名为FooTest_PassDemo_Test的子类,将宏定义的断言EXPECT_EQ()等放入Run()方法主题中。展开代码如下。
int Foo(int a, int b) { return a + b; } NTEST(FooTest_PassDemo) { EXPECT_EQ(3, Foo(1, 2)); EXPECT_EQ(2, Foo(1, 1)); } // 将以上宏定义展开等价于以下代码。 class FooTest_PassDemo_TEST : public TestCase { public: FooTest_PassDemo_TEST(const char* case_name) : TestCase(case_name){}; virtual void Run(); private: static TestCase* const testcase_; }; TestCase* const FooTest_PassDemo_TEST::testcase_ = UnitTest::GetInstance()->RegisterTestCase(new FooTest_PassDemo_TEST("FooTest_PassDemo")); void FooTest_PassDemo_TEST::Run() { if (3 != Foo(1, 2)) { UnitTest::GetInstance()->CurrentTestCase->nTestResult = 0; std::cout << red << "Failed" << std::endl; std::cout << red << "Expect:" << Foo(1, 2) << std::endl; std::cout << red << "Actual:" << 3 << std::endl; } if (2 != Foo(1, 1)) { UnitTest::GetInstance()->CurrentTestCase->nTestResult = 0; std::cout << red << "Failed" << std::endl; std::cout << red << "Expect:" << Foo(1, 1) << std::endl; std::cout << red << "Actual:" << 2 << std::endl; } }
int _tmain(int argc, _TCHAR* argv[])
{
//return UnitTest::GetInstance()->Run();
return RUN_ALL_TESTS();
}
坐在班车上,脑子里回想着这篇博客,突然间有种模糊的意识,COM的设计思想貌似也可以用来设计单元测试框架,没有成熟的思路,只是个想法,写下以备忘。
References:
微软UI自动化测试技术演进:
本文中事例代码来源:
http://www.cnblogs.com/coderzh/archive/2009/04/12/1434155.html
希望读者能提供不同看法或者建议。