[转贴]Vs2005 Tdd代码编写过程详细工作记录 from dotnettools

[转贴]Vs2005 Tdd代码编写过程详细工作记录 from dotnettools
楼主  2005-10-20, 10:10 上午 Reply Quote
时间:10月2日 22.30
目标:一个小时,做一个简版的继承自TreeView的控件,实现树型结构的增加同级下方节点。主要用来说明使用Vs2005的unit test测试驱动开发的过程。
计划使用:1小时
实际结束:0.15分

1、创建Winform控件工程:
创建一个Winform控件工程,将其改名为DataTreeView,将其命名空间改为KitBag.Component.DataTreeView,将DataTreeView类改作从TreeView继承。
注:这是准备工作,不符合先写测试再写代码的“规则”。做人不可僵化,我这么做是为了充分利用“自动生成测试工程”的特性,避免繁琐的工作。

2、创建测试工程:
选中DataTreeView类的名字,在快捷菜单中选择“创建新的测试工程”,在其成员中只勾选构造方法。将新的测试工程命名为TestDataTreeView,然后,删除掉构造方法的测试方法。

3、测试什么?
首先,我们的目的是为TreeView当前选中的节点,增加同级的下方节点。那么,我们需要测试什么呢?首先我们必须测试是否成功的增加了节点,然后,我们要测试新增加的节点是否成为当前选中节点

4、编写测试代码:
创建一个DataTreeView对象,为其增加两个节点,名称分别为“节点1”和节点2,选中第一个节点。
执行增加节点的方法。
[TestMethod()]
public void AddNextNodeTest()
{
//准备工作:准备好测试数据
DataTreeView target = new DataTreeView();
target.Nodes.Add("节点1");
target.Nodes.Add("节点2");
target.SelectedNode = target.Nodes[0];

//执行要测试的方法
target.AddNextNode();
}

5、运行测试,编译不能通过。
当然,我们还没有那个方法。现在先加进去,测试马上通过。
这有些极端,事实上在Vs2005中,应该先将准备测试的方法体写好,这样即使编写测试代码,也能使用代码智能输入。

6、测试1:是否正常的添加了新的节点,我们通过检查节点的总数来测试。
//测试节点总数是否为3
Assert.Equals(target.Nodes.Count, 3);
通过,这没道理的,因为我们并没有写任何代码。呵呵,仔细看了看,测试代码写错了,应该使用
Assert.AreEqual(3,target.Nodes.Count);
这时候提示:期望是3,实际上是2。也就是说,我们期望有三个节点,但实际上只有2个,我们并没有增加节点。

7、写代码1:增加节点。
this.Nodes.Add("新节点");
这样,执行AddNextNode()方法,必定会增加一个节点。
运行测试,没有通过。
需要重新编译控件工程,测试是针对dll而非代码的。重新编译DataTreeView工程,测试通过。

8、测试2:增加的节点,应该在选中的节点的下方。
选中的节点,index为0,因此,index为1的节点,其Text应该是"新节点",增加一个测试:
//测试新节点是否增加到正确的位置
Assert.AreEqual("新节点",target.Nodes[1].Text);
测试没有通过,看看代码:期望为“新节点”,实际为“节点2”。说明我们增加的节点并不在其应该出现的位置。
Nodes.Add方法,实际上是在最后位置增加节点。

9、写代码2:修改代码。
this.Nodes.Insert(this.SelectedNode.Index+1,"新节点");
测试通过。这说明我们将新节点添加到了合适的位置。

10、测试3:如果nodes中不包括任何节点,我们执行这个方法,是否也是正常的?
//准备工作,清除掉所有节点
target.Nodes.Clear();
target.AddNextNode();

//测试不包括任何节点的情况下,该方法是否正常工作
Assert.AreEqual(1, target.Nodes.Count);
Assert.AreEqual("新节点", target.Nodes[0].Text);
测试失败:测试出现异常,这时候是“测试中断”,这也视为Fail
看看异常信息。显然,由于没有任何节点,我们代码中的selectNode是null,我们使用一个为null的对象的属性,显然不行啊。
11、写代码3:
if (this.SelectedNode.==null)
this.Nodes.Add("新节点");
else
this.Nodes.Insert(this.SelectedNode.Index+1,"新节点");
rebuild之后,运行测试代码,先提示“虚拟内存不足”,呵呵,为了ghost方便,我将虚拟内存设为0。这时候测试也是“abort”,中断了。调整虚拟内存设置后,继续运行测试,依然没有通过。
刚才rebuild的时候出现语法错误,并没有成功。修改,rebuild,再测试。pass了。

12、要点:修改后的代码,也能通过前面的测试。这确保我们不至于修改了这里,而导致先前的测试无法通过。

13、测试4:如果当前选中的节点,不是根节点,情况如何?
注意,这里的测试代码我们写在最初的测试代码之后,写在测试不含任何节点的代码之前。
//测试当目前选中的节点不是根节点的时候,是否能正常工作
//首先为当前节点增加一个子节点,并将该子节点设为当前节点
target.SelectedNode=target.SelectedNode.Nodes.Add("不是根节点的节点");
target.AddNextNode();
//这时候第二个根节点应该有两个子节点
Assert.AreEqual(2, target.Nodes[1].Nodes.Count);
测试失败,但奇怪的是,我们期望的是2,应该至少有一个子节点的。但测试结果是:实际上为0。
再看看,测试代码错了
1、在测试代码运行之前,当前节点应为0。我们在与1比较
2、由于我们要求增加节点之后,将当前节点设到新增的节点上,因此,这里使用“当前节点”,会导致测试结果相互影响,这是后话,但在这里解决是最好的。
将测试代码改为:
//测试当目前选中的节点不是根节点的时候,是否能正常工作
//首先为当前节点增加一个子节点,并将该子节点设为当前节点
target.SelectedNode=target.Nodes[0].Nodes.Add("不是根节点的节点");
target.AddNextNode();
//这时候第二个根节点应该有两个子节点
Assert.AreEqual(2, target.Nodes[0].Nodes.Count);
这时候测试没有通过,但结果正常了:期望2,实际为1。

14、写代码4:我们再来修改代码,以通过测试。
if (this.SelectedNode == null)
this.Nodes.Add("新节点");
else
this.SelectedNode.Parent.Nodes.Insert(this.SelectedNode.Index+1,"新节点");

这时候测试不仅没有通过,而且出现和上次相同的异常:
System.NullReferenceException: Object reference not set to an instance of an object.
看看代码,呵呵,如果选中节点是根节点,它是根本没有父节点的。怎么办?
修改:
if (this.SelectedNode == null)
this.Nodes.Add("新节点");
else
if (SelectedNode.Parent == null)
this.Nodes.Insert(SelectedNode.Index + 1, "新节点");
else
this.SelectedNode.Parent.Nodes.Insert(this.SelectedNode.Index+1,"新节点");
好,测试通过。

15、测试5:由于要求增加节点后,必须将当前节点设为刚增加的节点,那么,我们在刚才测试代码后,增加1个测试。
//测试增加的节点,是否变成了当前节点
Assert.AreEqual("新节点", target.SelectedNode);
Assert.AreEqual("不是根节点的节点", target.SelectedNode.PrevNode.Text);
不能通过,期望“新节点”,实际值为“不是根节点的节点”。

16、将代码改为:
if (this.SelectedNode == null)
this.SelectedNode = this.Nodes.Add("新节点");
else
if (SelectedNode.Parent == null)
this.SelectedNode = this.Nodes.Insert(SelectedNode.Index + 1, "新节点");
else
this.SelectedNode = this.SelectedNode.Parent.Nodes.Insert(this.SelectedNode.Index+1,"新节点");
this.SelectedNode.BeginEdit();
测试没有通过,错误:
Assert.AreEqual failed. Expected:<新节点 (System.String)>, Actual:<TreeNode: 新节点 (System.Windows.Forms.TreeNode)>.
原来是第一行测试代码错误,改为:
Assert.AreEqual("新节点", target.SelectedNode.Text);
忘了加上Text了;

好,测试完全通过,我们基本上实现了
1、创建一个控件,继承于treeview
2、增加同级下方节点:
treeview不含任何节点
treeview根节点上添加
treeview非根节点上添加
三种情况下,都添加成功并且增加的位置正确。
我们也实现了,增加节点后即直接的将其设为当前的节点。
3、通过如此众多的测试做下来,我们对自己实现的这个方法,其正确性和稳定性,有较充足的信心。
虽然为了说明测试驱动开发的过程,我是一个个的在写单元测试,这导致过程显得冗长。实际工作中,估计会先写2-4个测试,再实现之。
4、对于vs2005的单元测试,几点要注意的:
首先,还需进一步了解Assert等在测试工程中应用的方法含义;
AreEqual方法的期望值放在前面,实际值放在后面;
测试中发生异常的时候,测试会失败,并提示异常信息。
修改了被测试代码之后,必须先编译之后再跑单元测试,因为单元测试是针对编译后的dll的。
6、接下来做什么?
老实说,写出来的代码,虽然质量较好且稳定,但确实难看。仅仅这个方法,里面显然有很多重复的味道。如果不赶时间,可以进行重构,这时候,通过全部单元测试,就成了重构是否成功的标准。
我们通过重构改善代码的设计,甚至性能;但前面TDD的过程,作为副产品的单元测试,则确保了正确性。
先解决正确性,再解决“代码优雅”的问题,这就将同时作的事情拆分成了两个阶段,每个阶段分别关注不同的侧面。
7、体验:
这样子写代码,感觉时间使用的效率不错。每个阶段都有一个目标,这个目标就是通过测试。写测试的时候,实际上是在预想产品的行为特点,这也是强迫做类设计的过程。
1.21分结束。前后历经3个小时。
去掉:1、学习单元测试用法;2、熟悉vs2005单元测试过程;3、记录的过程。
估计就是1个小时吧?如果一个小时完成这个方法,一个类的开发效率,将以小时来度量,而非dx们那样几天甚至几周时间,完成的代码还总感到没有把握。
难点:做那些测试?然后,在做增加上方同级节点,增加子节点的时候,是否需要这么完整的测试?呵呵,我想并不一定。拷贝后改变一下就行。但是:代码若有拷贝的现象,实际上是重构的时机了。这就是类结构逐渐设计的过程。关于重构,下一步再说。
posted @ 2005-12-27 11:16  torome  阅读(432)  评论(0编辑  收藏  举报