[鐵人賽 Day25] ASP.NET Core 2 系列 – 單元測試 (NUnit)

.NET Core 的單元測試框架有支援 xUnit、NUnit 及 MSTest,官方是比較推薦用 xUnit,但 NUnit 似乎比較受 .NET 工程師歡迎,我個人也是比較愛用 NUnit。
本篇將介紹 ASP.NET Core 搭配 NUnit 單元測試框架及如何用 Visual Studio Code (VS Code) 呈現視覺化測試結果。

iT 邦幫忙 2018 鐵人賽 – Modern Web 組參賽文章:
[Day25] ASP.NET Core 2 系列 – 單元測試 (NUnit)

建立方案

之前的範例都只有一個 Web 專案,由於要增加測試專案的關係,檔案的目錄結構建議異動成以下架構:

1
2
3
MyWebsite/                        # 方案資料夾
  MyWebsite/                      # Web 專案目錄
  MyWebsite.Tests/                # 單元測試專案目錄

若要透過 .NET Core CLI 建立 NUnit 樣板專案,需要先安裝 NUnit 的樣板專案,指令如下:

1
dotnet new --install NUnit3.DotNetNew.Template

跟著以下步驟建立整個方案:

1
2
3
4
5
6
mkdir MyWebsite
cd MyWebsite
# 建立 Web 樣板專案
dotnet new web --name MyWebsite
# 建立 NUnit 樣板專案
dotnet new nunit --name MyWebsite.Tests

[鐵人賽 Day25] ASP.NET Core 2 系列 - 單元測試 (NUnit) - 建立方案

包含 Web 專案及 NUnit 專案的方案內容如下:

[鐵人賽 Day25] ASP.NET Core 2 系列 - 單元測試 (NUnit) - 方案內容

執行測試

NUnit 樣板專案會預帶一個 UnitTest1.cs 做為單元測試的範例,可以透過 .NET Core CLI 執行測試,指令如下:

1
2
# dotnet test <測試專案名稱>
dotnet test MyWebsite.Tests

[鐵人賽 Day25] ASP.NET Core 2 系列 - 單元測試 (NUnit) - 執行測試

測試案例

被測試的目標以[鐵人賽 Day24] ASP.NET Core 2 系列 – Entity Framework Core文中的 Repository Pattern 的 Controllers/UserController.cs 做為範例。
由於測試專案 MyWebsite.Tests 會參考到 MyWebsite 專案,所以要在 MyWebsite.Tests 加入對 MyWebsite 的參考,透過 .NET Core CLI 加入參考的指令如下:

1
2
# dotnet add <專案名稱> reference <被參考專案的 csproj 檔>
dotnet add MyWebsite.Tests reference MyWebsite\MyWebsite.csproj

被測試的目標會需要用到 Mock Framework,我慣用的 Mock Framework 是 NSubstitute,所以會以 NSubstitute 為 Mock 範例,安裝指令:

1
dotnet add MyWebsite.Tests package NSubstitute

在 MyWebsite.Tests 專案新增 Controllers\UserControllerTests.cs,測試案例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using MyWebsite.Controllers;
using MyWebsite.Repositories;
using NSubstitute;
using NUnit.Framework;

namespace MyWebsite.Tests.Controllers
{
    public class UserControllerTests
    {
        private IRepository<UserModel, int> _fakeRepository;
        private UserController _target;

        [SetUp]
        public void SetUp()
        {
            _fakeRepository = Substitute.For<IRepository<UserModel, int>>();
            _target = new UserController(_fakeRepository);
        }

        [Test]
        public void SearchUser()
        {
            // Arrange
            var query = "test";
            var model = new UserModel { Id = 1 };
            _fakeRepository.Find(Arg.Any<Expression<Func<UserModel, bool>>>())
                .Returns(new List<UserModel> { model });

            // Act
            var actual = _target.Get(query);

            // Assert
            Assert.IsTrue(actual.IsSuccess);
        }

        [Test]
        public void GetUser()
        {
            // Arrange
            var model = new UserModel { Id = 1 };
            _fakeRepository.FindById(Arg.Any<int>()).Returns(model);

            // Act
            var actual = _target.Get(model.Id);

            // Assert
            Assert.IsTrue(actual.IsSuccess);
        }

        [Test]
        public void CreateUser()
        {
            // Arrange
            var model = new UserModel();

            // Act
            var actual = _target.Post(model);

            // Assert
            Assert.IsTrue(actual.IsSuccess);
        }

        [Test]
        public void UpdateUser()
        {
            // Arrange
            var model = new UserModel { Id = 1 };

            // Act
            var actual = _target.Put(model.Id, model);

            // Assert
            Assert.IsTrue(actual.IsSuccess);
        }

        [Test]
        public void DeleteUser()
        {
            // Arrange
            var model = new UserModel { Id = 1 };

            // Act
            var actual = _target.Delete(model.Id);

            // Assert
            //Assert.IsTrue(actual.IsSuccess);
            Assert.Fail();
        }
    }
}

測試結果如下:

[鐵人賽 Day25] ASP.NET Core 2 系列 - 單元測試 (NUnit) - 測試結果

Visual Studio Code

每次要測試都要打指令,顯得有點麻煩,而且透過指令執行顯示的測試結果,以純文字顯示也不怎麼好看。
VS Code 有測試專案用的擴充套件,可以直接在程式碼中看到那些測試案例成功或失敗。

打開 VS Code 在 Extensions 搜尋列輸入 test ,便可以找到 .NET Core Test Explorer 的擴充套件安裝。如下圖:

[鐵人賽 Day25] ASP.NET Core 2 系列 - 單元測試 (NUnit) - .NET Core Test Explorer

安裝完成後在方案資料夾下的 .vscode\settings.json 新增 dotnet-test-explorer.testProjectPath 指定測試專案位置,如下:

.vscode\settings.json

1
2
3
{
    "dotnet-test-explorer.testProjectPath": "MyWebsite.Tests"
}

就可以透過 VS Code UI 執行單元測試,並且能在程式碼中看到那些測試案例成功或失敗。如下:

[鐵人賽 Day25] ASP.NET Core 2 系列 - 單元測試 (NUnit) - .NET Core Test Explorer

參考

Unit Testing in .NET Core and .NET Standard