单元测试基础知识

作者:随飞 2008年3月7日

‡vډ »Äùøwww.netcsharp.cn:<åƒažõ

建议阅读时安装“微软雅黑”并开启ClearType效果

‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
前言‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
本文是阅读了《单元测试之道》一书后的笔记,也是公司安排本人进行单元测试培训的材料,原文是一个Powerpoint,故修改了下,并针对Visual studio 2005自带的单元测试做的一个整理,将其奉献出来,目的是供需要了解和学习单元测试的朋友们阅读。如有错误望指出。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
什么是单元测试?单元测试是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。例如,你可能把一个很大的值放入一个有序list 中去,然后确认该值出现在list 的尾部。或者,你可能会从字符串中删除匹配某种模式的字符,然后确认字符串确实不再包含这些字符了。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
执行单元测试,是为了证明某段代码的行为确实和开发者所期望的一致。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
为什么需要单元测试?当编写项目的时刻,如果我们假设底层的代码是正确无误的,那么先是高层代码中使用了底层代码;然后这些高层代码又被更高层的代码所使用,如此往复。当基本的底层代码不再可靠时,那么必需的改动就无法只局限在底层。虽然你可以修正底层的问题,但是这些对底层代码的修改必然会影响到高层代码。于是,一个对底层代码的修正,可能会导致对几乎所有代码的一连串改动,从而使修改越来越多,也越来越复杂。从而使整个项目也以失败告终。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
而单元测试的核心内涵:这个简单有效的技术就是为了令代码变得更加完美。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
什么是断言      Assertion(断言),它是一个简单的方法调用,用于判断某个语句是否为真。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
例如:‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
public void IsTrue(bool condtion){‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
if(!condition) abort();‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
}‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
应用则为:‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
int a=2;‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
IsTrue(a==2);‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
还可以编写更多的特定数据类型的断言。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
计划你的单元测试‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
当我们编写了一个如下的函数,它用于查找list中的最大值:static int Largest(int[] list);‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
所能想到的测试如下:‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
输入‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
预期结果‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
7,8,9‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
9‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
8,9,7‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
9‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
9,7,8‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
9‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
7,9,8,9‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
9‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
1‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
1‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
-9,-8,-7‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
-7‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
null‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Exception‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
创建单元测试 ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
在解决方案资源管理器中右击某个测试项目,或在 Visual Studio 代码编辑器中,右击要测试的命名空间、类或方法并选择“创建单元测试”。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
VsUnit 的各种断言Assert ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
在测试方法中,可以调用任意数量的 Assert 类方法,如 Assert.AreEqual()。Assert 类有很多方法可供选择,其中许多方法具有若干重载。 ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
CollectionAssert ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
使用 CollectionAssert 类可比较对象集合,也可验证一个或多个集合的状态。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
StringAssert ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
使用 StringAssert 类可对字符串进行比较。此类包含各种有用的方法,如 StringAssert.Contains、StringAssert.Matches 和 StringAssert.StartsWith。 ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
AssertFailedException ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
只要测试失败,就会引发 AssertFailedException 异常。如果测试超时,引发意外的异常,或包含生成了 Failed 结果的 Assert 语句,则该测试失败。 ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
AssertInconclusiveException (无结果的)‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
只要测试生成的结果为 Inconclusive,就会引发 AssertInconclusiveException。通常,向仍在处理的测试添加 Assert.Inconclusive 语句可指示该测试尚未准备好,不能运行。 ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
UnitTestAssertException ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
编写新的 Assert 异常类时使该类从基类 UnitTestAssertException 进行继承,可更方便地将异常标识为断言失败而非从测试或产品代码引发的意外异常。 ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
ExpectedExceptionAttribute ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
如果希望开发代码中的某方法引发异常,又想用测试方法来验证是否真的在该方法中引发了异常,则请用 ExpectedExceptionAttribute 属性来修饰测试方法。 ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
如:‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
        [TestMethod]‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
        [ExpectedException(typeof(ArgumentException),‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
          "userID 为 NULL 的异常检测.")]‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
        public void NullUserIdInConstructor()‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
        {‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
            LogonInfo logonInfo = new LogonInfo(null, "P@ss0word");‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
        }‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
单元测试的属性‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
除了单元测试方法的 [TestMethod()] 属性及其包容类的 [TestClass()] 属性之外,可使用其他属性启用特定的单元测试功能。在这些属性中,最主要的属性有 [TestInitialize()] 和 [TestCleanup()]。使用标记有 [TestInitialize()] 的方法对将要在其中运行单元测试的环境的各个方面进行准备;这样做的目的在于为单元测试的运行建立已知的状态。例如,可以使用 [TestInitialize()] 方法复制、更改或创建测试中将要使用的某些数据文件。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
在运行完某个测试后,可通过标记有 [TestCleanup()] 的方法将环境返回到已知状态;这可能意味着需要删除文件夹中的文件,或将某个数据库返回到已知状态。例如,在测试了订单录入应用程序中使用的某个方法后,可将库存数据库重置为初始状态。此外,建议您在 [TestCleanup()] 或 ClassCleanup 方法中使用清除代码,而不要在终结器方法(~Constructor)中使用此代码。从终结器方法引发的异常不会被捕捉到,并且会导致无法预料的结果。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
用于建立调用顺序的属性对于程序集:‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
在加载程序集之后以及卸载程序集之前,将调用 AssemblyInitializeAssemblyCleanup‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
对于类 :‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
在加载类之后以及卸载类之前,将调用 ClassInitialize ClassCleanup‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
对于测试方法 :‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
在每个测试方法加载以及卸载之前,将调用TestInitialize TestCleanup‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
什么是VsUnit 的TestContext 类‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
测试上下文类的属性存储有关当前测试运行的信息。例如,TestContext.DataRow 和 TestContext.DataConnection 属性包含测试用于数据驱动的单元测试的信息。 ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
其他用于对测试进行标识和排序的属性‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
测试配置类‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
用于生成报告的属性‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
用于专用访问器的类‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
测试哪些内容:Right-BICEPRight----------结果是否正确?‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
B---------------是否所有的边界条件都是正确的?‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
I----------------能查一下反向关联吗?‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
C---------------能用其他手段交叉检查一下结果吗?‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
E---------------你是否可以强制错误条件发生?‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
P---------------是否满足性能要求?‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
RIGHT:结果是否正确如果代码能运行正确,如何才知道它是正确的呢?‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
1、使用更明确的设计文档‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
2、真实环境数据‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
3、????‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
B:边界条件‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
尽可能的至少各种特殊或者意外的情况,测试程序是否能正常工作,如:‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 完全伪造或者不一致的输入数据,如叫做“(*@Q!&#?±的文件。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 格式错误的数据,如错误格式的邮件地址‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 空值或不完整的值‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 一些与意料中的合理值相去甚远的值,如年纪为10000‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 如果要求是一个不允许出现重复数值的list,但传入一个有重复数值的list‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 要求是一个有序的list,但传入一个无序的list‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 处理的顺序是错误的,或者与期望的次序不一致。如未登录系统就尝试打印。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
B:边界条件的-CORRECTConformance(一致性)值是否和预期的一致‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Ordering(顺序性)值是否应该的那样有序或者无序‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Range(区间性)值是否位于合理范围‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Reference(依赖性)代码是否引用了一些代码本身控制范围之外的资源‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Existence(存在性)值是否存在(是否非空,非零,在集合中等等)‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Cardinality(基数性)是否恰好有足够的值‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Time(相对或绝对的时间性)所有事情的发生是否有序?是否在正确的时间?是否恰好及时?‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
I:检查反向关联‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
通常一些结果可以使用反向的逻辑关系来验证它们是否正确,如:计算a*b的函数,测试方法如下:‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Public void UsingInverse(){‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
double x = MyMath.AB(4,4);‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Assert.AreEqual<double>(x,4*4);‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
}‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
C:使用其他手段实现交叉检查计算一个结果可以存在多个算法,同一个算法可以使用稳定的版本来校验新改进的版本,如:‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Public void UsingStd(){‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
    double number = 23214.01;‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
    double result1 = MyMath.旧方法(number1);‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
    double result2 = MyMath.新方法(number1);‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
    Assert.AreEqual<double>(result1,result2);‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
}‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
E:强制产生错误条件‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
真实运行环境各种出乎意料的事情都可能发生,如断电,断网等等。在测试中模拟这些情况可以使用Mock对象来实现。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
P:性能特性‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
如数据采集的功能,在十个网站上进行采集工作很正常,那么在1000个网站上或更多的网站上进行采集它的速度如何?是否写个单元测试?‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
如何才是一个好的单元测试好的测试应该具有的品质是:A-TRIP(合称)‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Automatic 自动化‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Thorough 彻底的‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Repeatable可重复‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Independent独立的‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Professional 专业的‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Automatic自动化‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
调用自动化‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
检查结果自动化‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Thorough 彻底的‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
测试所有可能会出现问题的情况,一个极端是,对于每行代码、代码可能到达的分支,每个可能抛出的异常等等,都可以作为测试的对象。另一个极端是,你仅仅测试最可能的情况---边界条件、残缺和畸形的数据等等。然而这些都基于项目需求的决策问题。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
这些所说的归纳为“代码覆盖率”。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Repeatable 可重复‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
测试应该独立于所有其他的测试,而且必须独立于周围的环境。目标只有一个,就是测试应该能够以任意的顺序一次又一次的运行,并且产生相同的结果。如果结果不同,则存在BUG。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Independent 独立的‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
编写测试时,确保一次只测试了一样东西,但并不表示一个TestMethod内只能使用一个Assert,而是一个测试函数应该专注于产品代码中的一个函数,或者组合起来并共同提供某个特性的一组函数。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Professional 专业的‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
测试代码必须同产品代码相同的风格来编写。这意味着你需要抽取出共同且重复的代码,并把它们放到一个功能类之中,从而可以复用;单元测试的代码一样讲究------维护封装,DRY原则,降低耦合。‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
何时需要Mock对象l 真实对象具有不可确定的行为(产生不可预测的结果,如股票的行情)‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 真实对象很难被创建(比如具体的web容器)‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 真实对象的某些行为很难触发(比如网络错误)‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 真实情况令程序的运行速度很慢‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 真实对象有用户界面‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 测试需要询问真实对象它是如何被调用的(比如测试可能需要验证某个回调函数是否被调用了)‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
l 真实对象实际上并不存在(当需要和其他开发小组,或者新的硬件系统打交道的时候,这是一个普遍的问题)‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
Mock对象的三个步骤1.‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
原型‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
ClassA调用 ClassB的Method() ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
2.‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
使用一个接口来描述这个对象‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
ClassA 通过接口调用ClassB的Method ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
3.‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
以测试为目的,在mock对象中实现这个接口 ‡vډ »Äùøwww.netcsharp.cn:<åƒažõ
全文完‡vډ »Äùøwww.netcsharp.cn:<åƒažõ