1. 使测试易于阅读和维护
测试代码的可读性和被测试代码同样重要。很多程序员会把测试代码看做非正式文档,它记录了代码如何工作及如何使用。
当测试代码多得让人望而却步,程序员不敢修改真实代码,不会再增加新的测试。从而对测试代码丧失信心。
2. 有问题的测试代码示例,后面要一一对它们进行修改。
1 void Test1() { 2 vector<ScoredDocument> docs; 3 docs.resize(5); 4 docs[0].url = "http://example.com"; 5 docs[0].score = -5.0; 6 docs[1].url = "http://example.com"; 7 docs[1].score = 1; 8 docs[2].url = "http://example.com"; 9 docs[2].score = 4; 10 docs[3].url = "http://example.com"; 11 docs[3].score = -99998.7; 12 docs[4].url = "http://example.com"; 13 docs[4].score = 3.0; 14 15 SortAndFilterDocs(&docs); 16 17 assert(docs.size() == 3); 18 assert(docs[0].score == 4); 19 assert(docs[1].score == 3.0); 20 assert(docs[2].score == 1); 21 }
3. 使这个测试更可读
对使用者隐去不重要的细节,以便更重要的细节能突出。这样测试代码变得紧凑一点了。
1 void MakeScoredDoc(ScoredDocument* sd, double score, string url) { 2 sd->score = score; 3 sd->url = url; 4 } 5 6 void Test1() { 7 vector<ScoredDocument> docs; 8 docs.resize(5); 9 MakeScoredDoc(&docs[0], -5.0, "http://example.com"); 10 MakeScoredDoc(&docs[1], 1, "http://example.com"); 11 MakeScoredDoc(&docs[2], 4, "http://example.com"); 12 MakeScoredDoc(&docs[3], -99998.7, "http://example.com"); 13 ... 14 }
进一步隐藏细节
1 void AddScoredDoc(vector<ScoredDocument>& docs, double score) { 2 ScoredDocument sd; 3 sd.score = score; 4 sd.url = "http://example.com"; 5 docs.push_back(sd); 6 } 7 8 void Test1() { 9 vector<ScoredDocument> docs; 10 AddScoredDoc(docs, -5.0); 11 AddScoredDoc(docs, 1); 12 AddScoredDoc(docs, 4); 13 AddScoredDoc(docs, -99998.7); 14 ... 15 }
4. 创建最小的测试声明
CheckScoresBeforeAfter("-5, 1, 4, -99998.7, 3", "4, 3, 1");
我们可以把测试的基本内容精简为一行代码来描述,即对于给定的输入,期望的输出是什么。
5. 实现定制的“微语言”
在较新版本的C++中,可以这样传入数组:
CheckScoresBeforeAfter({-5, 1, 4, -99998.7, 3}, {4, 3, 1});
然而在之前,需要对字符串进行解析:
1 vector<ScoredDocument> ScoredDocsFromString(string scores) { 2 vector<ScoredDocument> docs; 3 replace(scores.begin(), scores.end(), ',', ' '); 4 // Populate 'docs' from a string of space-separated scores. 5 istringstream stream(scores); 6 double score; 7 while (stream >> score) { 8 AddScoredDoc(docs, score); 9 } 10 return docs; 11 } 12 13 string ScoredDocsToString(vector<ScoredDocument> docs) { 14 ostringstream stream; 15 for (int i = 0; i < docs.size(); i++) { 16 if (i > 0) { 17 stream << ", "; 18 stream << docs[i].score; 19 } 20 } 21 return stream.str(); 22 } 23 24 void CheckScoresBeforeAfter(string input, string expected_output) { 25 vector<ScoredDocument> docs = ScoredDocsFromString(input); 26 SortAndFilterDocs(&docs); 27 string output = ScoredDocsToString(docs); 28 assert(output == expected_output); 29 }
乍一看有很多的代码,但是实际代码的能力更强了,你可以只调用。
CheckScoresBeforeAfter一次就写出了整个测试,你将倾向于增加更多的测试代码。
6. 让错误消息具有可读性
assert(output == expected_output);
Assertion failed: (output == expected_output),
function CheckScoresBeforeAfter, file test.cc, line 37.
大部分语言都有高级版本的assert()可用。
BOOST_REQUIRE_EQUAL(output, expected_output)
test.cc(37): fatal error in "CheckScoresBeforeAfter": critical check
output == expected_output failed ["1, 3, 4" != "4, 3, 1"]
手工打造错误消息。理想的错误消息可以像这样:
CheckScoresBeforeAfter() failed,
Input:
"-5, 1, 4, -99998.7, 3"
Expected Output: "4, 3, 1"
Actual Output: "1, 3, 4"
1 void CheckScoresBeforeAfter(...) { 2 //... 3 if (output != expected_output) { 4 cerr << "CheckScoresBeforeAfter() failed," << endl; 5 cerr << "Input: 6 "" << input << """ << endl; 7 cerr << "Expected Output: "" << expected_output << """ << endl; 8 cerr << "Actual Output: "" << output << """ << endl; 9 abort(); 10 } 11 }
7. 选择好的测试输入
应当选择一组最简单的输入,它能完整地使用被测代码。
1 CheckScoresBeforeAfter("1, 2, 3", "3, 2, 1"); //未测试负分的情况 2 CheckScoresBeforeAfter("123014, -1082342, 823423, 234205, -235235", 3 "823423, 234205, 123014"); // 无必要的复杂
简化输入值
CheckScoresBeforeAfter("-5, 1, 4, -99998.7, 3", "4, 3, 1"); // -99998.7, 3只是想表示很大的负数,用-1e100这样的值更好 // 实际上只需要一个负数来测试负数会被移除就可以了: CheckScoresBeforeAfter("1, 2, -1, 3", "3, 2, 1"); //你可能会想包含这样一个测试, //大型输入在发现bug方面很有用,但是这样的代码大多看上去很吓人,效果却不好 CheckScoresBeforeAfter("100, 38, 19, -25, 4, 84, [lots of values] ...", "100, 99, 98, 97, 96, 95, 94, 93, ..."); // 用编程的方法来生成大型输入(如100000个值)会有更好的效果。
一个功能的多个小测试,更容易、更高效且更有可读性
SortAndFilterDocs()的四个测试:
1 CheckScoresBeforeAfter("2, 1, 3", "3, 2, 1"); //Basic sorting 2 CheckScoresBeforeAfter("0, -0.1, -10", "0"); // All values < 0 removed 3 CheckScoresBeforeAfter("1, -2, 1, -2", "1, 1"); //Duplicates not a problem 4 CheckScoresBeforeAfter("",""); //Empty input O
8. 为测试函数命名
不要用Test1()这样的名字,可以用Test_<FunctionName>()这样的格式:
void Test_SortAndFilterDocs()
或者Test_<FunctionName>_<Situation>()这样的格式:
void Test_SortAndFilterDocs_BasicSorting()
void Test_SortAndFilterDocs_NegativeValues()
在你的整个代码库中不会调用这些函数,因此避免使用长函数名的情形在这里不适用
9. 对测试较好的开发方式
对测试友好的东西往往自然地产生有良好组织的代码。
尽量避免:1. 使用全局变量;2. 对外部组件有大量依赖的代码;3. 代码有不确定的行为。
应当做到:1. 类中只有很少或者没有内部状态;2. 一个类/函数只做一件事;3. 每个类对别的类的依赖很少(低耦合);4. 函数的接口简单,定义明确。
不要走得太远:1. 为了测试,牺牲代码可读性;2. 着迷于100%的测试覆盖率;3. 让测试成为产品开发的阻碍