最近一直在关注敏捷开发的工程实践和技术实践,一直想自己亲身体会一下TDD的威力,总是因为这样那样的原因而一再拖延。
这两天正好有个朋友问我Project euler里面49题的解法,我动手编程时候发现自己的基础运算的库存在以前笔记本上,本子被老婆带走用了。所以,我需要在现在的电脑上重新编写求排列组合的功能,我想正好,这是我实践TDD的大好机会,于是花了一些时间进行了TDD的实践,下面说说自己如何入手和使用TDD后的体会。
我的TDD的实践步骤:
1、初步设计测试用例和对象模型
2、编写测试代码 -> 编译运行
3、伪实现测试代码 -> 编译运行
4、实现测试代码 -> 编译运行
5、查看测试代码覆盖率,为未覆盖代码添加测试代码 ->
6、重构 -> 编译运行
7、如果设计也发生了变动,则重构测试代码 -> 编译运行
循环 4 -> 7
这里我自己的需要就是我的需求 (US)
US:我希望可以给定一个包含m元素对象的集合,给定我想从中选出的元素个数n,从中找出所有满足条件的元素组合列表(无序) C(n,m) 或 排列列表(有序) P(n,m)
所以,我初步设计一些测试的用例来编写测试代码(TC)
TC:给出一个泛型的对象数组T[],和要从中选出的的元素个数,能够返回一个包含了所有无序组合结果的列表
TC:给出一个泛型的对象数组T[],和要从中选出的的元素个数,能够返回一个包含了所有有序组合结果的列表
设计(抱歉我这次不打算绘制UML,所以,习惯看UML类图,对象图的朋友们抱歉了):
1、我需要创建一个类库,暂且命名为:MathLibrary
2、类库内包含 ComposeSelector<T> 和 PermutationSelector<T> 两个类
3、两个类都包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
4、我希望在给定这些值后,可以调用DoProcess方法来进行排列组合的运算
5、我希望运算成功后,可以在对象的Result属性读取到对应的结果
编写测试代码:
首先创建了一个名为:MathLibraryTest的测试项目,在项目内新增两个单元测试,取名为:ComposerTest 和 PremutationTest
里面有一些IDE自动生成的代码,我们不去理会,删除里面的 TestMethod1;
分别为我们的两个类编写创建对象的测试代码
两个类中分别引入我们即将使用的命名空间
创建对象的测试代码
ComposerTest类
public void TestCreateComposerSelector<T>()
{
ComposerSelector<T> composer = new ComposerSelector<T>();
}
PremutationTest类
public void TestCreatePermutationSelector<T>()
{
PremutationSelector<T> premutationSelector = new PremutationSelector<T>();
}
这个时候,我们编译运行我们的测试,会发现有4个错误,我们接下来的工作,就是编写代码,消除这四个错误,使代码可以通过编译运行
因为我们并没有真正的创建
类库 MathLibrary 和 类 ComposerSelector<T>, PremutationSelector<T>
下一步我们要做的就是创建这些所需的类库和类,并在测试项目添加对MathLibrary的引用。
编译-通过
Ctrl + R , A 运行测试,该死:提示"泛型方法不能为测试方法。"
原来是我们的测试方法有问题,我们可以再创建一个非泛型方法,调用我们的泛型方法进行测试;
在C#中,我们可以使用GenericParameterHelper来作为T的输入,谨慎起见,我决定多选几种类型作为输入对创建进行测试。
ComposerTest类
public void TestCreateComposerSelector()
{
ComposerSelector<GenericParameterHelper> generalSelector = TestCreateComposerSelector<GenericParameterHelper>();
ComposerSelector< int> intSelector = TestCreateComposerSelector< int>();
ComposerSelector< string> stringSelector = TestCreateComposerSelector< string>();
ComposerSelector< object> objectSelector = TestCreateComposerSelector< object>();
}
private ComposerSelector<T> TestCreateComposerSelector<T>()
{
return new ComposerSelector<T>();
}
PremutationTest 类
public void TestCreatePermutationSelector()
{
PremutationSelector<GenericParameterHelper> generalSelector = TestCreatePermutationSelector<GenericParameterHelper>();
PremutationSelector< int> intSelector = TestCreatePermutationSelector< int>();
PremutationSelector< string> stringSelector = TestCreatePermutationSelector< string>();
PremutationSelector< object> objectSelector = TestCreatePermutationSelector< object>();
}
private PremutationSelector<T> TestCreatePermutationSelector<T>()
{
return new PremutationSelector<T>();
}
编译通过;
运行测试通过;
我们再来检查一下我们最初的设计,我们发现其中的第一项和第二项已经在我们的项目和测试代码中有了完整地体现了
2、类库内包含 ComposeSelector<T> 和 PermutationSelector<T> 两个类
3、两个类都包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
4、我希望在给定这些值后,可以调用DoProcess方法来进行排列组合的运算
5、我希望运算成功后,可以在对象的Result属性读取到对应的结果
这里我们最好打开测试工具中对代码覆盖率的统计,对于基础的类库,如果可能,最好还是达到100%的测试覆盖率,这样,你在对代码进行重构的时候,将会感觉信心十足。
方法:在项目管理器中右键点击解决方案,添加新建项,测试设置,添加完成后,我们点击测试设置文件,会弹出测试设置的窗体
选择“数据和诊断”,勾上代码覆盖率的启用框,先不要确定,点击“配置”按钮(比较隐蔽,不要忘了)
会让我们选择要检测的项目,勾上 MathLibrary 项目,确定。
运行测试-通过
这个时候,我们可以打开代码覆盖率的窗口,我们发现“ 生成了空结果: 未使用任何被检测的二进制文件。请查看测试运行详细信息,确定是否存在任何检测问题。 ”的提示,不要着急,这是因为我们还没有在类里面编写任何的代码,所以才会有空结果,在我们编写完一些代码并测试后,就可以生成代码覆盖率了。
==================================================
我知道对有些人可能太简单了,我这篇Blog的初衷,不是讨论模式、重构、算法或任何其他相关,只是一步步地记录我在进行TDD过程中的体会,希望能够对有兴趣做TDD实践的人有些帮助。
关于节奏,我写了这么长,也许你看了之后,只用五分钟就做完了编码,因为我不想简单的给出代码,而是想把我为什么这样进行编码的思考与大家分享。
所以,这个个人实践体会,应该会是一个比较长期写下去的话题,也会穿插我在其他方面的一些想法,所以,对于性子比较急得同僚,只能是抱歉了。