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<ExpectedException>(() => methodToTest());,这样就可以在一个test method里面验证多个抛出异常的情况了。
(4)使用mock对象
单元测试的目标是一次只验证一个方法或一个类,但是如果这个方法依赖一些其他难以操控的东西,比如网络、数据库等。这时我们就要使用mock对象,使得在运行unit test的时候使用的那些难以操控的东西实际上是我们mock的对象,而我们mock的对象则可以按照我们的意愿返回一些值用于测试。
比如说,我们在某个函数中需要利用HttpClient通过SendAsync方法从某个EndPoint获取数据进行处理。但是在local测试的时候不一定能够连上那个EndPoint,或者不能保证那个EndPoint会返回什么东西。所以我们可以写mock一个ResponseHandler,这样我们就可以把mock的返回结果放进httpClient中传给需要测试的模块,这样就可以测试该模块内后续部分的处理了。
internal class MockResponseHandler : DelegatingHandler { public HttpStatusCode StatusCode { get; set; } public HttpContent Content { get; set; } protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) { return await ReturnRespsonse(); } private Task<HttpResponseMessage> ReturnRespsonse() { var response = new HttpResponseMessage() { StatusCode = this.StatusCode, Content = this.Content }; return Task.Run(() => response); } }``` |
var successHttpClient = new HttpClient(
new MockResponseHandler
{
StatusCode = HttpStatusCode.OK
});
var forbidHttpClient = new HttpClient(
new MockResponseHandler
{
StatusCode = HttpStatusCode.Forbidden,
Content = new StringContent(testError)
});
实际上,`.NET`中现在很多mock对象的框架供选择(参见http://www.mockobjects.org ),很多常用的mock都可以直接使用框架,而不需要自己去写。
####4. 帮助你更好地进行单元测试的工具
[NUnit](http://www.nunit.org/index.php?p=download)
[ReShaper](https://resharper-support.jetbrains.com/hc/en-us/articles/207242355-Where-can-I-download-an-old-previous-ReSharper-version-)
奈何家里的笔记本下载它们一直失败,所以这里先给个链接,以后有机会再介绍一下它们吧(⊙﹏⊙)b