天天看點

.NET Core系列 :4 測試 NSubstitute

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;

        }

    }

}

.NET Core系列 :4 測試 NSubstitute

下面我們要建立一個針對DotnetCoreLib的測試項目,具體建立過程我們參照文章 https://github.com/dotnet/core-docs/tree/master/samples/core/getting-started/unit-testing-using-dotnet-test ,我們修改DotnetCoreLibTest 項目的project.json ,增加XUnit相關的nuget包引用,并修改部配置設定置。

.NET Core系列 :4 測試 NSubstitute

還有我們設定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模式:

  1. Arrange:為測試準備
  2. Act:運作SUT(實際測試的代碼)
  3. 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包:

.NET Core系列 :4 測試 NSubstitute

我們把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完全手冊索引。

.NET Core系列 :4 測試 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.Threading.Tasks;

using Filters101.Models;

using Newtonsoft.Json;

namespace IntegrationTests.AuthorsController

    public class Get : AuthorsControllerTestBase

        private readonly HttpClient _client;

        public Get()

            _client = base.GetClient();

        [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視窗執行單元測試,檢視測試結果

.NET Core系列 :4 測試 NSubstitute

歡迎大家掃描下面二維碼成為我的客戶,為你服務和上雲

.NET Core系列 :4 測試 NSubstitute