这样一来,Initialize需要存储函数的指针。再一次,我们使用声明于Sale.c中的static变量:
static int (*LookUpPrice)(char* barCode);
void Initialize(int (*db)(char*))
{
total = 0;
LookUpPrice = db;
}
最后,对GetPrice进行修改,这样它就可以提领(dereferences)数据库函数指针,以便调用数据库查询函数:
int GetPrice(char* barCode)
{
return LookUpPrice(barCode);
}
编译连接并运行测试。成功。
现在我们可以选定的任何方式获得商品的价格。对于测试而言,我们使用一个简单的if/else结构基于已知的条形码来返回“硬编码”的价格。而在产品代码里,无论谁来构建销售点系统都必须提供一个指向Sale的函数指针,以便存取真正的数据库。(这个函数当然应该拥有它自己的一套测试!)
连接期多态
指针威力巨大但也危险无比,近十年来引入的主要语言倾向于将它排除在外就是一个证据。指针的问题在于我们必须确保它指向我们要它指向的地方。一个迷途的指针很容易导致程序崩溃并致使整个开发小组调试到半夜。因为提领(dereferencing)一个非法的指针确定无疑是一个bug并且很可能造成程序崩溃,所以很多程序员在对每一个指针进行提领操作之前都要测试其合法性,这样的检查使得代码难于阅读。
对于使用指针指向不同的函数而言存在一个替代方案:连接期多态。我们改用连接器来连接不同的函数。函数BuyItem(char*)将继续调用GetPrice(char*),不过将存在两个GetPrice(char*)函数定义:一个用于测试,我们将其放在测试文件SaleTest.cpp中,而另一个则被放于GetPrice.c文件中。
当在整个开发日每几分钟就编译和连接测试时,我们连接SaleTest.o和Sale.o;当希望构建真实的系统时,我们连接真实的应用程序和GetPrice.o而非SaleTest.o。以下是一个非常简单的Makefile,描述了这两个构建操作:
POSApp: Sale.o GetPrice.o main.o
g++ -o POSApp Sale.o GetPrice.o main.o
SaleTest: SaleTest.o Sale.o
g++ -o SaleTest SaleTest.o Sale.o \
../TestHarness/TestHarness.a
SaleTest.o: SaleTest.cpp Sale.h
g++ -c SaleTest.cpp
Sale.o: Sale.c Sale.h GetPrice.h
gcc -c Sale.c
GetPrice.o : GetPrice.c GetPrice.h
gcc -c GetPrice.c
结语
在这个练习中,要注意的一个极其重要事情是我们在整个开发中都采用非常小的步幅。我们的目标是确保能够以一个稳步的速率递增地改善系统。我们希望看到测试每隔5到10分钟(甚至更短一些)就通过一次。这使我们免于沿着错误的方向花费太多的时间。在一天的结束,产品代码比一天的开始可以做的更多并且做的正确。
适度应用TDD将会带来惊人的效果。我们认为它是当今软件开发的一个非常重要的“潮流”。我们不断遇到开发人员,他们告诉我们他们已经尝试TDD并且再也不会放弃。正如我们在这儿示范的,TDD并不仅仅适用于“对象”,你也可以在C语言中使用它。实际上,我们的观点是,由于C是如此具有威力但充满危险的语言(这要归因于它的“低级”天性),你真的必须使用TDD保护你自己。
无可否认,我们选择的例子规模被缩小了以便描述TDD技术。然而,TDD已经被证实为可以处理巨大而复杂的系统。实际上,TDD的一个主要目标就是确保我们采用简化了的方式来处理复杂问题。利用TDD正确地管理复杂性,意味着我们可以构建“可以长期以合乎情理的代价而维护”的系统。
Saletest.cpp(可以到www.cuj.com下载)展示的最终代码示范了连接期多态技术。
附注
一个用户故事(user story)是这样的一个需求:1.具有显而易见的业务价值;2.可被测试;3.可以在一次迭代中完成(通常是两周)。
当在另外一台机器上编写这个Sale例子时,我们碰到了一些有意思的结