// Action & Assert
stack.Push(emptyString);
Assert.AreEqual(emptyString, stack.Top());
stack.Push(nullString);
Assert.AreEqual(nullString, stack.Top());
}
[TestMethod]
public void Test_Success_PopLastTwoElements()
{
// Arrange
var stack = new StackExercise();
var testElement1 = "test1";
var testElement2 = "test2";
// Action & Assert
stack.Push(testElement1);
stack.Push(testElement2);
Assert.AreEqual(testElement2, stack.Top());
stack.Pop();
Assert.IsFalse(stack.Empty());
Assert.AreEqual(testElement1, stack.Top());
stack.Pop();
Assert.IsTrue(stack.Empty());
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void Test_ThrowException_PopFromEmptyStack()
{
var stack = new StackExercise();
stack.Pop();
}
[TestMethod]
[ExpectedException(typeof(InvalidOperationException))]
public void Test_ThrowException_TopFromEmptyStack()
{
var stack = new StackExercise();
stack.Top();
}
}
}
所以,究竟该如何写单元测试呢?《单元测试之道C#版》里总结得很好:Right-BICEP。
Right,验证结果(主要功能和逻辑)是否正确;
B,边界条件是否正确;
I,是否可以检查反向关联;这里所谓反向关联,是指用反向逻辑来验证我们的结果,比如说要验证平方根是否正确时,可以求这个平方根的平方跟我们的输入是否一致。
C,是否可以采用其他方法来cross-check结果;cross-check是在单元测试中采用与实际模块中不同的方法来实现同样的功能作为期望结果,去与实际模块中得到的结果做对比。
E,错误条件是否可以重现;
P,性能方面是否满足条件。
3. 单元测试中不得不说的知识点
(1)断言Assertion
要验证代码的行为是否与期望一致时,我们需要使用断言来判断某个语句为真或为假,以及某些结果值与期望值是否相等,如IsTrue()、IsFalse()、AreEqual()等。
Assert.AreEqual(expected, actual [, string message]);
其中前两个参数很好理解,分别为期望值和实际值,最后一个可选参数是发生错误时报告的消息。如果不提供的话,出错后会看到这样的error message:Assert.AreEqual failed. Expected: xx. Actual: yy.。如果你的那个单元测试函数中有很多Assert.AreEqual的话,你就不清楚究竟是在哪个Assertion出错的,而当你对每个Assertion放上相应的message的话,出错时就可以一眼看出具体出错的Assertion。
另外,在用断言进行浮点数的比较时还需要提供另外一个参数tolerance。
有时候每个test里我们都需要进行一系列相同或者类似的断言,那么我们可以尝试编写自定义的断言,这样测试的时候使用这个自定义的断言即可。
(2)test 组成
从上面的例子可以看到,test project与普通project的区别就是在class和method上面增加了一个属性。在不同的框架下这些属性还是不一样的,比如说我们上面用到的VS里自带的test框架,使用的是[TestClass]和[TestMethod],而大家最常用的NUint框架则使用的是[TestFixture]和[Test]。
另外,还有几个attribute在实际项目中我们也会经常用到,那就是[SetUp]、[TearDown]、[TestFixtureSetUp]和[TestFixtureTearDown]。它们用来在调用test之前设置测试环境和在test之后释放资源。前两个是per-method,即每个用[Test]修饰的方法在运行前后都会调用[SetUp]和[TearDown];而后两个则是per-class的,即用于[TestFixture]修饰的类的前后。
(3)对于异常的测试
对于预期的异常,只要在测试方法上添加[ExpectedException(typeof(YourExpectedExcetion))]属性即可。但是需要注意的是,一旦这个方法期望的异常抛出了,测试方法中剩余的代码就会被跳过。
所以NUint里面还有一种方式来验证异常,即Assert.Throws(() => methodToTest());,这样就可以在一个test method里面验证多个抛出异常的情况了。
(4)使用mock对象
单元测试的目标是一次只验证一个方法或一个类,但是如果这个方法依赖一些其他难以操控的东西,比如网络、数据库等。这时我们就要使用mock对象,使得在运行unit test的时候使用的那些难以操控的东西实际上是我们mock的对象,而我们mock的对象则可以按照我们的意愿返回一些值用于测试。
原文转自:http://www.jianshu.com/p/7984955720e2