你的RP是由你的程序质量决定的。
——阿超
这一章讲的是两人合作,既然程序是两个人写的,那就会出现一个人写的模块被另一个人写的模块调用的情况。很多误解、疏忽都发生在两个模块之间。如何能让自己写的模块尽量无懈可击?单元测试就是一个很有效的解决方案。
1、用VSTS写单元测试
例子:我们写一个比较常用的类型,看看它的单元测试应该怎么写?比如在各种网站应用程序中都会用到的“用户”这一类型。谁自告奋勇上来表演一下写代码?小飞,好,请上台。
小飞创建了一个C#的类库(Class Library),并写了如代码清单11-1的代码:
代码清单11-1
namespace DemoUser
{
public class User
{
public User(string userEmail)
{
m_email = userEmail;
}
private string m_email; //user email as user id
}
}
好,现在右键选中User,就可以看到“Create Unit Tests”的菜单,这样就可以创建新的单元测试(如图11-2所示)。
图11-2 创建单元测试项目
创建单元测试后,注意到在Solution Explorer中出现了三个新的文件(如图11-3所示)。
图11-3 新的单元测试文件
Class1.cs是程序的文件,而Class1Test.cs是与之对应的单元测试文件。
DemoUser.vsmdi:测试管理文件。
Localtestrun.testrunconfig:本地测试运行设置文件。
如何管理设置文件呢?右键再选属性(Property)并不对。你得双击文件才能进入管理及设置界面。在设置界面中,你可以让单元测试产生“demouser.dll”的代码覆盖报告。
注意在单元测试中,VSTS自动为你生成了测试的骨架,但是你还是要自己做不少事情,最起码要把那些//TODO的事情给做了(如代码清单11-2所示)。在这个时候,单元测试还都是用的Assert. Inconclusive,表明这是一个未经验证的单元测试。
代码清单11-2
///
///A test for User (string)
///
[TestMethod()]
public void ConstructorTest()
{
string userEmail = null; // TODO: Initialize to an appropriate
// value
User target = new User(userEmail);
// TODO: Implement code to verify target
Assert.Inconclusive("TODO: Implement code to verify target");
}
进行简单的修改后,我们得到了一个如代码清单11-3正式的单元测试:
代码清单11-3
[TestMethod()]
public void ConstructorTest()
{
string userEmail = "someone@somewhere.com";
User target = new User(userEmail);
Assert.IsTrue(target != null);
}
//我们还可以进一步测试E-mail是否的确是保存在User类型中。
解释单元测试的结构
从上面这个例子可以看到创建单元测试函数的主要步骤:
(1)设置数据(一个假想的正确的E-mail地址);
(2)使用被测试类型的功能(用E-mail地址来创建一个User类的实体);
(3)比较实际结果和预期的结果(Assert.IsTrue(target != null);)。
现在可以运行单元测试了,同时可以看看代码覆盖报告“code coverage report”,代码百分之百地都被覆盖了。
当然这时候的代码还有很多情况没有处理,同学们在台下杂曰——
处理空的字符串,长度为零的字符串,都是空格的串……
小飞熟练地用Copy/Paste又写了下面的三个测试,如代码清单11-4所示。
代码清单11-4
[TestMethod()]
[ExpectedException(typeof (ArgumentNullException))]
public void ConstructorTestNull()
{
User target = new User(null);
}
[TestMethod()]
[ExpectedException(typeof(ArgumentException))]
public void ConstructorTestEmpty()
{
User target = new User("");