TDD个人实践体会(C#)二
2012-06-06 18:18
288 查看
上一篇我们编写了第一个测试代码,我们再来看一下我们的 US, 测试备注 和 设计
US:我希望可以给定一个包含m元素对象的集合,给定我想从中选出的元素个数n,从中找出所有满足条件的元素组合列表(无序) C(n,m) 或 排列列表(有序) P(n,m)
设计
1、我需要创建一个类库,暂且命名为:MathLibrary
2、类库内包含 ComposerSelector<T> 和 PermutationSelector<T> 两个类
3、两个类都包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
4、我希望在给定这些值后,可以调用DoProcess方法来进行排列组合的运算
5、我希望运算成功后,可以在对象的Result属性读取到对应的结果
从之前的设计中,可以看到,ComposerSelector 和 PermutationSelector 有完全相同的属性和方法,我们就要考虑采用什么形式进行抽象
SourceObject 、 CountToSelect 和 Result 属性,在两个类中会具有完全相同的特性、可访问性和具体含义,显然如果采用接口抽象并不合适,将其放在一个Selector<T>的基类中会更合适,也许需要重构一下设计了
需要增加一个Selector的基类
SourceObjects和CountToSelect属性应该在赋值之后不允许外部对其进行修改,应该是pirvate set的
SourceObjects和CountToSelect需要创建能够传入对应参数的构造函数来对其进行初始的赋值
Result属性石DoProcess计算的结果,也不允许在外部进行修改,也应该是 private set 的
希望在Selector后能有个属性,直接获取到返回的排列组合的条数,而不需要去再每次写 selector.Result.Coun()
重构后的设计
1、我需要创建一个类库,暂且命名为:MathLibrary
2、类库内包含 ComposerSelector<T> 和 PermutationSelector<T> 两个类
3、ComposerSelector<T> 和 PermutationSelector<T> 是 Selector<T> 的子类
4、Selector<T> 包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
5、ComposerSelector<T> 和 PermutationSelector<T> 对象,可以调用DoProcess方法来进行排列组合的运算
6、Selector<T> 包含了 Result 属性,这个属性用来存储 DoProcess 方法运算后的结果值
7、Selector<T> 包含 ResultCount 属性,返回结果包含的记录条数
这些设计,已经改变了代码的一些外部行为,因此需要重构的测试代码。
ComposerTest 类
[TestMethod]
public void TestCreateComposerSelector()
{
ComposerSelector<GenericParameterHelper> generalSelector = TestCreateComposerSelector<GenericParameterHelper>(new GenericParameterHelper[] { }, 5);
ComposerSelector<int> intSelector = TestCreateComposerSelector<int>(new int[] { }, 10);
ComposerSelector<string> stringSelector = TestCreateComposerSelector<string>(new string[] { }, 3);
ComposerSelector<object> objectSelector = TestCreateComposerSelector<object>(new object[] { }, 9);
}
private ComposerSelector<T> TestCreateComposerSelector<T>(T[] source, int countToSelect)
{
return new ComposerSelector<T>(source, countToSelect);
PremutationTest 类
[TestMethod]
public void TestCreateComposerSelector()
{
ComposerSelector<GenericParameterHelper> generalSelector = TestCreateComposerSelector<GenericParameterHelper>(new GenericParameterHelper[] { }, 5);
ComposerSelector<int> intSelector = TestCreateComposerSelector<int>(new int[] { }, 10);
ComposerSelector<string> stringSelector = TestCreateComposerSelector<string>(new string[] { }, 3);
ComposerSelector<object> objectSelector = TestCreateComposerSelector<object>(new object[] { }, 9);
}
private ComposerSelector<T> TestCreateComposerSelector<T>(T[] source, int countToSelect)
{
return new ComposerSelector<T>(source, countToSelect);
编译:失败(这是肯定的)
我们下面就来调整代码使创建对象的测试能够通过
ComposerSelector类
public class ComposerSelector<T>
{
public ComposerSelector(T[] sourceObjects, int countToSelect) { }
}
PremutationSelector类
public class PremutationSelector<T>
{
public PremutationSelector(T[] sourceObjects, int countToSelect) { }
}
编译成功
测试通过
然而,这时候的代码还远远没有达到设计的要求,接着就逐条解决
3.ComposerSelector<T> 和 PermutationSelector<T> 是 Selector<T> 的子类
这一条,并不不改变类的外部行为,根本不需要改变测试代码只需要创建一个基类并在子类中继承基类
我不希望能够直接创建Selector的对象,因此将Selector类标记为Abstract
修改后的 PremutationSelector类
public class PremutationSelector<T>:Selector<T>
{
public PremutationSelector(T[] sourceObjects, int countToSelect):base(sourceObjects,countToSelect) { }
}
PremutationSelector类就不给出代码了,你可能又发现了可以重构的地方了吧,不过当时我并没有发现这个,我是在后来代码有了更多的行为后,才发现了需要做构造方法的重构的,所以,这里我并不解决它,会在后面对其进行重构,在早起,过多的应用重构还有过度使用设计模式,也许会让你陷入一个泥潭。要知道,我们现在连一行具有实际意义的行为代码都没写。
编译成功
测试通过
下一条设计
4.Selector<T> 包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
这一条,就明显的需要来构建测试代码来进行测试了,这两个属性,都有读写的操作
属性写操作将会在构造函数内被执行
而读操作则需要我们进行读取操作来覆盖,下面就来编写测试代码
模拟从给定0-9十个数字中选出4个元素的Selector(ComposerSelector和PremutationSelector)
为了测试我们的设计能否应对更复杂的情况,我们再创建一个从一个包含有整型数字、字符、字符串、浮点、bool等不同类型对象的object的数组中选出三个元素的Selector(ComposerSelector和PremutationSelector)
ComposerTest 新增代码
[TestMethod]
public void ComposerPropertiesTest() {
int[] intSource = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int intCountToSelect = 4;
ComposerSelector<int> intSelector = new ComposerSelector<int>(intSource, intCountToSelect);
Assert.AreEqual(intSelector.SourceObjects, intSource);
Assert.AreEqual(intSelector.CountToSelect, intCountToSelect);
object[] objSource = new object[] { 10, 'A', "HelloWorld", 2.69f, true };
int objCountToSelect = 3;
ComposerSelector<object> objSelector = new ComposerSelector<object>(objSource, objCountToSelect);
Assert.AreEqual(objSelector.SourceObjects, objSource);
Assert.AreEqual(objSelector.CountToSelect, objCountToSelect);
}
编译:失败
下面要做的就是在Selector<T>类中增加对应的属性,并在构造函数中增加赋值行为,来使测试可以通过。
Select<T> 类:
public T[] SourceObjects { get; private set; }
public int CountToSelect { get; private set; }
public Selector(T[] sourceObjects,int countToSelect){
this.SourceObjects = sourceObjects;
this.CountToSelect = countToSelect;
}
编译:成功
测试:通过
虽说这个时候代码覆盖率已经100%了(因为PremutationSelector的构造行为和这两个属性的相关操作在基类中已经被完成,因此已经被ComposerSelector的测试代码覆盖)
我仍旧固执的为PremutationSelector编写了测试代码(这让人很痛苦,一模一样的代码的复制黏贴,但这会更增加对其重构的决心。如果对重构抱有偏执,恭喜,这就是重构的原因之一,写测试代码遇到的让人蛋疼的事情,在编写功能代码的时候,几乎会100%遇到。)
其实,这个问题,早在编写构造函数的测试代码时候,就碰到了,但我仍旧把对代码的重构,放在了完成我们对修改的设计进行完整的编码后才进行的。
继续
5.ComposerSelector<T> 和 PermutationSelector<T> 对象,可以调用DoProcess方法来进行排列组合的运算
目前为止对DoProcess的行为,还是只定义为依据给定的数据,进行运算,并且把结果存入Result属性。
只需修改测试,调用DoProcess方法即可,可以发现,原来测试方法中的ComposerPropertiesTest和PremutationTest方法中已经做过了创建的操作,因此,只需要修改这个测试方法,加入调用方法即可。
编译:失败
目前的类中还没有DoProcess方法,肯定无法编译通过,只要简单的加入一个DoProcess方法就可以使其通过运行了。
目前 ComposerSelector<T> 和 PermutationSelector<T> 在DoProcess 方法上就有了差别,一个是求组合,一个是求排列。
DoProcess会在所有的Selector中都需要有的方法,将其放在抽象基类中,然后在子类对其实现。
Selector<T> 中新增
public abstract void DoProcess();
ComposerSelector<T> 和 PermutationSelector<T> 中新增
public override void DoProcess()
{
}
编译通过
测试成功
这个时候,可以查看代码覆盖率,仍旧是100%,但是,目前还不能够在测试代码中对各个对象的DoProcess进行Assert来确定其是否在正确的位置被正确的执行,目前还不去具体实现算法,因此这个问题后面再解决。
6、Selector<T> 包含了 Result 属性,这个属性用来存储 DoProcess 方法运算后的结果值
初步一看,这一条包含了两条信息在里面
一,Selector<T> 包含了 Result 属性
二,DoProcess 方法运算后会将结果存入Result
再分析一下,Result 应该是只能通过调用DoProcess方法来改变其值,对外部只读。(当然这里因为我们缺少用户方,也可以说我自己就是用户,因此,我可以自己按自己的想法来设定。如果在正式工作中,应该与用户沟通以确定各个细节的对外特性)
三,Result 的 set 方法应该是 private
继续分析,
ComposerSelector<T> 和 PermutationSelector<T> 在DoProcess方法执行之后,Result 的值会不同,也有一些情况会出现相同的值的情况,视SourceObjects和CountToSelect值而定。
依据我们的上面三条分析结果
一,Selector<T> 包含了 Result 属性
二,DoProcess 方法运算后会将结果存入Result
三,Result 的 set 方法应该是 private
只要对给对应的selector加入Result 的读取操作就可以了,而Result的写操作会在DoProcess被覆盖。
在ComposerTest和PermutationTest测试方法内增加对DoProcess方法的调用和对Result方法的读取
ComposerTest 类内
[TestMethod]
public void ComposerPropertiesTest() {
int[] intSource = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int intCountToSelect = 4;
ComposerSelector<int> intSelector = new ComposerSelector<int>(intSource, intCountToSelect);
intSelector.DoProcess();
Assert.AreEqual(intSelector.SourceObjects, intSource);
Assert.AreEqual(intSelector.CountToSelect, intCountToSelect);
Assert.IsNotNull(intSelector.Result);
object[] objSource = new object[] { 10, 'A', "HelloWorld", 2.69f, true };
int objCountToSelect = 3;
ComposerSelector<object> objSelector = new ComposerSelector<object>(objSource, objCountToSelect);
objSelector.DoProcess();
Assert.AreEqual(objSelector.SourceObjects, objSource);
Assert.AreEqual(objSelector.CountToSelect, objCountToSelect);
Assert.IsNotNull(objSelector.Result);
}
PermutationTest内代码相同(我现在有了更多的重复代码,你应该已经嗅出了bad smell了,重构的理由更加充分)
编译:失败
在Selector<T>中添加Result属性即可;
Selector<T> 类中添加
public List<T[]> Result { get; private set; }
编译:成功
测试会有两条失败信息:
未通过 ComposerPropertiesTest MathLibraryTest Assert.IsNotNull 失败。
未通过 PremutationPropertiesTest MathLibraryTest Assert.IsNotNull 失败。
这是并没有在DoProcess方法对结果进行赋值引发的测试失败。
这点我们在第二条已经考虑到了。
第二条并不改变测试代码,但是需要我们在DoProcess方法添加对Result的赋值来覆盖Result的set;
为了能够更好的测试,在DoProcess中,将SourceObjects存为Result中的一条结果,这样,就可以使用equals方法来测试Result是否被正确赋值
第三条无需多说吧
我们分别在ComposerSelector和PermutationSelector的DoProcess方法中加入伪实现的代码;
this.Result = new List<T[]>() { this.SourceObjects };
编译:失败
一点失误,由于子类的Result属性继承于基类,而对基类Result的set方法的private造成了子类也无法对其赋值。
只需要将基类内Result的set方法设为protected即可
修改代码
编译:通过
测试:通过
1、我需要创建一个类库,暂且命名为:MathLibrary
2、类库内包含 ComposerSelector<T> 和 PermutationSelector<T> 两个类
3、ComposerSelector<T> 和 PermutationSelector<T> 是 Selector<T> 的子类
4、Selector<T> 包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
5、ComposerSelector<T> 和 PermutationSelector<T> 对象,可以调用DoProcess方法来进行排列组合的运算
6、Selector<T> 包含了 Result 属性,这个属性用来存储 DoProcess 方法运算后的结果值
7、Selector<T> 包含 ResultCount 属性,返回结果包含的记录条数
DoProcess的功能尚未真正实现。
============待续================
到了这里,测试代码可以通过运行,而且覆盖率也很高 100% ,但是的测试代码内充满了让人恼怒的 bad smell 代码。
下一步,我并没有立即编写算法的代码,而是对测试代码和重构进行了简单的重构
这里我和一个朋友产生了争论,至今没有结果
对方说,我应该尽快地实现功能,在功能实现后再来进行重构,否则,我可能会耗费大量的工作在重构上而延缓了功能实现的时间(我同意,这的确会使功能实现的时间延后)
而我认为,这个时候,已经可以明显确定的设计上的缺陷,此时重构的成本最小,如果在完全实现功能后再重构,那么重构的工作量将会很大。
关于重构的时机,希望各位能够给些意见。
US:我希望可以给定一个包含m元素对象的集合,给定我想从中选出的元素个数n,从中找出所有满足条件的元素组合列表(无序) C(n,m) 或 排列列表(有序) P(n,m)
设计
1、我需要创建一个类库,暂且命名为:MathLibrary
2、类库内包含 ComposerSelector<T> 和 PermutationSelector<T> 两个类
3、两个类都包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
4、我希望在给定这些值后,可以调用DoProcess方法来进行排列组合的运算
5、我希望运算成功后,可以在对象的Result属性读取到对应的结果
从之前的设计中,可以看到,ComposerSelector 和 PermutationSelector 有完全相同的属性和方法,我们就要考虑采用什么形式进行抽象
SourceObject 、 CountToSelect 和 Result 属性,在两个类中会具有完全相同的特性、可访问性和具体含义,显然如果采用接口抽象并不合适,将其放在一个Selector<T>的基类中会更合适,也许需要重构一下设计了
需要增加一个Selector的基类
SourceObjects和CountToSelect属性应该在赋值之后不允许外部对其进行修改,应该是pirvate set的
SourceObjects和CountToSelect需要创建能够传入对应参数的构造函数来对其进行初始的赋值
Result属性石DoProcess计算的结果,也不允许在外部进行修改,也应该是 private set 的
希望在Selector后能有个属性,直接获取到返回的排列组合的条数,而不需要去再每次写 selector.Result.Coun()
重构后的设计
1、我需要创建一个类库,暂且命名为:MathLibrary
2、类库内包含 ComposerSelector<T> 和 PermutationSelector<T> 两个类
3、ComposerSelector<T> 和 PermutationSelector<T> 是 Selector<T> 的子类
4、Selector<T> 包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
5、ComposerSelector<T> 和 PermutationSelector<T> 对象,可以调用DoProcess方法来进行排列组合的运算
6、Selector<T> 包含了 Result 属性,这个属性用来存储 DoProcess 方法运算后的结果值
7、Selector<T> 包含 ResultCount 属性,返回结果包含的记录条数
这些设计,已经改变了代码的一些外部行为,因此需要重构的测试代码。
ComposerTest 类
[TestMethod]
public void TestCreateComposerSelector()
{
ComposerSelector<GenericParameterHelper> generalSelector = TestCreateComposerSelector<GenericParameterHelper>(new GenericParameterHelper[] { }, 5);
ComposerSelector<int> intSelector = TestCreateComposerSelector<int>(new int[] { }, 10);
ComposerSelector<string> stringSelector = TestCreateComposerSelector<string>(new string[] { }, 3);
ComposerSelector<object> objectSelector = TestCreateComposerSelector<object>(new object[] { }, 9);
}
private ComposerSelector<T> TestCreateComposerSelector<T>(T[] source, int countToSelect)
{
return new ComposerSelector<T>(source, countToSelect);
PremutationTest 类
[TestMethod]
public void TestCreateComposerSelector()
{
ComposerSelector<GenericParameterHelper> generalSelector = TestCreateComposerSelector<GenericParameterHelper>(new GenericParameterHelper[] { }, 5);
ComposerSelector<int> intSelector = TestCreateComposerSelector<int>(new int[] { }, 10);
ComposerSelector<string> stringSelector = TestCreateComposerSelector<string>(new string[] { }, 3);
ComposerSelector<object> objectSelector = TestCreateComposerSelector<object>(new object[] { }, 9);
}
private ComposerSelector<T> TestCreateComposerSelector<T>(T[] source, int countToSelect)
{
return new ComposerSelector<T>(source, countToSelect);
编译:失败(这是肯定的)
我们下面就来调整代码使创建对象的测试能够通过
ComposerSelector类
public class ComposerSelector<T>
{
public ComposerSelector(T[] sourceObjects, int countToSelect) { }
}
PremutationSelector类
public class PremutationSelector<T>
{
public PremutationSelector(T[] sourceObjects, int countToSelect) { }
}
编译成功
测试通过
然而,这时候的代码还远远没有达到设计的要求,接着就逐条解决
3.ComposerSelector<T> 和 PermutationSelector<T> 是 Selector<T> 的子类
这一条,并不不改变类的外部行为,根本不需要改变测试代码只需要创建一个基类并在子类中继承基类
我不希望能够直接创建Selector的对象,因此将Selector类标记为Abstract
修改后的 PremutationSelector类
public class PremutationSelector<T>:Selector<T>
{
public PremutationSelector(T[] sourceObjects, int countToSelect):base(sourceObjects,countToSelect) { }
}
PremutationSelector类就不给出代码了,你可能又发现了可以重构的地方了吧,不过当时我并没有发现这个,我是在后来代码有了更多的行为后,才发现了需要做构造方法的重构的,所以,这里我并不解决它,会在后面对其进行重构,在早起,过多的应用重构还有过度使用设计模式,也许会让你陷入一个泥潭。要知道,我们现在连一行具有实际意义的行为代码都没写。
编译成功
测试通过
下一条设计
4.Selector<T> 包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
这一条,就明显的需要来构建测试代码来进行测试了,这两个属性,都有读写的操作
属性写操作将会在构造函数内被执行
而读操作则需要我们进行读取操作来覆盖,下面就来编写测试代码
模拟从给定0-9十个数字中选出4个元素的Selector(ComposerSelector和PremutationSelector)
为了测试我们的设计能否应对更复杂的情况,我们再创建一个从一个包含有整型数字、字符、字符串、浮点、bool等不同类型对象的object的数组中选出三个元素的Selector(ComposerSelector和PremutationSelector)
ComposerTest 新增代码
[TestMethod]
public void ComposerPropertiesTest() {
int[] intSource = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int intCountToSelect = 4;
ComposerSelector<int> intSelector = new ComposerSelector<int>(intSource, intCountToSelect);
Assert.AreEqual(intSelector.SourceObjects, intSource);
Assert.AreEqual(intSelector.CountToSelect, intCountToSelect);
object[] objSource = new object[] { 10, 'A', "HelloWorld", 2.69f, true };
int objCountToSelect = 3;
ComposerSelector<object> objSelector = new ComposerSelector<object>(objSource, objCountToSelect);
Assert.AreEqual(objSelector.SourceObjects, objSource);
Assert.AreEqual(objSelector.CountToSelect, objCountToSelect);
}
编译:失败
下面要做的就是在Selector<T>类中增加对应的属性,并在构造函数中增加赋值行为,来使测试可以通过。
Select<T> 类:
public T[] SourceObjects { get; private set; }
public int CountToSelect { get; private set; }
public Selector(T[] sourceObjects,int countToSelect){
this.SourceObjects = sourceObjects;
this.CountToSelect = countToSelect;
}
编译:成功
测试:通过
虽说这个时候代码覆盖率已经100%了(因为PremutationSelector的构造行为和这两个属性的相关操作在基类中已经被完成,因此已经被ComposerSelector的测试代码覆盖)
我仍旧固执的为PremutationSelector编写了测试代码(这让人很痛苦,一模一样的代码的复制黏贴,但这会更增加对其重构的决心。如果对重构抱有偏执,恭喜,这就是重构的原因之一,写测试代码遇到的让人蛋疼的事情,在编写功能代码的时候,几乎会100%遇到。)
其实,这个问题,早在编写构造函数的测试代码时候,就碰到了,但我仍旧把对代码的重构,放在了完成我们对修改的设计进行完整的编码后才进行的。
继续
5.ComposerSelector<T> 和 PermutationSelector<T> 对象,可以调用DoProcess方法来进行排列组合的运算
目前为止对DoProcess的行为,还是只定义为依据给定的数据,进行运算,并且把结果存入Result属性。
只需修改测试,调用DoProcess方法即可,可以发现,原来测试方法中的ComposerPropertiesTest和PremutationTest方法中已经做过了创建的操作,因此,只需要修改这个测试方法,加入调用方法即可。
编译:失败
目前的类中还没有DoProcess方法,肯定无法编译通过,只要简单的加入一个DoProcess方法就可以使其通过运行了。
目前 ComposerSelector<T> 和 PermutationSelector<T> 在DoProcess 方法上就有了差别,一个是求组合,一个是求排列。
DoProcess会在所有的Selector中都需要有的方法,将其放在抽象基类中,然后在子类对其实现。
Selector<T> 中新增
public abstract void DoProcess();
ComposerSelector<T> 和 PermutationSelector<T> 中新增
public override void DoProcess()
{
}
编译通过
测试成功
这个时候,可以查看代码覆盖率,仍旧是100%,但是,目前还不能够在测试代码中对各个对象的DoProcess进行Assert来确定其是否在正确的位置被正确的执行,目前还不去具体实现算法,因此这个问题后面再解决。
6、Selector<T> 包含了 Result 属性,这个属性用来存储 DoProcess 方法运算后的结果值
初步一看,这一条包含了两条信息在里面
一,Selector<T> 包含了 Result 属性
二,DoProcess 方法运算后会将结果存入Result
再分析一下,Result 应该是只能通过调用DoProcess方法来改变其值,对外部只读。(当然这里因为我们缺少用户方,也可以说我自己就是用户,因此,我可以自己按自己的想法来设定。如果在正式工作中,应该与用户沟通以确定各个细节的对外特性)
三,Result 的 set 方法应该是 private
继续分析,
ComposerSelector<T> 和 PermutationSelector<T> 在DoProcess方法执行之后,Result 的值会不同,也有一些情况会出现相同的值的情况,视SourceObjects和CountToSelect值而定。
依据我们的上面三条分析结果
一,Selector<T> 包含了 Result 属性
二,DoProcess 方法运算后会将结果存入Result
三,Result 的 set 方法应该是 private
只要对给对应的selector加入Result 的读取操作就可以了,而Result的写操作会在DoProcess被覆盖。
在ComposerTest和PermutationTest测试方法内增加对DoProcess方法的调用和对Result方法的读取
ComposerTest 类内
[TestMethod]
public void ComposerPropertiesTest() {
int[] intSource = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int intCountToSelect = 4;
ComposerSelector<int> intSelector = new ComposerSelector<int>(intSource, intCountToSelect);
intSelector.DoProcess();
Assert.AreEqual(intSelector.SourceObjects, intSource);
Assert.AreEqual(intSelector.CountToSelect, intCountToSelect);
Assert.IsNotNull(intSelector.Result);
object[] objSource = new object[] { 10, 'A', "HelloWorld", 2.69f, true };
int objCountToSelect = 3;
ComposerSelector<object> objSelector = new ComposerSelector<object>(objSource, objCountToSelect);
objSelector.DoProcess();
Assert.AreEqual(objSelector.SourceObjects, objSource);
Assert.AreEqual(objSelector.CountToSelect, objCountToSelect);
Assert.IsNotNull(objSelector.Result);
}
PermutationTest内代码相同(我现在有了更多的重复代码,你应该已经嗅出了bad smell了,重构的理由更加充分)
编译:失败
在Selector<T>中添加Result属性即可;
Selector<T> 类中添加
public List<T[]> Result { get; private set; }
编译:成功
测试会有两条失败信息:
未通过 ComposerPropertiesTest MathLibraryTest Assert.IsNotNull 失败。
未通过 PremutationPropertiesTest MathLibraryTest Assert.IsNotNull 失败。
这是并没有在DoProcess方法对结果进行赋值引发的测试失败。
这点我们在第二条已经考虑到了。
第二条并不改变测试代码,但是需要我们在DoProcess方法添加对Result的赋值来覆盖Result的set;
为了能够更好的测试,在DoProcess中,将SourceObjects存为Result中的一条结果,这样,就可以使用equals方法来测试Result是否被正确赋值
第三条无需多说吧
我们分别在ComposerSelector和PermutationSelector的DoProcess方法中加入伪实现的代码;
this.Result = new List<T[]>() { this.SourceObjects };
编译:失败
一点失误,由于子类的Result属性继承于基类,而对基类Result的set方法的private造成了子类也无法对其赋值。
只需要将基类内Result的set方法设为protected即可
修改代码
编译:通过
测试:通过
1、我需要创建一个类库,暂且命名为:MathLibrary
2、类库内包含 ComposerSelector<T> 和 PermutationSelector<T> 两个类
3、ComposerSelector<T> 和 PermutationSelector<T> 是 Selector<T> 的子类
4、Selector<T> 包含了 T[] SourceObjects 属性和 int CountToSelect 属性,来存储数据源和要从中取出的排列组合要包含的项目个数
5、ComposerSelector<T> 和 PermutationSelector<T> 对象,可以调用DoProcess方法来进行排列组合的运算
6、Selector<T> 包含了 Result 属性,这个属性用来存储 DoProcess 方法运算后的结果值
7、Selector<T> 包含 ResultCount 属性,返回结果包含的记录条数
DoProcess的功能尚未真正实现。
============待续================
到了这里,测试代码可以通过运行,而且覆盖率也很高 100% ,但是的测试代码内充满了让人恼怒的 bad smell 代码。
下一步,我并没有立即编写算法的代码,而是对测试代码和重构进行了简单的重构
这里我和一个朋友产生了争论,至今没有结果
对方说,我应该尽快地实现功能,在功能实现后再来进行重构,否则,我可能会耗费大量的工作在重构上而延缓了功能实现的时间(我同意,这的确会使功能实现的时间延后)
而我认为,这个时候,已经可以明显确定的设计上的缺陷,此时重构的成本最小,如果在完全实现功能后再重构,那么重构的工作量将会很大。
关于重构的时机,希望各位能够给些意见。
相关文章推荐
- TDD个人实践体会(C#)一
- TDD个人实践体会(C#)三
- TDD个人实践体会(C#)五
- TDD个人实践体会(C#)四
- TDD个人实践体会
- c# 使用ChartDirector绘图的一些个人体会
- c# 使用ChartDirector绘图的一些个人体会
- IT增值服务实践心得体会:企业客户的钱比个人客户好赚得多
- C#中事件处理的个人体会
- C#中事件处理的个人体会
- IT增值服务实践心得体会:企业客户的钱比个人客户好赚得多
- C#中事件处理的个人体会
- 个人对C++、JAVA和C#的体会
- C#转向C/C++的个人体会
- IT增值服务实践心得体会:企业客户的钱比个人客户好赚得多
- C#中事件处理的个人体会
- C#中事件处理的个人体会
- IT增值服务实践心得体会:企业客户的钱比个人客户好赚得多
- 学习Linux的版本选择--个人实践体会心得
- C#中事件处理的个人体会