第一章
简介
在一个软件项目实施时候,总有这么多各种各样不同种类的测试可以实施。有些测试甚至需要最终用户来涉入进行测试。另外的一些形式可能 需要团队献身于质量保证事业或者其他的利用昂贵资源的途径。
但是这些并不是我们打算讨论的。
取而代之的,我们打算讨论单元测试。一个必要的,但是又经常被误解的,实现成功项目的途径。单元测试确实一点也不昂贵,非常简便的方法来更好,更快的产生代码。
很多组织在项目时间安排计划的高压下对测试进行裁剪,只是在项目尾期进行验收测试。
很多程序员感觉到测试是令人讨厌的。
每个人同意更多的测试时需要的,就像每个人认为你应该吃了你的绿菜花,戒烟,获取更好的休息,并且有规律的锻炼。但这并不意味着这是我们确实要做的事情。
但是单元测试笔这些更应该实施,当你会把单元测试考虑成在家里的绿菜花一样鸡肋时,我们要在这里告诉你它更像甜果酱让事情变起来更好。单元测试不是被设计用来实现企业的最终质量规范的;他不是最终用户、经理、团队领导的工具。单元测试时被程序员实施,是为了程序员实施的.这是我们程序员单独的利益,来让我们做的更简单更好。
简单的来说,单独的单元测试可以带来你成功和失败的差别。
1.1自信的编写代码
自信并不可靠,单元测试策略才是可靠的
1.2什么是单元测试?
单元测试时由程序员编写的,一小段特殊区域和功能的用来测试代码的代码。通常一段单元测试练习一些特定的方法在特定的环境下。例如,你要在一个排序了的队列中加个很大的值,那么确认这个值在队列的最后面显示出来。或者你可以删除一个字符串的样式并且确认它是否已经被去掉。单元测试被实施证明一段代码确实实现了开发者需要它实现的功能。
问题仍然会存在因为最后决定产品是否合格的是最终用户和客户:验收测试才是最终决定是否合格的。我们在单元测试中不关心,我们的产品是否被验收,是否能达到性能要求。所有我们要做的就是确认我们的代码达到了我们预定的要求,所以我们要测试非常小而且孤立的功能组件。通过完成个人预期工作目标来建立信心,我们可以继续进行集成和系统测试工作。
换句话说,如果我们连对自己代码实现预定功能的信心都没有的话,那其他测试简直是在浪费时间。
1.3为什么我没有单元测试感到麻烦?
单元测试可以使你的生活更加简单。它可以让你的设计更好更便捷减少你调试程序的时间。
在我们上面阐述的故事中,Pat碰到的问题是以为底层代码能正常工作,所以继续在上层代码中使用底层代码。对于任何代码都没有获得一个合理的信息,Pat因为构建了一个不可靠的计划的设想,底层出现了一个细微的错误,那么整个事情都会轰然倒塌。
当基础的,底层的代码不可靠的时候,那些必须修改的不只是在底层,你修改最底层的问题,但是这会影响高层的代码,那些同样也需要修改。修改开始贯穿整个代码,整个项目轰然倒塌。
Pat总是说“这不可能”或者是“我不知道这为什么会发生”。如果你发现你自己有这类想法,那么可能你对你的代码没有很好的自信你不清楚的明白这是如何工作的。
为了获得那种Dale所具备的对代码的自信,你应该自己询问代码它是如何工作的,并且检查这个结果是你所预料的。
用嘴简单的思想来描述单元测试的核心:这是最简单最高效的编码技术。
1.4我需要完成什么?
要实施单元测试时很简单的,因为它很有乐趣,但是在最后的时间我们仍然需要为了客户和最终用户的需求生产代码,因此我们必须知道我们单元测试的目标。
它确实做我需要的吗?
基本上,你需要回答如下问题:“这些代码满足我的需要吗?”当需求不被关注的时候,这个代码可能处理一些错误的事情,但是这其中有个被分割练习。你想要这些代码来证明你确实做到了需要做的效果。
它会一直做我想要的吗?
很多开发者为了证明他们确实是做过测试的只写一种测试用例。通过代码在代码完美的地方运用一个正确的路径。
但是当然,现实中并不是所有东西都与预料中一样的:抛出溢出,磁盘控件满,网络断开,缓存溢出,还有我们写的BUG。那是软件开发过程中的工程部分。土木工程师必须考虑桥的负重,强风的影响,地震,洪水,等等。电力工程师考虑搞频繁的偏移,突增的瞬间电压,噪音等。
你不需要像测一个桥一样的在天气晴朗的时候把一辆车开到桥当中,这也是不完全的。相似的,在保证代码能做你想要它做的之上,你需要保证代码正常工作的稳定性,甚至在大风天气,网络阻塞或者参数被怀疑,磁盘满等情况。
我能依靠它吗?
你不能依赖的代码是无用的。更糟的,代码值得你信任的代码你会消耗大量的时间去追踪和调试它。几乎没有任何一个项目可以经得起消耗时间,所以你想要避免“进一步退两步”的方式,并且保持持续前进。
没人能写出完美的代码,没关系,只要你明白其中存在的问题。大多数严重并且造成惨重后果的在软件运行过程那个中发生的软件错误在知道软件局限性时候就能很简单的避免。比如,阿莲娜5火箭的软件中重用了一个之前火箭的软件包,而这个软件包又不能适应新型火箭的庞大数据量。它只飞了40秒钟,却花了500,000,000。
我们想要依靠我们所写的代码,但是必须很好的知道它的能力和局限性。
它能验证我的意图吗?
单元测试一个最好的原因之一是它能帮助你把你的意图和代码联系起来。实际上,一个单元测试就是一个可执行文件,在各种环境下展现出你的代码是否实现了你的意图。
相同测试组的成员可以通过测试用例看你的代码是如何工作的。
你如何做单元测试?
单元测试是可以很基础的简单的被实用。但是还有有些准则和通用的步骤你可以遵从使之变得更加简单有效。
第一步在编写代码之前就决定如何来测试这些方法。至少一个粗略的关于流程的想法,你在执行代码之前或者同时编写测试代码。
下一步,你运行测试代码,和整个系统其他部分的测试代码,或者整个系统测试代码如果那些可以很快的被关联完成。最重要的是每次测试都是所有测试都运行通过,不会是新的一个。你想要避免任何附属伤害和任何即时的问题。
任何测试都需要判断通过与否。
不进行单元测试的理由
(1)编写测试用了太多的时间
很多人认为单元测试会很消耗他们的时间,那么只能说他们对自己产品的质量要求不高,捣糨糊啊,一般这些仁兄都是写好代码测试那一仍,其实不然,如若公司对质量要求高些,如若对单元测试有要求,完全不应该这样,一开始拿到需求急忙写代码,全部写完了再开始想想怎么设计单元测试,这法子,会有效率就怪了,正确的方法是在设计代码块功能的时候,单元测试用例就应该已经设计完全了。若对于每个代码块都有完善的单元测试体系,那么对于快速定位和调试都是非常有帮助的。
(2)测试将会花费太多的时间
这种情况下,可以把长时间测试用例和短时间测试用例分离,长时间跑的测试在特定时间一次跑完,短时间的可以集成集中跑。
(3)测试我的代码不是我的工作
当测试和QA组发现要在你的代码里发现错误很难,那么你的声誉将很快传开—你的工作也危险了!
为什么不使用编译器代替
如下代码
public void Addit(Object anObject) f List myList = new List();
myList.Add(anObject);
myList.Add(anObject);
// more code...
}
连续两次ADD(anObject)
编译器不会报错
第二章 你的第一个单元测试
单元测试就是一小段测试另外一段代码是否正常工作的代码。
你如何做那个。
为了检查代码,你使用一个假设,用一个很简单的方法验证有些东西是正确的。比如,IsTrue检查条件是否正确的。
public void IsTrue(bool condition)
{
if(!condition)
{
abort();
}
}
当然这个方法不只能用来检查条件是否正确,而且能用来检查数据之间是否相等。
int a=2;
IsTrue(a==2);
判断两个参数相等:
public void AreEqual(int a,int b){
IsTrue(a==b);
}
2.1 计划测试
我们将要开始一个简单的例子,一个简单的,静态方法来设计寻找一列数据中最大的数字:
static int Largest(int[] list);
另外的话说,给一列数字[7,8,9].这个方法返回9.这是很符合常理的一个测试用例。但是,另外的测试用例你如何想?
你要写多少测试用例?
下面很清晰地给出了测试思想。
下面数列的关键不是它给出什么,而是给出的测试思想(我们写出的你输入什么输出什么)。
[7,8,9]—>9
[8,9,7]—>9
[9,7,8]—>9
当有重复的最大的数字时候将会发生什么?
[7,9,8,9]—>9
因为只存在int类型,没有objects类型,所以你可能不会关系9返回的是什么类型。
那么当只有1个数字的时候,它将会是什么?
[1]—>1
并且当出现负数时候又会发生什么情况:
[-9,-8,-7]—>-7
可以很很简单的看出来-7比-9大。
为了使这些更集中,我们写一个真实的最大方法并且测试它
Line 1 public class Cmp {
- ///
- /// <summary>
5 /// Return the largest element in a list.
- /// </summary>
- /// <param name="list"> A list of integers </param>
- /// <returns>
- /// The largest number in the given list
10 /// </returns>
- ///
public static int Largest(int[] list) { int index, max=Int32.MaxValue;
for (index = 0; index < list.Length-1; index++) {
15 if (list[index] > max){ - max = list[index];
}
} return max;
20 }
}
现在应该对单元测试的概念有所了解了,下面我们将要用Nunit框架来测试一下。
2.2测试一个简单的程序
首先,我们开始一个简单的例子用一连串简单的有不相等数字的数组。下面是完成的测试类。我们下面将要讲解测试类;但是现在,只是集中讲解声明的语句:
using NUnit.Framework;
[TestFixture]
public class TestLargest{
[Test]
public void LargestOf3(){
Assert.AreEqual(9,Cmp.Largest(new int[]{8,9,7}));
}
}
2.3用NUnit来运行测试
NUnit是免费的灵活的开源的单元测试框架。它可以作为一个C#的源代码来让你编译运行装载它,并且还可以作为一个Microsoft Installer(MSI) 文件。
1.安装,最简单的方法是用NUnit来运行MSI文件。
2.下一步,你需要编译我们展现出来的代码。如果你要使用VisualStudio,为实例代码建立一个空工程命名Class Library。我们要测试的文件则命名为Largest.cs,我们新的测试代码则命名为TestLargest.cs.
3.注意测试中中用到NUnit.Framework;你必须加一个索引到nunit.framework.dll为了编译这段代码.在Visual Studio中,在主菜单中选择“Project”,然后选择“Add Reference. . .”,到了这步,在.net表格中选择“nunit.framework”,并且按下"select"按钮来添加dll到控件列表中。点击OK按钮,现在你可以在项目中使用nunit.framework中的函数了。
4.和往常一样新建一个你的工程。现在你获得一个全局分布。但是这只是一个字典。我们怎样来运行它呢?
Test Runner来做这件事。
有3主要地方法来用TestRunner
1. NUnit GUI “C:n> set "PATH=%PATH%;C:nProgram FilesnNunit V2.2nbin"”
2. NUnit command line
3. Add-in to Vistual Studio
运行这个实例
现在可以运行上面写的那个用例
运行结果如下:
Failures:
1) TestLargest.LargestOf3 :
expected:<9>
but was:<2147483647>
at TestLargest.LargestOf3() in c:ntestlargest.cs:line 6
那个并不会像预期的一样运行。会报出错误,并且定位到错误位于哪行。
用NUnit来写TEST
测试代码和产品代码分开
如下:
TestAccount.cs Account.cs
CreateSimpleAccount()
CreateDefaultAccount() —> CreateAccount()
CreateDupAccount()
(Internal Only) (Delivered)
测试代码必须被写了做如下事情:
建立所有测试需要的条件(建立任何需要的类,分配任何需要的资源,等等)
调用被测试的方法
证明被测试的方法是和预期一致的
但测试清除自己
你写测试代码并且编译它在正常的风格下。当你想在你的工程里要任何其他大小的源代码时,它可能用其他额外的字典,但是另外的这里没有魔法-这只是正交代码。
当是执行代码的时候,记住你不能真实的那样直接运行产品代码;至少,不是和一个用户用一样。取而代之 ,你可以运行操作测试代码,在非常小心的控制条件之下。
现在,尽管你可以在最初开始写所有你的测试,这些不是浪费时间的。
3.2 NUnit 声明
1.AreEqual
Assert.AreEqual(expected,actual[,string message])
//判断结果与预期是否相等
报告的结果如下:
Assert.AreEqia;(expected,actual,tolerance[,string message])
2.IsNull
Assert.IsNull(object [, string message])
Assert.IsNotNull(object [, string message])
声明给的对象是否属于空
3.AreSame
Assert.AreSame(expected, actual [, string message])
声明是否同类型
4.IsTrue
Assert.IsTrue(bool condition [, string message])
判断返回条件是否为真
5.Fail
Assert.Fail([string message])
测试立即失败。
3.3 NUnit 框架
首先:通过Globally assembly cath调用using NUnit.Framework;
NUnit框架提供我们需要的单元测试的功能。
下一步,我们要定义一个类:每一个类包含着测试用例状态属性的注释。必须被声明为共有的,它必须有一个共有的,没有参数和构造器的。
最终,这个测试类包含单个带有测试属性的测试方法。
using NUnit.Framework;
[TestFixture]
public class TestSimple{
[Test]
public void LargestOf3(){
Assert.AreEqual(9,Cmp.Largest(new int[]{8,9,7}));
Assert.AreEqual(100,Cmp.Largest(new int[]{100,4,25}));
Assert.AreEqual(64,Cmp.Largest(new int[]{1,64,38}));
}
}
分类测试
NUnit 提供一个简单的方法分类来标记运行不同的测试方法和 。分类的名字由你随意决定。你可以分配给你想要测试的方法一个分类,然后当你要测试的时候可以根据这个分类运行它。
建议在你的测试用例之间使用(short和long);
using NUnit.Framework;
using NUnit.Core;
[TestFixture]
public class TestShortestPath{
[Test,Category("Short")]
public void User5Cities(){
TSP tsp = new TSP();
Assert.AreEqual(586,tsp.ShortestPath(10));
}
[Test,Category("Short")]
public void Use10Cities(){
TSP tsp = new TSP();
Assert.AreEqual(586,tsp.ShortestPath(10));
}
[Test,Category("Long")]
public void User50Cities(){
TSP tsp = new TSP();
Assert.AreEqual(2300,tsp.ShortestPath(50));
}
}
方法中的装载和卸载,例:
[TestFixture]
public class TestDB{ private Connection dbConn;
[SetUp]
public void MySetup(){ dbConn = new Connection("oracle", 1521, user, pw);
dbConn.Connect();
}
[TearDown]
public void MyTeardown() { dbConn.Disconnect();
dbConn = null;
}[Test]
public void TestAccountAccess() { // Uses dbConn
xxx xxx xxxxxx xxx xxxxxxxxx;
xx xxx xxx xxxx x xx xxxx;
}[Test]
public void TestEmployeeAccess() { // Uses dbConn
xxx xxx xxxxxx xxx xxxxxxxxx;
xxxx x x xx xxx xx xxxx;
}
}
如上例,在每个方法前后都有装载和卸载。