A Test FrameWork Affected by Linking 一个受链接顺序影响的测试框架

最近看到一个好用的测试框架,自己做了个实验,于是记录下来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//framework.hpp
#include <string>
#include <vector>
namespace framework {
class TestBase{
public:
std::string test_name;
TestBase(std::string m);
void addTest();
virtual void runTest()=0;
};

extern std::vector<TestBase *> tests;
void runAllTests();
}

//framework.cpp
#include <iostream>
#include <string>
#include <vector>
#include "framework.hpp"
namespace framework {
std::vector<TestBase *> tests;
TestBase::TestBase(std::string m):test_name(m){
addTest();
}
void TestBase::addTest(){
tests.push_back(this);
}

void runAllTests(){
for(auto testPtr = tests.begin(); testPtr != tests.end(); testPtr++ )
(*testPtr)->runTest();
}

}

//main.cpp
#include <iostream>
#include <vector>
#include "framework.hpp"
int main(){
framework::runAllTests();
}

//test_a.cpp
#include <iostream>
#include "framework.hpp"
class Test_A: public framework::TestBase {
public:
Test_A():framework::TestBase("Test_A"){}
void runTest() { std::cout<<"Running "<< test_name << std::endl; }

};
Test_A test_a_obj;

//test_b.cpp
#include <iostream>
#include "framework.hpp"
class Test_B: public framework::TestBase {
public:
Test_B():framework::TestBase("Test_B"){}
void runTest() { std::cout<<"Running "<< test_name << std::endl; }

};
Test_B test_b_obj;


//Makefile
FILES = main.cpp
FILES += framework.cpp
FILES += test_a.cpp
FILES += test_b.cpp
EXE = test
FLAGS += -std=c++11
all:
g++ $(FILES) -o $(EXE) $(FLAGS)

//编译运行结果如下
$ make
g++ main.cpp framework.cpp test_a.cpp test_b.cpp -o test -std=c++11
$ ./test
Running Test_A
Running Test_B

讲解
framework.hpp和framework.cpp分别是框架的定义和实现, main.cpp是主程序入口,test_a.cpp是其中一个测试用例。
框架的设计思路就是提供一个TestBase类,而测试用例只要继承并定义自己的runTest()实现即可。那么之后要加新测试,只要创建独立的同test_a.cpp的文件,然后加入到Makefile即可。这个跟所谓的工厂模式没有什么区别。

框架中定义了一个全局的std::vector tests来保存每一个测试用例,然后main会遍历运行。
接下来就是这个设计里最最有意思的地方, 测试用例是怎么推入tests容器的呢? —— 每个测试用例“自觉”进入,先来先运行
实现方法是每一个用例继承TestBase并实例化的过程中,通过构造函数自动push“自己”到tests里去,而进入tests的先后顺序,跟代码没关系,跟链接顺序有关系!

进一步分析
下面说明如何跟链接有关系。
首先把上面make的过程拆解为编译和链接,然后再替换test_a.o test_b.o的链接顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//编译
$ g++ -c main.cpp -std=c++11
$ g++ -c framework.cpp -std=c++11
$ g++ -c test_a.cpp -std=c++11
$ g++ -c test_b.cpp -std=c++11
//链接
$ g++ main.o framework.o test_a.o test_b.o -o test -std=c++11
//执行
$ ./test
Running Test_A
Running Test_B
//替换链接顺序
$ g++ main.o framework.o test_b.o test_a.o -o test -std=c++11
//执行
$ ./test
Running Test_B
Running Test_A

我如何理解这个现象: 编译过程只是进行了语法检查而不执行,链接过程是进行了执行的检查,可以认为虚拟地跑通了一遍程序。 这个跑通的过程,安排好了变量内存管理,函数的入栈出栈等等,安排好了就形成了可执行文件。 等你真正运行的时候,就按链接过程的顺序来。

以上