2016.6.27 微軟已經正式發佈了.NET Core 1.0 RTM,但是工具鏈還是預覽版,同樣的大量的開源測試庫也都是至少發佈了Alpha測試版支持.NET Core, 這篇文章 The State of .Net Core Testing Today 就將各個開源測試庫的目前進展進行了彙總。 ...
2016.6.27 微軟已經正式發佈了.NET Core 1.0 RTM,但是工具鏈還是預覽版,同樣的大量的開源測試庫也都是至少發佈了Alpha測試版支持.NET Core, 這篇文章 The State of .Net Core Testing Today 就將各個開源測試庫的目前進展進行了彙總。本文我們的目的是在我們構建我們應用程式的時候能夠進行測試,如何使用XUnit結合你可以通過為你的項目添加不同的測試用例NSubstitute進行單元測試,同時對整個項目進行集成測試。這次我們使用Visual Studio 2015 Update 3進行編寫 。xUnit.net是基於.NET Framework 的開源測試工具。通過xUnit.net可以針對C#/F#/VB.NET等進行單元測試。ASP.NET Core 更直接把以往的Visual Studio Unit Test Framework 說再見了,而直接使用上了xUnit.net,xUnit.net基於NUnit 。從網站或者官網上,你可以找到不少xUnit的優點,與NUnit和其他測試框架相比有一下一些優勢
1)為每個測試方法產生一個對象實例
2)取消了[SetUp]和[TearDown]
3)取消了[ExpectedException]
4)類似於Aspect的功能
5)減少了自定義屬性(Attribute)的數目
6)採用泛型
7)匿名委托
8)可擴展的斷言
9)可擴展的測試方法
10)可擴展的測試類
瞭解更多關於xUnit.net可以參考這裡(點擊打開鏈接[捨棄Nunit擁抱Xunit])。
使用xUnit.net 單元測試
首先我們類似於.NET Core系列 :3 、使用多個項目 創建一個解決方案testdemo,添加一個類庫項目叫做DotnetCoreLib,Library.cs 也替換為:
namespace DotnetCoreLib
{
public class Calculator
{
public int Multi(int x, int y)
{
return x * y;
}
}
}
下麵我們要創建一個針對DotnetCoreLib的測試項目,具體創建過程我們參照文章 https://github.com/dotnet/core-docs/tree/master/samples/core/getting-started/unit-testing-using-dotnet-test ,我們修改DotnetCoreLibTest 項目的project.json ,增加XUnit相關的nuget包引用,並修改部分配置。
還有我們設置Framework節點為 netcoreapp1.0, 依賴的xunit 和xunit.runner的包
"dependencies": {
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"DotnetCoreLib": {
"version": "1.0.0-*",
"target": "project"
},
"xunit": "2.2.0-beta2-build3300",
"xunit.runner.console": "2.2.0-beta2-build3300"
}
Calculator接下來就開始測試我們的類庫Calculator, 修改Class1.cs為CalculatorTest.cs ,
using DotnetCoreLib;
using Xunit;
namespace DotnetCoreLibTest
{
public class CalTest
{
private readonly Calculator calculator;
public CalTest()
{
calculator = new Calculator();
}
[Fact]
public void OneMutiOneIsOne()
{
var result = calculator.Multi(1, 1);
Assert.Equal(1, result);
}
[Theory]
[InlineData(-1)]
[InlineData(0)]
[InlineData(1)]
public void ReturnValue(int value)
{
var result = calculator.Multi(1,value);
Assert.Equal(result, value);
}
}
}
上面的兩個測試,我們分別用了2個特性[Fact] 和[Theory], [Fact]屬性表示為一個方法的單個測試,[Theory]屬性表示執行相同的代碼,但是有不同的輸入的參數的測試套件。[InlineData] 屬性可用於指定為這些輸入值。通過特性[Fact] 和[Theory],xUnit就理解了這是個測試方法,然後運行這個方法。在一個測試方法中,我們一般遵循包含三步驟的AAA模式:
- Arrange:為測試準備
- Act:運行SUT(實際測試的代碼)
- Assert:校驗結果
下麵我們運行dotnet test 就可以看到結果了。
C:\Users\geffz\Documents\Visual Studio 2015\Projects\TestDemo\DotnetCoreLibTest>dotnet test
Project DotnetCoreLib (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
Project DotnetCoreLibTest (.NETCoreApp,Version=v1.0) was previously compiled. Skipping compilation.
xUnit.net .NET CLI test runner (64-bit .NET Core win10-x64)
Discovering: DotnetCoreLibTest
Discovered: DotnetCoreLibTest
Starting: DotnetCoreLibTest
Finished: DotnetCoreLibTest
=== TEST EXECUTION SUMMARY ===
DotnetCoreLibTest Total: 4, Errors: 0, Failed: 0, Skipped: 0, Time: 0.206s
SUMMARY: Total: 1 targets, Passed: 1, Failed: 0.
上面的輸出我們知道已經執行了4個測試,都通過了,[Face]特性標識表示固定輸入的測試用例,而[Theory]特性標識表示可以指定多個輸入的測試用例,結合InlineData特性標識使用。在上面的例子里,總共使用了三次InlineData特性標識,每次設定的值都不同,在執行單元測試時,設定的值會被測試框架賦值到對應的測試方法的參數里。你可以通過為你的項目添加不同的測試用例,這樣就可以讓你的代碼得到充分測試。
xUnit.net 搭配NSubstitute 進行單元測試
在一個分層結構清晰的項目里,各層之間依賴於事先約定好的介面。在多人協作開發時,大多數人都只會負責自己的那一部分模塊功能,開發進度通常情況下也不一致。當某個開發人員需要對自己的模塊進行單元測試而依賴的其他模塊還沒有開發完成時,則需要對依賴的介面通過Mock的方式提供模擬功能,從而達到在不實際依賴其他模塊的具體功能的情況下完成自己模塊的單元測試工作。這時我們通常需要有一個單元測試模擬類庫,一直以來,開發者對 mocking 類庫的語法的簡潔性有強烈的需求,NSubstitute 試圖滿足這一需求。簡單明瞭的語法可以讓我們將重心放在測試本身,而不是糾纏在測試替代實例的創建和配置上。NSubstitute 已嘗試將最常用的操作需求簡單化、易用化,並支持一些不常用的或探索性的功能,與此同時還儘可能地將其語法向自然語言靠近。關於NSubstitute的更詳細信息請往 NSubstitute完全手冊索引。
NSubstitute 已經發佈2.0 RC版本支持.NET Core。引入NSubstitute 相關nuget包:
我們把Calculator 類重構下提取出介面ICalculator:
public interface ICalculator
{
int Multi(int x, int y);
}
我們可以讓NSubstitute來創建類型實例的替代實例,可以創建諸如 Stub、Mock、Fake、Spy、Test Double 等,但當我們只是想要一個能有一定程度控制的替代實例時,為什麼我們要困擾於此呢?我們可以告訴被創建的替代實例,當方法被調用時返回一個值:
[Fact]
public void Test_GetStarted_ReturnSpecifiedValue()
{
ICalculator calculator = Substitute.For<ICalculator>();
calculator.Multi(1, 2).Returns(2);
int actual = calculator.Multi(1, 2);
Assert.Equal(2, actual);
}
下麵我們運行dotnet test 就可以看到結果了,增加了上面的2個用例,關於NSubstitute的更詳細信息請往 NSubstitute完全手冊索引。
集成測試
上面我們只是對邏輯進行了單元測試。對於Asp.Net Core項目,還需要模擬在網站部署的情況下對各個請求入口進行測試。NET Core 可為快速輕鬆集成測試提供非常棒的支持。
TestServer 類為 ASP.NET Core 中的集成測試執行大部分繁重操作,Microsoft.AspNetCore.TestHost 包中具有此類。本節內容來自於MSDN雜誌《 ASP.NET Core - 實際的 ASP.NET Core MVC 篩選器》,這些集成測試不需要資料庫或 Internet 連接或運行的 Web 伺服器。它們如同單元測試一樣快速簡單,但最重要的是,它們允許你在整個請求管道中測試 ASP.NET 應用,而不只是控制器類中的孤立方法。建議儘可能編寫單元測試,並針對無法單元測試的行為退回到集成測試,但使用此類高性能方式在 ASP.NET Core 中運行集成測試是非常棒的。
通過在一個工程里同時模擬了服務端(TestServer)和客戶端(HttpClient)的通信,從而達到了整體測試WebApi介面的目的,相關的代碼放在https://github.com/ardalis/GettingStartedWithFilters/tree/master/IntegrationTests 。文章對ASP.NET CORE MVC的篩選器進行測試,由於很難通過編寫單元測試來測試此類場景,但是可以通過ASP.NET Core 的集成測試來達到相同的目的。
using System.IO;
using System.Net.Http;
using System.Net.Http.Headers;
using Filters101;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
namespace IntegrationTests
{
public class AuthorsControllerTestBase
{
protected HttpClient GetClient()
{
var builder = new WebHostBuilder()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseStartup<Startup>()
.UseEnvironment("Testing");
var server = new TestServer(builder);
var client = server.CreateClient();
// client always expects json results
client.DefaultRequestHeaders.Clear();
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
return client;
}
}
}
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using Filters101.Models;
using Newtonsoft.Json;
using Xunit;
namespace IntegrationTests.AuthorsController
{
public class Get : AuthorsControllerTestBase
{
private readonly HttpClient _client;
public Get()
{
_client = base.GetClient();
}
[Theory]
[InlineData("authors")]
[InlineData("authors2")]
public async Task ReturnsListOfAuthors(string controllerName)
{
var response = await _client.GetAsync($"/api/{controllerName}");
response.EnsureSuccessStatusCode();
var stringResponse = await response.Content.ReadAsStringAsync();
var result = JsonConvert.DeserializeObject<IEnumerable<Author>>(stringResponse).ToList();
Assert.Equal(2, result.Count());
Assert.Equal(1, result.Count(a => a.FullName == "Steve Smith"));
Assert.Equal(1, result.Count(a => a.FullName == "Neil Gaiman"));
}
}
}
此案例中的客戶端是標準的 System.Net.Http.HttpClient,你可以使用它向伺服器發出請求,正如同通過網路一樣。但因為所有請求都在記憶體中進行,所以測試極其快速可靠。在cmd視窗執行單元測試,查看測試結果