转自 胡胡ID:fenghu89
原文地址 : http://blog.youkuaiyun.com/fenghu89/archive/2008/05/04/2379128.aspx
实际中,为了达到这个目标,我们一般会使用 assertion (断言)
现在我们开始尝试一个简单的测试开始。
public class Cmp
{
public static int Largest(int [] list)
{
int index,max = Int32.MaxValue;
for (index = 0;index < list.Length -1; index++)
{
if (list[index] > max)
{
max = list[index];
}
}
return max;
}
}
这个类文件中包含了一个Largest 方法用来返回一个整数数组的最大值。
打开NUnit
我们编写测试方法
[TestFixture]
public class TestDemo
{
public TestDemo()
{
//
// TODO: 在此处添加构造函数逻辑
//
}
public void IsTrue(bool condition)
{
if (!condition)
{
}
}
[Test]
public void LargestOf()
{
Assert.AreEqual(9,Cmp.Largest(new int []{8,9,7}));
}
[Test]
public void LargestOf3Alt()
{
int [] arr = new int [3];
arr[0] = 8;
arr[1] = 9;
arr[2] = 7;
Assert.AreEqual(9,Cmp.Largest(arr));
}
}
然后在 NUnit 中加载这个需要测试的 DLL
左边会显示这个测试类的方法结构 点击要运行的测试方法 在点击 RUN
我们可以看到图片显示所有方法的指示灯为红色,那就是意味着测试没有通过。如果通过应该显示为了绿色,是黄色的时候表示有些测试被忽略。
测试为红色,输出的值不是 9 ,是一个大数字。我们找寻原因,原来
int index,max = Int32.MaxValue;
这里附值出错,我们修改为
Int index , max = 0 ;
测试通过。
为了达到真正的测试的有效性,我们要尝试测试的全面性。当然,对于测试来说,可能要做到 100% 的全面角度测试根本就是不可能的。简单的说,想一下验证是否是一个有效的三角形需要多少种测试。我想,最有经验的程序员,也会遗漏。
我们在测试的方法里面在增加一个测试用例
public void LargestOf()
{
Assert.AreEqual(9,Cmp.Largest(new int []{8,9,7}));
Assert.AreEqual(9,Cmp.Largest(new int []{9,8,7}));
Assert.AreEqual(9,Cmp.Largest(new int []{7,8,9}));
}
运行Run 出现错误
出现错误的原因来源自
for (index = 0;index < list.Length -1; index++){}
应该修改成
for (index = 0;index <= list.Length -1; index++)
OR
for (index = 0;index < list.Length; index++)
在循环条件中,很容易发生这种off-by-one 的错误。
我们还可以增加对重复的值的测试,一个元素的测试还有包含负数的测试
[Test]
public void TestDups()
{
Assert.AreEqual(9,Cmp.Largest(new int []{9,7,9,8}));
}
[Test]
public void TestOne()
{
Assert.AreEqual(1,Cmp.Largest(new int []{1}));
}
[Test]
public void testNegative()
{
int [] negList = new int []{-9,-8,-7};
Assert.AreEqual(7,Cmp.Largest(negList));
}
运行测试结果是
负数测试出现问题
原因是 max = 0 ;出现的问题
我们修改为 max=Int32.MinValue;
我们还可以增加对异常的处理
public static int Largest(int [] list)
{
int index,max = Int32.MinValue;
if (list.Length == 0)
{
throw new ArgumentException(" 数组为空");
}
for (index = 0;index <= list.Length -1; index++)
{
if (list[index] > max)
{
max = list[index];
}
}
return max;
}
和测试用方法
[Test,ExpectedException(typeof (ArgumentException))]
public void TestEmpty()
{
Cmp.Largest(new int []{});
}
具体我们将在后面讨论
测试代码是我们用来测试所开发的函数是否被它所宣称的那样达到目的。但是,测试代码仅限于我们开发使用,所以最终它不会和产品一起跑到顾客那里,这就通常意味着我们的测试代码一般是写在一个独立的项目中。一个自己的程序集。
那么测试代码必须要做的几件事:
1 :准备测试所需要的各种条件 ( 创建所有必须的对象,分配必要的资源等 )
2 :调用要测试的方法。
3 :验证被测试的方法的行为和期望是否和我们的一致。
4 :完成后清理各种资源
NUnit 的各种断言( assert ) 是 NUnit 提供的帮助我们用来进行测试的函数
1 : AreEquals
Assert.AreEqual(expected,actual[,string message])
这个是使用最多的断言形式,其中 expected 是你期望的值, actual 是测试代码产生的值,
Message 是一个可选的参数,如果提供了这个参数,将会在发生错误的时候报告这个消息。我们上面的测试例子就是使用这个断言。但是针对比较浮点数的时候要设定将精确到多少位
例子: Assert.AreEqual(3.33, 10.0/3.0, 0.01, “Wanted 3 1/3”)
2:IsNull
Assert.IsNull(object [,string message])
Assert.IsNotNull(object [,string message])
验证一个给定的对象是否为 null 或者非 null
3:AreSame
Assert.AreSame(expected, actual [, string message])
验证 expected 参数 和 actual 所引用的是否为同一个对象
4:IsTrue
Assert.IsTrue(bool condition [, string message])
验证给定的二元条件是否为真
也可以使用
Assert.IsFalse(bool condition[, string message])
5:Fail
Assert.Fail([string message])
使测试立刻失败
使用 NUnit 框架
1 :在测试项目中引入 NUnit DLL 然后在类文件中
Using NUnit.Framework;
2: 每个包含测试的类必须加上
[TestFixture] attribute 标记
测试类必须是 Public 的
3 :每个测试类的测试方法必须加上
[Test] attribute 标记
在测试中,有时候,我们可以希望把一些不同程序员或者不同的测试类放在一个测试用例中进行。这个时候,我们可以使用
[Suite] attribute 标记把这些的测试放入一个 TestFixture 集合里,要引入 NUnit.Core
然后
[TestFixture]
public class testClassSuite
{
[Stuit]
public static TestSuite Suite
{
get
{
TestSuite suite = new TestSuite("Name of Suite");
suite.Add(new DatebaseTests()); // 增加一个测试类
suite.Add(new FredsTests()); // 添加的测试类
return suite;
}
}
}
这样可以把 几个想共同执行的测试类放在一起去执行
分类 Categories
NUint 用 category 的概念提供了标记和运行一个个单独的测试和 fixture 的简单方法。
[TestFixture]
public class TestShorttestPath
{
[Test]
[Category("Short")]
public void UserCities()
{
int [] negList = new int []{-9,-8,-7};
Assert.AreEqual(-7,Cmp.Largest(negList));
}
[Test,Category("Short")]
public void UserCitiesTest()
{
int [] negList = new int []{-9,-8,-7,5};
Assert.AreEqual(-7,Cmp.Largest(negList));
}
[Test,Category("Long")]
public void UserCitiesDemo()
{
int [] negList = new int []{3,-8,-7,5};
Assert.AreEqual(-7,Cmp.Largest(negList));
}
}
这样运行会在UNint 上看到
可以对不同的 category 进行测试
也可以把整个 TestFixture 标志成 Category
Per-method 的 Setup 和 Teardown
每个测试的运行都应该是相互独立的,从而你就可以在任何时候以任意的顺序运行每个单独的测试。为了达到这个目的,在每个测试开始以前,可能需要重新‘
设置测试环境 NUint 有 2 中 Attribute 分别用来对环境的建立和清理
[SetUp]
Public void MySetup()
{}
[TearDown]
Public void MyTeardown()
{}
比如我们对某个测试要打开某个资源或者数据库,然后测试完成后在释放。
Per-class Setup 和 Per-class Teardown
当需要对整个 test class 设置一些环境的时候 使用这个 attribute
[TestFixtureSetUp]
Public void OneTimeSetup()
{}
[TestFixtureTearDown]
Public void OneTimeTeardown()
{}
自定义 Nunit 断言
NUnit 和异常
1 :从测试代码抛出的可预测异常
2 :由于某个模块或代码发生严重错误,而抛出的不可预测的异常
我们已经会用 NUnit 编写一些简单的测试代码了,但是我们如何去开始自己的测试,并更好的实现自己的测试目标?这 - 也许才是最重要的。
记得以前刚开始接触单元测试的时候,有一位大师是这么说的:“编写测试,运行使其不通过,编写代码,运行,使测试通过。。。”
那么,我们在软件开发的时候,要努力的去习惯于对每个功能编写有效的测试 Case. 特别是当我们发现了新的或者别人的功能的 Bug 的时候,一定要学会用 Case 来证明。不要只会大声的说,你或者这个有 Bug. 而要说,现在这里有一个问题,让我们去修改问题使 Case 通过吧
在真实的测试过程中,可能会出现一些额外的问题。譬如我开始使用Nunit 的时候,我要测试一个Rule (业务规则层)的一个方法。当我创建了一个测试的Case ,并引入了ERP 的Rule 层DLL 的时候,发现测试无法通过,并且无法通过的原因是低层连接无法实现,开始我很困惑,不知道发生么什么事情。因为DataAccess 曾(数据访问曾)的功能是完整的且经过验证的。并且引用也没有问题,经过仔细检查才发现,其实问题很简单,我的数据连接串是通过XML 文件app.Config 来创建的。我在建立测试Case 项目的时候,只关注于这个测试的内容,却忽略了读取连接的问题,测试项目读取的连接其实是这个测试项目自身的app.config 文件。一个很简单的问题,因为疏忽而产生,其实作为程序员来说,大部分的问题都是由于思考的不周全。就是我们常说的低级错误。