本篇作為筆記用途,記錄 .NET Unit Test 參考資料

Test Execution Workflow

using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MSTestUnitTests
{
    // 包含MSTest單元測試的類(必要)
    [TestClass]
    public class YourUnitTests
    {
        [AssemblyInitialize]
        public static void AssemblyInit(TestContext context)
        {
            // 在測試運行之前執行一次(選用)
        }

        [ClassInitialize]
        public static void TestFixtureSetup(TestContext context)
        {
            // 對測試類執行一次(選用)
        }

        [TestInitialize]
        public void Setup()
        {
            // 在每次測試之前運行(選用)
        }

        [AssemblyCleanup]
        public static void AssemblyCleanup()
        {
            // 測試運行後執行一次(選用)
        }

        [ClassCleanup]
        public static void TestFixtureTearDown()
        {
            // 在執行該類中的所有測試之後運行一次(選用)
            // 不保證在類進行所有測試後立即執行
        }

        [TestCleanup]
        public void TearDown()
        {
            // 在每次測試後運行(選用)
        }

        // 標記這是一種單元測試方法(必要)
        [TestMethod]
        public void YouTestMethod()
        {
            // 您的測試代碼在這裡
        }
    }
}

Attributes

MSTest v2.x NUnit xUnit.net 2.x 說明
[TestMethod] [Test] [Fact] 標記測試方法
[TestClass] [TestFixture] n/a 標記測試班
[TestInitialize] [SetUp] Constructor 在每個測試用例之前觸發
[TestCleanup] [TearDown] IDisposable.Dispose 在每個測試用例之後觸發
[ClassInitialize] [OneTimeSetUp] IClassFixture 測試用例開始之前的一次性觸發方法
[ClassCleanup] [OneTimeTearDown] IClassFixture 測試用例結束後的一次性觸發方法
[Ignore] [Ignore(“reason”)] [Fact(Skip=”reason”)] 忽略測試用例
[TestProperty] [Property] [Trait] 在測試上設置任意元數據
[DataRow] [Theory] [Theory] 配置數據驅動的測試
[TestCategory(“”)] [Category(“”)] [Trait(“Category”, “”)] 對測試用例或類進行分類

Assertions

// 測試指定的值是否相等。
Assert.AreEqual(28, _actualFuel);
// 測試指定的值是否不相等。與數字的AreEqual相同。
Assert.AreNotEqual(28, _actualFuel);
// 測試指定的對像是否都引用相同的對象
Assert.AreSame(_expectedRocket, _actualRocket);
// 測試指定的對像是否引用了不同的對象
Assert.AreNotSame(_expectedRocket, _actualRocket);
// 測試指定條件是否為真
Assert.IsTrue(_isThereEnoughFuel);
// 測試指定條件是否為假
Assert.IsFalse(_isThereEnoughFuel);
// 測試指定的對像是否為null
Assert.IsNull(_actualRocket);
// 測試指定的對像是否為非null
Assert.IsNotNull(_actualRocket);
// 測試指定的對像是否為預期類型的實例
Assert.IsInstanceOfType(_actualRocket, typeof(Falcon9Rocket));
// 測試指定的對像是否不是type的實例
Assert.IsNotInstanceOfType(_actualRocket, typeof(Falcon9Rocket));
// 測試指定的字符串是否包含指定的子字符串
StringAssert.Contains(_expectedBellatrixTitle, "Bellatrix");
// 測試指定的字符串是否以指定的子字符串開頭
StringAssert.StartsWith(_expectedBellatrixTitle, "Bellatrix");
// 測試指定的字符串是否與正則表達式匹配
StringAssert.Matches("(281)388-0388", @"(?d{3})?-? *d{3}-? *-?d{4}");
// 測試指定的字符串是否與正則表達式不匹配
StringAssert.DoesNotMatch("281)388-0388", @"(?d{3})?-? *d{3}-? *-?d{4}");
// 測試指定的集合是否具有相同的元素,並且順序和數量相同。
CollectionAssert.AreEqual(_expectedRockets, _actualRockets);
// 測試指定的集合是否不具有相同的元素,或者元素的順序和數量是否不同。
CollectionAssert.AreNotEqual(_expectedRockets, _actualRockets);
// 測試兩個集合是否包含相同的元素。
CollectionAssert.AreEquivalent(_expectedRockets, _actualRockets);
// 測試兩個集合是否包含不同的元素。
CollectionAssert.AreNotEquivalent(_expectedRockets, _actualRockets);
// 測試指定集合中的所有元素是否為預期類型的實例
CollectionAssert.AllItemsAreInstancesOfType(_expectedRockets, _actualRockets);
// 測試指定集合中的所有項目是否為非null
CollectionAssert.AllItemsAreNotNull(_expectedRockets);
// 測試指定集合中的所有項目是否唯一
CollectionAssert.AllItemsAreUnique(_expectedRockets);
// 測試指定的集合是否包含指定的元素
CollectionAssert.Contains(_actualRockets, falcon9);
// 測試指定的集合是否不包含指定的元素
CollectionAssert.DoesNotContain(_actualRockets, falcon9);
// 測試一個集合是否是另一個集合的子集
CollectionAssert.IsSubsetOf(_expectedRockets, _actualRockets);
// 測試一個集合是否不是另一個集合的子集
CollectionAssert.IsNotSubsetOf(_expectedRockets, _actualRockets);
// 測試委託指定的代碼是否拋出T類型的確切給定異常
Assert.ThrowsException<ArgumentNullException>(() => new Regex(null));

指定輸入值

MSTest 可以使用在 [DataRow] 來對 [TestMethod] 加上多筆測試用的輸入資料,裡如下面的用法:

[TestMethod]
[DataRow(08, 07)]
[DataRow(12, 11)]
[DataRow(00, 23)]
public void 執行GetLastHourPeriod會取得上一個小時的區間(int hour, int expectedHour)
{
    var date = new DateTime(2020, 01, 01, hour, 00, 00);
    var result = TimeHelper.GetLastHourPeriod(date);

    Assert.AreEqual(result.StartTime.Hour, expectedHour);
    Assert.AreEqual(result.EndTime.Hour, hour);
}

並行執行單元測試

可以在單元測試的專案資料夾中加入 .runsettings 設定檔,並填寫以下設定:

<RunSettings>
  <MSTest>
    <Parallelize>
      <Workers>4</Workers>
      <Scope>MethodLevel</Scope>
    </Parallelize>
  </MSTest>
</RunSettings>
  • Workers 設定執行測試的並行處理數量。若要設定成連續的序列執行,設定成 0 即可。
  • Scope 指執行的程序是使用 Method Level 或 Class Level。若設定為 Method Level 則所有測試方法都會並行執行,若設定成 Class Level 則類別中的測試方法將採序列執行。如果測試方法之間有相依性,則必須採用 Class Level 的方式來處理。

.NET 單元測試

我會盡可能使用 MSTest 的方式來寫單元測試,只使用 MSTest 的內建功能,不使用第三方套件。

IOption

在 .NET 的架構下(非 .NET Framework),你會看到大量使用 Options 模式來處理設定值,如果你的專案中也有使用此模式,可以參考下面的方式來將設定注入至測試方法中。

使用 Microsoft.Extensions.Options 這個命名空間中的 Options.Create() 靜態方法,讓你可以產生出適合的注入設定物件。

.Net Unit Testing - Mock IOptions https://stackoverflow.com/questions/40876507/net-core-unit-testing-mock-ioptionst

https://stackoverflow.com/questions/41399526/how-to-initialize-ioptionappsettings-for-unit-testing-a-net-core-mvc-service/41399622

using using Microsoft.Extensions.Options;

var appSettings = new AppSettings
    {
        Setting1 = "...",
        Setting2 = "...",
    };
var options = Options.Create(appSettings);
var controller = new MyController(options);

ILogger、IMemoryCache、IHttpClient

使用 ServiceCollection 來建立 ServiceProvider,並透過 GetService 取得需要的物件。

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging.Abstractions;

[TestClass]
public class SampleUnitTest
{
    private ILoggerFactory loggerFactory;
    private IMemoryCache memoryCache;
    private IHttpClientFactory httpClientFactory;

    [TestInitialize]
    public void Initialize()
    {
        var services = new ServiceCollection();
        // 註冊 ILoggerFactory
        services.AddSingleton<ILoggerFactory, NullLoggerFactory>();
        // 註冊 IMemoryCache
        services.AddMemoryCache();
        // 註冊 HttpClient 和 HttpClientFactory
        services.AddHttpClient();

        // 建立 ServiceProvider
        var serviceProvider = services.BuildServiceProvider();

        // 取得 ILoggerFactory 和 IMemoryCache
        loggerFactory = _serviceProvider.GetService<ILoggerFactory>();
        memoryCache = serviceProvider.GetService<IMemoryCache>();
        memoryCachhttpClientFactory = serviceProvider.GetService<IHttpClientFactory>();
    }

    [TestMethod]
    public void SampleTest()
    {
        var logger = loggerFactory.CreateLogger<MyController>();
        var controller = new MyController(logger, memoryCache);

        controller.TestSomething();
    }
}

參考資料:


Poy Chang

Trial and Error