使用 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
- Assert.AreEqual
- Assert.AreNotEqual
- Assert.AreNotSame
- Assert.AreSame
- Assert.Contains
- Assert.ContainsSingle
- Assert.DoesNotContain
- Assert.DoesNotEndWith
- Assert.DoesNotMatchRegex
- Assert.DoesNotStartWith
- Assert.EndsWith
- Assert.Fail
- Assert.HasCount
- Assert.Inconclusive
- Assert.IsEmpty
- Assert.IsExactInstanceOfType
- Assert.IsFalse
- Assert.IsGreaterThan
- Assert.IsGreaterThanOrEqualTo
- Assert.IsInRange
- Assert.IsInstanceOfType
- Assert.IsLessThan
- Assert.IsLessThanOrEqualTo
- Assert.IsNegative
- Assert.IsNotEmpty
- Assert.IsNotExactInstanceOfType
- Assert.IsNotInstanceOfType
- Assert.IsNotNull
- Assert.IsNull
- Assert.IsPositive
- Assert.IsTrue
- Assert.MatchesRegex
- Assert.StartsWith
Assert.That- Assert.Throws
- Assert.ThrowsAsync
- Assert.ThrowsExactly
- Assert.ThrowsExactlyAsync
備註
從 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.AddValueFormatterAPI,用來自訂斷言失敗訊息中值的呈現方式。 -
Span<T> 以及 Memory<T> 的
Assert.HasCount超載。 - 針對
Assert.IsTrue、Assert.IsFalse、Assert.IsNull和Assert.IsNotNull的結構化斷言失敗訊息,其中包含已求值的運算式。 - 非同步
Assert.ThrowsAsync/Assert.ThrowsExactlyAsync方法的插值字串訊息過載,以及拒絕ValueTask<TResult>原本不會等待的 -回傳代理。 - 完整的例外細節,包括堆疊追蹤與內部例外,皆包含
Assert.Throws*失敗訊息中的內容。 - 斷言失敗堆疊,能隱藏 MSTest 實作幀,並以全精度渲染內建數值。
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 包括:
- StringAssert.Contains
- StringAssert.DoesNotMatch
- StringAssert.EndsWith
- StringAssert.Matches
- StringAssert.StartsWith
CollectionAssert 類別
使用 CollectionAssert 類別來比較物件集合,或確認集合的狀態。
警告
這個 CollectionAssert 類別很可能會在未來版本中被淘汰。 它主要是為了向下相容而維護,不建議用於新程式碼。 當 上 Assert 存在等價方法(例如 Assert.Contains、 Assert.DoesNotContain、 或 Assert.HasCount),則使用 Assert 以提升可發現性。
可用的 API 包括:
- CollectionAssert.AllItemsAreInstancesOfType
- CollectionAssert.AllItemsAreNotNull
- CollectionAssert.AllItemsAreUnique
- CollectionAssert.AreEqual
- CollectionAssert.AreEquivalent
- CollectionAssert.AreNotEqual
- CollectionAssert.AreNotEquivalent
- CollectionAssert.Contains
- CollectionAssert.DoesNotContain
- CollectionAssert.IsNotSubsetOf
- CollectionAssert.IsSubsetOf
建立自訂斷言 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 名稱相同,但功能不同。
最佳做法
使用具體的斷言:偏好
AreEqualoverIsTrue(a == b)以獲得更好的失敗訊息。包含描述性訊息:透過明確的斷言訊息幫助快速識別失敗。
一次測試一項:每個測試方法都應該驗證單一行為。
在 MSTest v3.8+ 中,使用
Throws/ThrowsExactly來處理例外情況時,偏好、Assert.Throws及其非同步版本(Assert.ThrowsExactly、ThrowsAsync),而非使用ThrowsExactlyAsync屬性。偏好
Assert高於StringAssert/CollectionAssert:為了更好的發現性和一致性,請使用該Assert類別。StringAssertCollectionAssert這些類別很可能會在未來版本中被棄用。擴充
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 - 避免在捕捉區塊中使用斷言。
另請參閱
- 在 MSTest 中撰寫測試
- 數據驅動測試
- TestContext 類別
- MSTest 分析儀