歡迎光臨
每天分享高質量文章

使用 xUnit 編寫 ASP.NET Core 單元測試

還記得 .NET Framework 的 ASP.NET WebForm 嗎?那個年代如果要在 Web 層做單元測試簡直就是災難啊。.NET Core 吸取教訓,在設計上考慮到了可測試性,就連 ASP.NET Core 這種 Web 或 API 應用要做單元測試也是很方便的。其中面向介面和依賴註入在這方面起到了非常重要的作用。

本文就來手把手教你如何用 xUnit 對 ASP.NET Core 應用做單元測試。.NET Core 常用的測試工具還有 NUnit 和 MSTest,我本人習慣用 xUnit 作為測試工具,所以本文用的是 xUnit。

建立示例專案

先用 ASP.NET Core API 模板建一個應用。

模板為我們自動建立了一個 ValuesController,為了方便演示,我們只留其中一個 Get 方法:

public class ValuesController : ControllerBase
{
    // GET api/values/5
    [HttpGet("{id}")]
    public ActionResult<string> Get(int id)
    {
        return "value";
    }
}

然後再新增一個 xUnit 單元測試專案:

模板自動為我們新增好了 xUnit 取用:

<ItemGroup>
  <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
  <PackageReference Include="xunit" Version="2.4.0" />
  <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
ItemGroup>

但要測試 ASP.NET Core 應用還需要新增兩個 NuGet 包:

Install-Package Microsoft.AspNetCore.App
Install-Package Microsoft.AspNetCore.TestHost

當然還要引入標的專案。最後的取用是這樣的:

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.5" />
    <PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
    <PackageReference Include="xunit" Version="2.4.0" />
    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.0" />
  ItemGroup>

<ItemGroup>
<ProjectReference Include=“..\WebApplication1\WebApplication1.csproj” />
ItemGroup>

新增完取用後編譯一下,編譯一下確認取用沒有問題。

編寫單元測試

寫單元測試一般有三個步驟:Arrange,Act 和 Assert。

  • Arrange 是準備階段,這個階段是準備工作,比如模擬資料、初始化物件等;

  • Act 是行為階段,這個階段是用準備好的資料去呼叫要測試的方法;

  • Assert 是斷定階段,就是把呼叫標的方法傳回的值和預期的值進行比較,如果和預期一致說明測試透過,否則為失敗。

按照這個步驟我們來編寫一個單元測試方法,以 ValuesController 中的 Get 方法作為要測試的標的。一般一個單元測試方法就是一個測試用例。

我們在測試工程新增一個 ValuesTests 單元測試類,然後編寫一個單元測試方法,程式碼如下:

public class ValuesTests
{
    public ValuesTests()
    {
        var server = new TestServer(WebHost.CreateDefaultBuilder()
            .UseStartup());
        Client = server.CreateClient();
    }

    public HttpClient Client { get; }

    [Fact]
    public async Task GetById_ShouldBe_Ok()
    {
        // Arrange
        var id = 1;

        // Act
        var response = await Client.GetAsync($"/api/values/{id}");

        // Assert
        Assert.Equal(HttpStatusCode.OK, response.StatusCode);
    }
}

這裡我們透過 TestServer 拿到一個 HttpClient 物件,用它我們可以模擬 Http 請求。我們寫了一個非常簡單的測試用例,完整演示了單元測試的 Arrange,Act 和 Assert 三個步驟。

建議單元測試的方法名使用“什麼應該是什麼”的樣式。比如上面的 GetById_ShouldBe_Ok,表示呼叫 GetById 這個 API 傳回的結果應該是 OK 的,這樣一看就知道你這個測試用例是幹嗎的,不需要過多的註釋。

執行單元測試

單元測試用例寫好後,開啟“Test Explore”(中文版 VS 看到的是中文),在測試方法上右擊,選擇“Run Seleted Tests”,也可以在方法程式碼塊內滑鼠右擊選擇“Run Tests”:

註意看測試方法前面圖示的顏色,目前是藍色的,表示測試用例還沒有執行過。

測試用例執行結束後如果結果和預期一致就是綠色的圖示:

如果執行結果和預期不一致就會是紅色圖示,然後你需要修改程式碼直到出現綠色圖示。你可以在“Test Explore”的下方看到執行消耗的時間,也可以在 Output 視窗看到執行的細節。

以上圖示顏色的變化過程是:藍色,紅色,再綠色,有可能藍色經過一次執行就直接變成綠色,也有可能經過很多次紅色才變成綠色。測試驅動開發中的BRG(藍紅綠)術語就是這麼來的。

除錯單元測試

你可以透過新增斷點的方式在單元測試中除錯。方法很簡單,在需要除錯的方法上右鍵選擇“Debug Seleted Tests”即可,和平時的除錯是一樣的。

如果我們要檢視 API 具體傳回了什麼,可以透過加斷點除錯來檢視傳回結果的變數字串值,但這種方式不是最好的選擇。比如對於同一個 API,我要看看 10 種引數傳回的結果是什麼樣的,每次都透過斷點除錯來檢視就很麻煩。

除了新增斷點來除錯,還有一種列印日誌的方法來快速除錯,xUnit 可以很方便地做到這一點。為此我們來修改一下 ValuesTests:

public ValuesTests(ITestOutputHelper outputHelper)
{
    var server = new TestServer(WebHost.CreateDefaultBuilder()
        .UseStartup());
    Client = server.CreateClient();
    Output = outputHelper;
}

public ITestOutputHelper Output{ get; }

// ... (省略其它程式碼)

這裡我們在建構式中添加了 ITestOutputHelper 引數,xUnit 會將一個實現此介面的實體註入進來。拿到這個實體後,我們就可以用它來輸出日誌了:

  [Fact]
  public async Task GetById_ShouldBe_Ok()
  {
      // Arrange
      var id = 1;

      // Act
      var response = await Client.GetAsync($"/api/values/{id}");

      // Output
      var responseText = await response.Content.ReadAsStringAsync();
      Output.WriteLine(responseText);

      // Assert
      Assert.Equal(HttpStatusCode.OK, response.StatusCode);
  }

執行(註意不是 Debug)此方法,執行結束後,在“Test Explore”的下方可以可以看到“Output”字樣,點選它就可以看到輸出的結果,如圖:

透過這種方式,每次執行測試我們就可以很方便的檢視輸出結果了。

其它

上面我們是透過模擬 Http 請求的方式來呼叫 API 測試的,還有一種就是 new 一個 Controller 來直接呼叫它的 Action 方法來測試。比如這樣:

  // Arrange
  var id = 1;
  var controller = new ValuesController();
  // Act
  var result = controller.Get(id);

如果 Controller 沒有其它依賴,這種方式當然是最方便的。但通常 Controller 是會有一個或多個依賴的,比如這樣:

public class ValuesController : Controller
{
    private readonly ISessionRepository _sessionRepository;

    public ValuesController(ISessionRepository sessionRepository)
    {
        _sessionRepository = sessionRepository;
    }

    // ...
}

我們就要模擬實體化這個 Controller 的所有依賴,當然手動模擬是不現實的,因為一個依賴類還可能會依賴其它的類或介面,依賴鏈可能很長,你不可能每個依賴都手動去實體化它們。有一個叫 Moq 的工具可以自動來模擬實體化依賴,它的用法是這樣的:

// ..
// Arrange
var mockRepo = new Mock();
mockRepo.Setup(...);
var controller = new HomeController(mockRepo.Object);

// Act
var result = await controller.Index();

這種方式我是不推薦的,因為拋開 Moq 的學習成本不說,重要的是它不如模擬 Http 請求那樣接近真實的呼叫場景,所以本文對它不作過多的介紹,大家知道有這麼回事就行。

    贊(0)

    分享創造快樂