下面我将编写一个 IntegerList 类来介绍事先测试开发的工作原理。IntegerList 是 ArrayList 类的变体,用于在本地存储整数,因此不存在装箱和取消装箱的开销。
第一步是创建一个控制台项目,并向其中添加一个 IntegerList.cs 源文件。要连接 nUnit 框架,需要添加对 nUnit 框架的引用。在我的系统中,它们位于 d:\program files\nUnit v2.0\bin 。
第二步是花些时间考虑如何对该类进行测试。这与确定类应该具备哪些功能的过程类似,但重点放在功能的特定用途(将值 1 添加到列表并检查是否成功),而不是功能本身(将一个项目添加到列表)。要生成此类,我们首先要提供一个要使用的测试列表:
- 测试该类可以构造
- 将两个整数添加到列表,并确保数目和项目都正确。
- 执行同一操作,但针对更多的项目。
- 将此列表转换为一个字符串。
- 使用 foreach 枚举此列表。
此示例从某种程度上代表了我开始时的想法,即希望这个类执行的操作。多数类一次只会创建一小部分,测试应随着类的增长而添加。
现在我可以开始了。我创建一个名为 IntegerListTest.cs 的新 C# 类文件,用于存放所有测试。下面是包含第一个测试的文件: using System;
using System.Collections;
using NUnit.Framework;
namespace IntegerList
{
/// <summary>
/// IntegerClassTest 的摘要说明。
/// </summary>
[TestFixture]
public class IntegerClassTest
{
[Test]
public void ListCreation()
{
IntegerList list = new IntegerList();
Assertion.AssertNotNull(list);
}
}
}
[TestFixture] 属性将此类标记为测试类,[Test] 属性将 ListCreation() 方法标记为测试方法。在此方法中,我创建了一个列表,然后使用 Assertion 类测试对象 gets 已经创建。
我启动 nUnit GUI 测试程序,打开可执行文件,并执行这些测试。将获得如下显示。
图 1:显示测试结果的 nUnit GUI
这表明所有测试都已通过。现在我想添加一些真实功能。第一个操作就是向列表中添加一个整数。此测试如下所示: [Test]
public void TestSimpleAdd()
{
IntegerList list = new IntegerList();
list.Add(5);
list.Add(10);
Assertion.AssertEquals(2, list.Count);
Assertion.AssertEquals(5, list[0]);
Assertion.AssertEquals(10, list[1]);
}
在此测试中,我选择同时测试两个操作:
- 列表正确维护 Count 属性。
- 列表可以包含两个项。
某些测试驱动开发的倡议者提倡测试应尽可能只测试数目,但是如果只测试数目而不测试项目,这对于我而言有些不可思议,因此我所选择的是两者一起测试。
编译这段代码时,由于 IntegerList 类中没有方法,因此编译失败,为此我加上以下代码进行编译: public int Count
{
get
{
return -1;
}
}
public void Add(int value)
{
}
public int this[int index]
{
get
{
return -1;
}
}
然后我返回并运行测试,这时它们显示为红色,表示测试失败。这很好,因为它意味着测试实际上已测试出程序错误。现在我可以执行此实现。我可以做些简单的工作,尽管这样做效率不是很高: public int Count
{
get
{
return elements.Length;
}
}
public void Add(int value)
{
int newIndex;
if (elements != null)
{
int[] newElements = new int[elements.Length + 1];
for (int index = 0; index < elements.Length;
index++)
{
newElements[index] = elements[index];
}
newIndex = elements.Length;
elements = newElements;
}
else
{
elements = new int[1];
newIndex = 0;
}
elements[newIndex] = value;
}
public int this[int index]
{
get
{
return elements[index];
}
}
我现在已经完成类的一小部分,并已经编写了可确保其正常工作的测试,但我仅仅测试了项目中很少的一部分。接下来,我要编写一个用于检查 1000 个项的测试: [Test]
public void TestOneThousandItems()
{
list = new IntegerList();
for (int i = 0; i < 1000; i++)
{
list.Add(i);
}
Assertion.AssertEquals(1000, list.Count);
for (int i = 0; i < 1000; i++)
{
Assertion.AssertEquals(i, list[i]);
}
}
此测试运行正常,因此无须进行任何更改。
添加 ToString() 方法
接下来,我将添加测试代码,以测试 ToString() 能否正常运行: [Test]
public void TestToString()
{
IntegerList list = new IntegerList();
list.Add(5);
list.Add(10);
string t = list.ToString();
Assertion.AssertEquals("5, 10", t.ToString());
}
失败了,没关系。以下代码可以使其通过: public override string ToString()
{
string[] items = new string[elements.Length];
for (int index = 0; index < elements.Length; index++)
{
items[index] = elements[index].ToString();
}
return String.Join(", ", items);
}
启用 Foreach
许多用户希望能够使用 foreach 遍历我的列表。为此,我需要在类中实现 Ienumerable,并定义一个单独的用于实现 Ienumerable 的类。第一步,测试: [Test]
public void TestForeach()
{
IntegerList list = new IntegerList();
list.Add(5);
list.Add(10);
list.Add(15);
list.Add(20);
ArrayList items = new ArrayList();
foreach (int value in list)
{
items.Add(value);
}
Assertion.AssertEquals("Count", 4, items.Count);
Assertion.AssertEquals("index 0", 5, items[0]);
Assertion.AssertEquals("index 1", 10, items[1]);
Assertion.AssertEquals("index 2", 15, items[2]);
Assertion.AssertEquals("index 3", 20, items[3]);
}
我还通过 IntegerList 实现 IEnumerable: public IEnumerator GetEnumerator()
{
return null;
}
运行测试时,此代码生成异常。为了正确地实现此功能,我将使用一个嵌套类作为枚举器。 class IntegerListEnumerator: IEnumerator
{
IntegerList list;
int index = -1;
public IntegerListEnumerator(IntegerList list)
{
this.list = list;
}
public bool MoveNext()
{
index++;
if (index == list.Count)
return(false);
else
return(true);
}
public object Current
{
get
{
return(list[index]);
}
}
public void Reset()
{
index = -1;
}
}
此类将一个指针传递给 IntegerList 对象,然后只返回此对象中的元素。
这样,便可以对列表执行 foreach 操作,但遗憾的是 Current 属性属于对象类型,这意味着每个值将被装箱才能将其返回。此问题可采用一种基于模式的方法加以解决,此方法酷似当前方法,但它通过 GetEnumerator() 返回一个真正的类(而非 IEnumerator),且此类中的 Current 属性为 int 类型。
然而执行此操作后,我要确保在不支持该模式的语言中仍然可以使用这种基于接口的方法。我将复制编写的上一个测试并修改 foreach 以转换为接口: foreach (int value in (IEnumerable) list)
只需少许改动,列表即可在两种情况下正常运行。请查看代码样例以获取更多细节和更多测试。
出处:Microsoft
责任编辑:蓝色
上一页 事先测试开发 下一页
◎进入论坛网络编程版块参加讨论
|