MSTest 斷言

使用 Assert 命名空間的 Microsoft.VisualStudio.TestTools.UnitTesting 類別來驗證特定功能。 測試方法在你的應用程式中執行程式碼,但只有在你加入 Assert 陳述式時才會報告正確性。

概觀

MSTest 提供三種斷言類別:

Class 目標
Assert 值、型別與例外的通用斷言。
StringAssert 字串專屬的斷言,用於模式、子字串與比較。
CollectionAssert 用於比較與驗證集合的集合斷言。

這很重要

新程式碼一定要用類別 Assert 。 StringAssert CollectionAssert這些類別很可能會在未來版本中被棄用。 它們主要是為了向下相容而維護,但不建議使用,因為將斷言分成三種類型會影響被發現性。

所有斷言方法都接受一個可選的訊息參數,當斷言失敗時會顯示,幫助你找出原因:

Assert.AreEqual(expected, actual, "Values should match after processing");

Assert 類別

使用 Assert 類別來驗證受測程式代碼是否如預期般運作。

備註

從 MSTest 4.0 開始,所有 Assert API 都會擷取參數表達式並將其包含在失敗訊息中。 此支援提供更豐富的診斷,無需手動 message 參數。

常見的斷言方法

[TestMethod]
public async Task AssertExamples()
{
    // Equality
    Assert.AreEqual(5, calculator.Add(2, 3));
    Assert.AreNotEqual(0, result);

    // Reference equality
    Assert.AreSame(expected, actual);
    Assert.AreNotSame(obj1, obj2);

    // Boolean conditions
    Assert.IsTrue(result > 0);
    Assert.IsFalse(string.IsNullOrEmpty(name));

    // Null checks
    Assert.IsNull(optionalValue);
    Assert.IsNotNull(requiredValue);

    // Type checks
    Assert.IsInstanceOfType<IDisposable>(obj);
    Assert.IsNotInstanceOfType<string>(obj);

    // Exception testing (MSTest v3.8+)
    Assert.ThrowsExactly<ArgumentNullException>(() => service.Process(null!));
    await Assert.ThrowsExactlyAsync<InvalidOperationException>(
        async () => await service.ProcessAsync());
}

Assert.That 方法

從 MSTest 4.0 開始,會 Assert.That 評估任何布林運算式並產生明確的失敗訊息。 為提供更豐富的診斷資訊,Assert.That 使用 [CallerArgumentExpression] 來自動擷取運算式文字。

Assert.That(order.Total > 0);

可用的 API

備註

從 MSTest 3.8 開始,集合斷言包括 Assert.Contains、 Assert.DoesNotContain、 Assert.HasCount、 Assert.IsEmptyAssert.IsNotEmptyAssert.ContainsSingle。

從 MSTest 3.10 開始,比較斷言包括 Assert.IsInRange、 Assert.IsGreaterThan、 Assert.IsGreaterThanOrEqualToAssert.IsLessThanAssert.IsLessThanOrEqualToAssert.IsPositiveAssert.IsNegative。

從 MSTest 3.10 開始,字串匹配斷言包括 Assert.StartsWith、 Assert.EndsWith、 Assert.MatchesRegex、 Assert.DoesNotStartWithAssert.DoesNotEndWithAssert.DoesNotMatchRegex。

從 MSTest 4.1 開始,Assert.IsExactInstanceOfType 和 Assert.IsNotExactInstanceOfType 都需要完全相符的型別。 與 Assert.IsInstanceOfType不同,這些方法不匹配導出型別。

MSTest 4.3 中的新集合與等價斷言

備註

以下斷言方法是在 MSTest 4.3.0 中引入的。

  • Assert.AreSequenceEqual / Assert.AreNotSequenceEqual — 元素序列比較。 跳過 SequenceOrder.InAnyOrder 以忽略元素順序。
  • Assert.AreEquivalent / Assert.AreNotEquivalent — 兩個物件或收藏的深度結構比較。
  • Assert.ContainsAll / Assert.DoesNotContainAll —— 斷言一個集合包含(或不包含)所有預期元素。
  • Assert.AreAllNotNull ——斷言集合中的每個元素都是非-null。
  • Assert.AreAllDistinct ——主張集合中的所有元素都是不同的。
  • Assert.AreAllOfType ——斷言集合中的每個元素都屬於預期型態。

比較集合時,應優先使用這些方法,而不是使用 Assert.AreEqual,因為它比較的是參照而非元素。

MSTest 4.3 也新增了:

  • 用於自訂值在斷言失敗訊息中呈現方式的實驗性 Assert.AddValueFormatter API
  • Span<T> 以及 Memory<T> 的 Assert.HasCount超載。
  • 針對 Assert.IsTrue、Assert.IsFalse、Assert.IsNull 和 Assert.IsNotNull 的結構化斷言失敗訊息,其中包含已求值的運算式。
  • 非同步 Assert.ThrowsAsync/Assert.ThrowsExactlyAsync 方法的插值字串訊息過載,以及拒絕 ValueTask<TResult>原本不會等待的 -回傳代理。
  • 在 Assert.Throws* 失敗訊息中提供完整的例外狀況詳細資料,包括堆疊追蹤和內部例外狀況。
  • 斷言失敗堆疊,能隱藏 MSTest 實作幀,並以全精度渲染內建數值。

MSTest 4.4 為 Assert.IsNotEmpty、Assert.IsEmpty 及其餘的集合 API 新增了 Span 和 Memory 多載。 這些多載可接受 Span<T>、ReadOnlySpan<T>、Memory<T> 和 ReadOnlyMemory<T>:

  • 所有項目檢查:AreAllDistinct、AreAllNotNull 和 AreAllOfType。
  • 比較:AreEquivalent、、AreNotEquivalentAreSequenceEqual、 AreNotSequenceEqual。
  • 包含:Contains、ContainsAll、ContainsSingle、DoesNotContain 和 DoesNotContainAll。

在 MSTest 4.4 與 MTP 2.4 中,支援此功能的 IDE 與報告工具會以分開的結構化屬性接收斷言的預期值與實際值。 消費者不需要將這些數值從故障訊息中解讀出來。

Assert.AddValueFormatter 傳回一個 IDisposable 註冊資訊。 處置該註冊以移除格式化器。 格式化器僅適用於當前非同步上下文,因此平行測試可以使用不同的格式化器,而不會改變彼此的輸出。 由於此 API 在 MSTest 4.3 中屬於實驗性功能,因此在使用前,請先認可或隱藏 MSTESTEXP 診斷警告。

使用 Assert.Scope() 的軟性斷言

這很重要

Assert.Scope() 是一個實驗性的 API。 使用它會產生 MSTESTEXP 診斷,您可以抑制該診斷(例如使用 #pragma warning disable MSTESTEXP,或在專案的 .editorconfig 檔案中抑制),以表示您已知悉 API 的形狀和行為在未來版本中可能會變更。

預設情況下,每個斷言一失敗就會立即拋出 AssertFailedException,從而立即結束測試。 Assert.Scope() 引入 軟斷言:當作用域處於啟用狀態時,斷言失敗會被收集而非拋出,因此執行持續,且可一次看到範圍內的每一次失敗。 當示波器被處理時,收集到的故障會一同報告:

[TestMethod]
public void ValidatePerson()
{
    using (Assert.Scope())
    {
        Assert.AreEqual("Jane", person.FirstName); // failure collected, execution continues
        Assert.AreEqual("Doe", person.LastName);   // failure collected, execution continues
        Assert.IsTrue(person.IsActive);            // failure collected, execution continues
    }
    // On Dispose, all collected failures are reported together.
}

當範圍被處置時:

  • 若恰好收集到一個失敗,則會擲回原始的 AssertFailedException。
  • 如果收集到多個失敗,則會擲出單一的 AssertFailedException,將它們全部封裝在 AggregateException 中。

後置條件不會在作用域內強制執行

因為失敗的斷言不再丟入作用域,執行後的程式碼無法依賴斷言是否成功。 這適用於 所有 後置條件,包括可空性與型別狹窄:

using (Assert.Scope())
{
    Assert.IsNotNull(item);
    // 'item' might still be null here: the failure was collected, not thrown.
    Assert.AreEqual("expected", item.Value);
    // 'item.Value' might not equal "expected" either.
}

如果失敗的斷言會在該作用域內後續的某一行引發 NullReferenceException(或任何其他例外),那麼該次要例外只是先前已收集到的失敗所呈現的症狀,而不是另一個獨立的錯誤。 當作用範圍被釋放時,原始的斷言失敗仍會被回報。

具回傳值的斷言在作用域內失敗時會回傳 null/default

有些斷言會在成功時回傳值,例如 Throws 和 ThrowsExactly 會回傳攔截到的例外,而 ContainsSingle 會回傳相符的元素。 當這些斷言其中之一在某個範圍內失敗時,系統會收集該失敗,且方法會傳回null/default,而非拋出:

using (Assert.Scope())
{
    // No exception is thrown by the lambda, so the assertion fails. The failure is
    // collected and 'ex' is null. Accessing 'ex' below throws NullReferenceException.
    InvalidOperationException ex = Assert.Throws<InvalidOperationException>(() => { });
    _ = ex.Message; // NullReferenceException—don't use the return value in a scope
}

不要依賴在示波器內軟性斷言所回傳的價值。 如果你需要回傳值(例如捕捉到的例外狀況),請在該作用域外呼叫斷言,或重新調整測試結構,使任何內容都不要依賴該回傳值,直到該作用域已釋放之後。

Assert.Fail 而且 Assert.Inconclusive 永遠要投擲

Fail 和 Inconclusive 絕不會是軟的。 即使在示波器內,他們總是立即拋出,因為他們表達的是無條件的測試結果。 當某個狀況很危急,且其他檢查無法有效進行時,才用其中一種。

巢狀示波器不被支援

你不能巢狀呼叫 Assert.Scope()。 一次只能啟用一個斷言範圍。

StringAssert 類別

使用 StringAssert 類別來比較並檢查字串。

警告

這個 StringAssert 類別很可能會在未來版本中被淘汰。 它只是為了向下相容而維護,不建議用於新程式碼。 所有 StringAssert 方法在 Assert 類別中都有對應的方法,讓它們更容易被找到。 若要移轉現有用法,請參閱分析器 MSTEST0046。

可用的 API 包括:

CollectionAssert 類別

使用 CollectionAssert 類別來比較物件集合,或確認集合的狀態。

警告

這個 CollectionAssert 類別很可能會在未來版本中被淘汰。 它主要是為了向下相容而維護,不建議用於新程式碼。 當 上 Assert 存在等價方法(例如 Assert.Contains、 Assert.DoesNotContain、 或 Assert.HasCount),則使用 Assert 以提升可發現性。

可用的 API 包括:

建立自訂斷言 Assert.That

內建的斷言方法無法涵蓋所有情境。 若要使用您自己的檢查來擴充斷言基礎結構,MSTest 會公開 Assert.That 單例屬性作為擴充點。 你可以在實例型別上以 C# 擴充方法 Assert 的方式新增自訂斷言,呼叫者則以熟悉 Assert.That.MyAssertion(...) 的語法呼叫它們。

為了更易發現,建議將專案範圍的斷言組織在專用的靜態類別中。 經由 Assert.That 存取的自訂斷言會與 IntelliSense 中的內建方法一同出現,因此取用者不必另外記住一個輔助型別。

撰寫自訂陳述

新增一個擴充方法,針對該 Assert 型別,當條件失敗時拋 AssertFailedException 出:

using System;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;

public static class CustomAssertExtensions
{
    public static void IsPrime(this Assert assert, int value)
    {
        if (value < 2 || Enumerable.Range(2, (int)Math.Sqrt(value) - 1).Any(i => value % i == 0))
        {
            throw new AssertFailedException($"Assert.That.IsPrime failed. Value <{value}> is not a prime number.");
        }
    }
}

使用自訂斷言

在匯入包含你擴充方法的命名空間後,透過以下 Assert.That方式呼叫你的自訂斷言:

[TestMethod]
public void Compute_ReturnsPrime()
{
    int result = _calculator.NextPrime(10);
    Assert.That.IsPrime(result);
}

StringAssert 和 CollectionAssert 上的擴充掛鉤

StringAssert.That 和 CollectionAssert.That 屬性為了向後相容性,公開了相同的單例模式。 對於新的自訂斷言,一律以 Assert.That 為目標。 否則,你的助手會繼承和舊有類別一樣的可偵測性問題,如果 StringAssert 和 CollectionAssert 被棄用,他們就需要遷移。

Assert.That 性質與 Assert.That(...) 方法

備註

不要將Assert.That單例屬性(作為擴充點)與 MSTest 3.8 中新增的 Assert.That(() => condition)方法混淆。 後者接受布林運算式,並透過分析表達式樹產生詳細的失敗訊息(例如, Assert.That(() => order.Total > 0))。 這兩個 API 名稱相同,但功能不同。

最佳做法

  • 使用具體的斷言:偏好 AreEqual over IsTrue(a == b) 以獲得更好的失敗訊息。

  • 包含描述性訊息:透過明確的斷言訊息幫助快速識別失敗。

  • 一次測試一項:每個測試方法都應該驗證單一行為。

  • 在 MSTest v3.8+ 中,使用Throws/ThrowsExactly來處理例外情況時,偏好、Assert.Throws及其非同步版本(Assert.ThrowsExactly、ThrowsAsync),而非使用ThrowsExactlyAsync屬性。

  • 偏好 Assert 高於 StringAssert/CollectionAssert:為了更好的發現性和一致性,請使用該 Assert 類別。 StringAssert CollectionAssert這些類別很可能會在未來版本中被棄用。

  • 擴充 Assert.That 自訂斷言:為了保持一致的可發現性,請將自訂斷言作為擴充方法加入 Assert ,並透過 Assert.That呼叫。 不要在新程式碼中以 StringAssert.That 或 CollectionAssert.That 為目標。

以下分析器有助於確保斷言的正確使用:

  • MSTEST0006 - 避免 ExpectedException 屬性,改用 Assert.Throws 方法。
  • MSTEST0017 - 斷言論證應依正確順序傳遞。
  • MSTEST0023 - 不要否定布林值斷言。
  • MSTEST0025 - 偏好使用 Assert.Fail,而不是永遠錯誤的條件。
  • MSTEST0026 - 斷言論證應避免條件存取。
  • MSTEST0032 - 檢視永遠為真的斷言條件。
  • MSTEST0037 - 使用正確的斷言方法。
  • MSTEST0038 - 避免 Assert.AreSame 使用價值類型。
  • MSTEST0039 - 使用更新 Assert.Throws 的方法。
  • MSTEST0040 - 避免在非同步 void 上下文中使用斷言。
  • MSTEST0046 - 用 Assert 代替 StringAssert。
  • MSTEST0051 - Assert.Throws 應該包含一個陳述。
  • MSTEST0053 - 避免 Assert 格式參數。
  • MSTEST0058 - 避免在捕捉區塊中使用斷言。

另請參閱