注意,所有测试都包含一个或多个CHECK宏。我们使用CHECK宏来确保一个布尔条件值为true。一个测试可能包含不止一个CHECK语句,每一个都将被执行,而不管先前的CHECK语句成功与否。
SaleTest当然无法编译,因为函数GetTotal还没有写出来呢。无论如何,我们先编译一回,以获得第一次反馈。如果SaleTest编译通过了,我们便会知道碰到了严重的配置问题。
g++ -c SaleTest.cpp
SaleTest.cpp:
In method ''void totalNewSaleSaleTest::run(TestResult &)'':
SaleTest.cpp:12:
implicit declaration of function ''int GetTotal()''
一旦得到了编译错误反馈,我们就创建刚好够用的Sale.h:
#ifdef __cplusplus
extern "C"
{
#endif
int GetTotal();
#ifdef __cplusplus
}
#endif
我们将Sale.h包含进SaleTest.cpp中,并且创建刚好够用的Sale.c:
#include "Sale.h"
int GetTotal()
{
return -1;
}
注意在Sale.h中对连接指示符extern "C"的使用。这是必需的,因为我们的测试框架使用C++编写而成并且#include了这个文件,而Sale.c使用C语言编写而成并且也包含了这个文件。对于C++代码调用C代码而言,需要这个机制。
我们的目的是为了通过编译,以便能够连接和运行测试。为了运行测试,只要简单地执行SaleTest即可:
Failure: "0 == GetTotal()" line 13 in SaleTest.cpp
发生了一处失败。
这在意料之中:我们编写了一个测试,但还没有写什么产品代码。不过,我们希望看到一个失败,以作为“反馈驱动、测试先行”的设计周期的一个组成部分。对此周期的补充提醒如下:
为尚未存在的代码编写一个测试。
预期测试将会失败。
编写刚好够用的代码使测试通过。
重构。
每次一旦预期失败的测试通过了,对于我们而言都是一个震动!
对于Sale应用来说,只要让GetTotal返回0就可以使得这个测试通过:
int GetTotal()
{
return 0;
}
尽管仅仅返回一个“硬编码”的值来让测试通过看上去荒谬可笑,但这正是TDD的一个组成部分。我们有一个失败的测试案例,我们希望尽可能快地通过测试。一旦测试通过,我们就会关心怎么来使它“正确”。返回0看起来是“错误”的,因为“硬编码”的0很快就会被取代掉。仅仅提供刚好够用的代码使得当前测试通过这一事实,意味着我们将必须编写更多的测试。这些测试将会失败,又证明需要更复杂的算法。我们以这种方式稳步增加测试范围。进行更多的测试是一件好事。
现在不存在测试失败了。
第一个测试完成了。我们知道了当对它什么也没有做时一个Sale的状态应该是什么样子。现在,我们希望具有销售至少一件商品的能力,因此编写测试sellOneItem。对于这个测试而言,我们虚构一件商品,其条形码为“a123” ,价格为199美分。(计算到分易于避免以后任何浮点数的四舍五入问题)
TEST(Sale, sellOneItem)
{
BuyItem("a123");
CHECK(199 == GetTotal());
}
为了销售一件商品,需要将其条形码字符串传入Sale。我们设计一个函数BuyItem来完成这项任务。购买条形码为“a123”的商品应该会将Sale的总价增加至199。编译之并预期失败的发生:
SaleTest.cpp:18:
implicit declaration of function ''int BuyItem()''
为了通过编译,必须将头文件修改为:
int GetTotal();
void BuyItem(char* barCode);
并将Sale.c修改为:
#include "Sale.h"
int GetTotal()
{
return 0;
}
void BuyItem(char* barCode)
{
}
现在可以运行所有测试并预期sellOneItem发生失败:
Failure: "199 == GetTotal()" line 19 in SaleTest.cpp
发生了一处失败。
现在是添加代码使这个测试通过的时候了。每一位顾客的购买货款必须从总价(total)为0开始。从这一点来说,可以工