1单元测试
你的RP是由你的程序质量决定的。
——阿超
这一章讲的是两人合作,既然程序是两个人写的,那就会出现一个人写的模块被另一个人写的模块调用的情况。很多误解、疏忽都发生在两个模块之间。如何能让自己写的模块尽量无懈可击?单元测试就是一个很有效的解决方案。
例子:我们写一个比较常用的类型,看看它的单元测试应该怎么写?比如在各种网站应用程序中都会用到的“用户”这一类型。谁自告奋勇上来表演一下写代码?小飞,好,请上台。
小飞创建了一个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
/// <summary> ///A test for User (string) ///</summary> [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(""); } [TestMethod()] [ExpectedException(typeof(ArgumentNullException))] public void ConstructorTestBlank() { User target = new User(" "); }
如果不修改类库中的代码,单元测试会报告这三个新的测试都失败了。
小飞对代码做了相应的修改。结果出了这样的错误,见代码清单11-5: