1单元测试

你的RP是由你的程序质量决定的。

——阿超

这一章讲的是两人合作,既然程序是两个人写的,那就会出现一个人写的模块被另一个人写的模块调用的情况。很多误解、疏忽都发生在两个模块之间。如何能让自己写的模块尽量无懈可击?单元测试就是一个很有效的解决方案。

1.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所示)。

现代软件工程讲义 2 开发技术 - 单元测试 & 回归测试_private

11-2  创建单元测试项目

创建单元测试后,注意到在Solution Explorer中出现了三个新的文件(如图11-3所示)。

现代软件工程讲义 2 开发技术 - 单元测试 & 回归测试_private_02

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