# 如何將現有 .NET Core 專案加入相對應的 xUnit 單元測試專案

在 Visual Studio 2017 開發工具裡，**建立單元測試專案**有好幾種方法，其中一種是將現有專案增加一個相對應的**單元測試專案**。在傳統 .NET Framework 專案中，很輕易的就可以建立完成，只要在任一類別上按下滑鼠右鍵，選擇「建立單元測試」即可。但在 .NET Core 專案中就沒那麼方便了，因為這個功能一直[沒被實現](https://developercommunity.visualstudio.com/content/problem/145930/create-unit-test-context-menu-missing-net-core-pro.html)。本篇文章將分享如何依據現有專案的程式碼，產生相對應的單元測試專案，一步一步帶大家了解建立單元測試專案的過程。

### 從類別庫專案開始

首先，我們先建立一個 .NET Core 的「類別庫」專案 (Class Library)，專案名稱假設為 `ClassLibrary1`，我們的專案中只有一個 `MathLib` 類別，且程式碼也相當簡單，只有 4 個方法 (Methods)。如下範例：

```csharp
namespace ClassLibrary1
{
    public class MathLib
    {
        public int Add(int a, int b)
        {
            return a + b;
        }
        public int Sub(int a, int b)
        {
            return a - b;
        }
        public int Mul(int a, int b)
        {
            return a * b;
        }
        public float Div(int a, int b)
        {
            return a / b;
        }
    }
}
```

### 加入單元測試專案

由於 Visual Studio 2017 目前還無法針對現有 .NET Core 專案方便的建立單元測試專案，所以步驟會稍微複雜一點。

1.  我們先在現有方案中，手動建立一個全新的 xUnit 單元測試專案，專案名稱可命名為 `ClassLibrary1Test`。建立完成後，請先透過 NuGet 套件管理員將 [xunit](https://xunit.net/) 相關套件升級到最新版本。
    
2.  刪除預設的 `UnitTest1.cs` 檔案。
    
3.  調整 `ClassLibrary1Test` 專案的相依性，將 `ClassLibrary1` 專案加入參考。
    
4.  安裝 [Unit Test Boilerplate Generator](https://marketplace.visualstudio.com/items?itemName=RandomEngy.UnitTestBoilerplateGenerator) 擴充套件
    
    -   **請注意**：這裡要安裝的不是 NuGet 套件喔！而是 Visual Studio 2017 的擴充套件！
    -   安裝完成後，必須先關閉 Visual Studio 2017，安裝套件，再重開專案才行！
5.  直接從方案總管選取 `MathLib.cs` 檔案，並按下滑鼠右鍵，選擇 `Create Unit Test Boilerplate`，然後在對話框中設定正確的單元測試專案、測試框架，以及想用的 Mock Framework，就可以自動產生測試類別/測試方法。
    
    ![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/b3401ef2f953101bfb27-image_3139.png)
    
    請注意:透過工具只會幫你大致寫好要測試的程式骨架([3A Pattern](http://wiki.c2.com/?ArrangeActAssert))，事實上範例程式幾乎都不能用，還必須修改過才行。
    
6.  修正程式碼
    
    -   先移除 `using NSubstitute;` (因為目前用不到)
    -   把所有 `TODO` 全部都改為 `2`
    -   最後把 `Assert.Fail();` 修正為 `Assert.True(false);` (全部都會測試失敗)

完成後的程式碼如下：

```csharp
using ClassLibrary1;
using Xunit;

namespace ClassLibrary1Test
{
    public class MathLibTests
    {

        public MathLibTests()
        {

        }

        private MathLib CreateMathLib()
        {
            return new MathLib();
        }

        [Fact]
        public void Add_StateUnderTest_ExpectedBehavior()
        {
            // Arrange
            var unitUnderTest = CreateMathLib();
            int a = 2;
            int b = 2;

            // Act
            var result = unitUnderTest.Add(
                a,
                b);

            // Assert
            Assert.True(false);
        }

        [Fact]
        public void Sub_StateUnderTest_ExpectedBehavior()
        {
            // Arrange
            var unitUnderTest = CreateMathLib();
            int a = 2;
            int b = 2;

            // Act
            var result = unitUnderTest.Sub(
                a,
                b);

            // Assert
            Assert.True(false);
        }

        [Fact]
        public void Mul_StateUnderTest_ExpectedBehavior()
        {
            // Arrange
            var unitUnderTest = CreateMathLib();
            int a = 2;
            int b = 2;

            // Act
            var result = unitUnderTest.Mul(
                a,
                b);

            // Assert
            Assert.True(false);
        }

        [Fact]
        public void Div_StateUnderTest_ExpectedBehavior()
        {
            // Arrange
            var unitUnderTest = CreateMathLib();
            int a = 2;
            int b = 2;

            // Act
            var result = unitUnderTest.Div(
                a,
                b);

            // Assert
            Assert.True(false);
        }
    }
}
```

### 執行所有測試

1.  從主選單找到 \[測試\] -> \[視窗\] -> \[Test Explorer\] 即可開啟測試總管
    
2.  從主選單找到 \[測試\] -> \[執行\] -> \[所有測試\] 執行所有測試
    

### 如何測試私有類別

撰寫單元測試的時候，我們有時候會需要測試專案中的私有類別。我們先修改 `MathLib.cs` 類別檔，將 `public class MathLib` 改為 `class MathLib`，用來模擬私有類別的情況。

此時進行建置，你就會發現我們的 `ClassLibrary1Test` 單元測試專案已經無法看見 `ClassLibrary1` 專案中的 `MathLib` 類別了！

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/d46fc7dd2d3328bced16-image_3140.png)

這個問題在單元測試專案中算是相當常見的，而且有標準的解決方法，那就是使用 [InternalsVisibleToAttribute](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.internalsvisibletoattribute?view=netcore-1.0) 進行宣告。

以前我們在建立 .NET Framework 專案時，都會有個 `Properties\AssemblyInfo.cs` 檔案。但是在 .NET Core 中，由於架構改變的關係，原本這個檔案的內容，大多已經搬到 `*.csproj` 專案檔中，所以這個檔案預設已經不存在了！

但在寫單元測試的時候，你只要把它加回來即可。請修改  `ClassLibrary1`  專案，加入 `Properties\AssemblyInfo.cs` 檔案，並設定成以下兩行內容：

```csharp
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo(assemblyName: "ClassLibrary1Test")]
```

請注意：上述程式碼的 `ClassLibrary1Test` 字串，要設定為單元測試專案的組件名稱！

最後，我們只要再重建一次專案，就會發現單元測試專案已經可以成功建置與執行了！

**相關連結**

-   [Unit Testing in .NET Core | Microsoft Docs](https://learn.microsoft.com/en-us/dotnet/core/testing/)
-   [Unit Test Boilerplate Generator - Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=RandomEngy.UnitTestBoilerplateGenerator)
-   [InternalsVisibleToAttribute Class (System.Runtime.CompilerServices) | Microsoft Docs](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.internalsvisibletoattribute?view=netcore-1.0)
