写完代码后,你当然要测试它。有许多种方法来测试代码:你可以在你的代码中加些断点,或者观察实际发生的情况并和你预计的结果相比较,又或者写些测试程序等等。
给代码写测试,通常是测试代码的最好方法。因为写出来的测试可以一遍又一遍地执行。当你修改了实现,你可以再次运行测试来检查一下你是否引入了任何bug。这种方法可以将你从调试中拯救出来,并引领你生产出具有更低错误倾向的代码。
作为一个一般的方针,在开发你的测试的时候,我们建议你不要把结果打印到屏幕上;而是,根据输入数据,用asserts创建一个后置条件指明期望得到哪些输出数据。
如果你在运行测试时把结果打印到了屏幕上,开始时,你会去分析这些结果。然而,随着时间延长,你会越来越不注意这些打印到屏幕上的数据。即使数据指出了一个错误,你也可能不会意识到。
此外,如果别的人试着运行测试,他们不得不首先弄明白这些打印出来的数据是什么意思,这可能要花不少时间。反过来,如果你用了asserts,人们就只要去运行你的测试。如果发生了错误,他们会被提示在哪一行发生了错误,他们就可以去修正它。
这儿是一个测试例程的例子:
#include #include // 整理一个字符串的前导和追尾空白, // 返回整理后的字符串 std::string trim_spaces( const std::string & str) { /* trim_spaces 的实现*/ } void testTrimSpaces() { assert( trim_spaces( " abc") == "abc"); assert( trim_spaces( "def ") == "def"); assert( trim_spaces( " this is a test ") == "this is a test"); assert( trim_spaces( "") == ""); assert( trim_spaces( " ") == ""); } |
怎样建立一个测试框架,和建立它每一个步骤:
第一步:将测试代码和实际代码清楚地分隔开。要做到这一步,一个很简单的方法是把所有用来做测试的文件都放到一个特别的目录中去。每个用来做测试的文件的名字都应该以test开头,然后加上它所测试的模块/类名。例如,从testWordTokenizer.cpp文件的名字就可以看出,它是用来测试一个断词类(word tokenizer class)的。
测试用的代码仅在执行测试时才被编译。当生成实际的应用程序时,测试代码会在预处理阶段就被移除。如果我们的每个测试文件都遵循下面的模式就可以保证这一点:
// Test.cpp #ifdef TESTING /* testing code*/ #endif // TESTING // End of file |
于是,如果定义了TESTING,我们就是在做测试;否则,我们就是在从实际的代码生成应用程序。
第二步:所有用来测试代码片断的函数的名字都应该以Test或test开头。对于你所测试的每个模块/类,都要有一个主测试函数负责调用其他测试函数来测试模块/类的各个代码段。这样你就不需要暴露出所有函数——只要暴露出主测试函数就可以了,像下面的例子:
// TestUrlUtility –用于测试 "Url Utility 函数族" #if defined( TESTING) #include "UrlUtility.h" // 这个命名空间中的函数,从这个源文件外部是不可见的 namespace // 匿名命名空间 { void TestDivideURL() { /* 测试代码 */ } void TestIsUrlValid() { /* 测试代码*/ } void TestIsHttpUrlValid() { /* 测试代码*/ } void TestParseHttpUrl() { /* 测试代码*/ } }; //匿名命名空间 // ... 这是从这个源文件外部唯一可见的函数 // // 我们希望暴露出这个函数; // 在任意文件中,你可以声明它的原型如下: // void TestUrlUtility(); // // 然后就可以在你的代码中调用 void TestUrlUtility() { TestDivideURL(); TestIsUrlValid(); TestIsHttpUrlValid(); TestParseHttpUrl(); } #endif // #if defined( TESTING) // 文件结束 |
第三步:创建一个测试主文件(大概应该叫做testMain.cpp),其中包含有开始执行测试的main函数。这个“main”函数要做的就是调用各种不同的测试函数。你可以取消希望执行的测试前面的注释符号,或者把你不想执行的测试注释掉。它的代码看起来会是这个样子:
// TestMain.cpp –用于测试整个应用程序 #ifdef TESTING #include // 测试函数开始 // ... 注意:这一部分包含的应该是 // 各个主测试函数(测试整个 // 模块或整个类的函数) void TestUrlUtility(); void TestProxyManager(); void TestHttpRequest(); void TestHttpHeaderFields(); // 测试函数结束 /* 用于测试应用程序的各个部分 */ int main() { std::cout << "Testing Application." << std::endl; // 在这里加入你要执行的测试 // TestProxyManager(); // TestHttpRequest(); // TestUrlUtility(); TestHttpHeaderFields(); // 取消你要执行的测试前面的注释符号 return 0; } #endif // 文件结束 |
第四步:有些代码片断不应该在测试时被编译(如用户界面代码,真正的“main”函数等)。这些代码将像下面这样括起来:
#ifndef TESTING /* 实际代码 */ #endif // ndef( TESTING) |
第五步:为测试创建一个配置。大多数最新的集成开发环境(IDE)都允许从不同的配置中选择,并至少提供了两个缺省配置:debug和release。基于debug配置创建一个新的。然后,直接打开TESTING标志。如果用的是gcc,那么给编译参数加上一个-DTESTING标志。如果用的是VC6,在Project Settings | C++ tab | General | Preprocessor处加一个TESTING的定义。