|
时间: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们那样几天甚至几周时间,完成的代码还总感到没有把握。 难点:做那些测试?然后,在做增加上方同级节点,增加子节点的时候,是否需要这么完整的测试?呵呵,我想并不一定。拷贝后改变一下就行。但是:代码若有拷贝的现象,实际上是重构的时机了。这就是类结构逐渐设计的过程。关于重构,下一步再说。
| |