[MSTest] Assert比對兩物件是否相等

  • 376
  • 0
  • 2022-07-02

擴充Assert,比對物件是否相等

前言

在測試的時候時常遇到需要比對Model物件是否相同,
此時使用Assert.AreEqual 就會比對失敗,
翻開程式碼會發現這個Method實際上是使用Object的Equals比對兩物件,
而一般物件因為Equals是比對Reference,
所以兩個物件不同Reference即使值一樣也會比對失敗。

以下為Assert.AreEqual的Source Code
Assert.AreEqual 的 Source Code
開始修改

這裡建立一個User的Class如下

class User
{
	public int ID { get; set; }
	public string Name { get; set; }
}

並且宣告兩個內容相同的物件後進行比對

[TestMethod]
public void 比對User是否相同()
{
    // 宣告物件1
    User user1 = new User
    {
        ID = 1,
        Name = "Tony"
    };

    // 宣告物件2
    User user2 = new User
    {
        ID = 1,
        Name = "Tony"
    };

    // 比對是否相等
    Assert.AreEqual(user1, user2);
}
實際測試

如同預期的會比對失敗。

未覆寫Equals前的測試結果

 

著手修改

知道起因之後就可以開始進行調整,
可以將object的Equals這個Method進行Override,
使其可以應用Override後的邏輯來比對兩物件。

修改程式

這裡調整上方的User物件,
覆寫Equals的比對邏輯

class User
{
	public int ID { get; set; }
	public string Name { get; set; }
	
	// 覆寫object.Equals()
	
public override bool Equals(object obj)
	{
		if (this == obj) return true;	// 兩物件完全相等時直接回傳true
		if (this == null || obj == null) return false;	// 其中一個物件為null時回傳false,避免後續比對出現NullReferenceException
		return ((User)obj).ID == ID && ((User)obj).Name == Name;	// 自行定義何謂物件相等
	}
}
實際測試

修改完以後這裡再跑一次測試,
這個時候就發現測試通過了。

覆寫Equals後的測試結果

 

換個想法

由上方可以知道,
如果要比對物件就要擴充Equals,
何不就寫一個專門用於比對的Lib來比對物件。

擴充Assert

這裡擴充了Assert的Method,
增加AreEquals將的Member逐一取出並比對,
另外使用MemberExpression排除不比對的Member。

/// <summary>
/// 單元測試驗證擴充方法
/// </summary>
public static class AssertExt
{
	/// <summary>
	/// 擴充 比對兩物件是否相等
	/// </summary>
	/// <typeparam name="T"></typeparam>
	/// <param name="assert"></param>
	/// <param name="expected">預期物件</param>
	/// <param name="actual">實際物件</param>
	/// <param name="exceptMembers">排除比對成員,若留空則比對所有成員</param>
	/// <exception cref="AssertFailedException">比對失敗錯誤訊息</exception>
	public static void AreEquals<T>(
		this Assert assert,
		T expected,
		T actual,
		List<Expression<Func<T, object>>> exceptMembers = null)
	{
		List<string> exceptMemberNames = exceptMembers == null ?
			new List<string>() :
			exceptMembers.Select(x =>
				((MemberExpression)((x.Body.NodeType == ExpressionType.Convert || x.Body.NodeType == ExpressionType.ConvertChecked) ?
					((UnaryExpression)x.Body).Operand : x.Body)).Member.Name).ToList();

		StringBuilder sb = new StringBuilder();
		foreach (var prop in
			typeof(T).GetProperties().Where(x => !exceptMemberNames.Contains(x.Name)))
		{
			var valExpected = prop.GetValue(expected);
			var valActual = prop.GetValue(actual);
			if (!Equals(valExpected, valActual))
				sb.Append($"[{prop.Name}]=>Expected:<{valExpected}>, Actual:<{valActual}>; ");
		}

		if (sb.Length > 0) throw new AssertFailedException($"Assert.AreEquals failed. {sb}");
	}
}
修改程式

這裡先移除原先覆寫Equals的相關程式,
並且使用上方擴充的Assert.That.AreEquals()進行比對

[TestMethod]
public void 比對User是否相同()
{
    // 宣告物件1
    User user1 = new User
    {
        ID = 1,
        Name = "Tony"
    };

    // 宣告物件2
    User user2 = new User
    {
        ID = 1,
        Name = "Tony"
    };

    // 比對是否相等
    Assert.That.AreEquals(user1, user2);
}
實際測試
移除Equals覆寫,並採用Assert擴充的測試結果

 

排除比對部分成員

這裡修改User故意將ID設為不同值後進行測試。

[TestMethod]
public void 比對User是否相同()
{
    // 宣告物件1
    User user1 = new User
    {
        ID = 1,
        Name = "Tony"
    };

    // 宣告物件2
    User user2 = new User
    {
        ID = 2,
        Name = "Tony"
    };

    // 比對是否相等
    Assert.That.AreEquals(user1, user2);
}

不出所料測試馬上失敗。

 


這時我們再把排除比對的欄位加入參數中

Assert.That.AreEquals(user1, user2,
	new System.Collections.Generic.List<System.Linq.Expressions.Expression<System.Func<User, object>>>
	{
		x => x.ID
	});

再跑一次測試,
這個時候測試就成功了。

引用清單

How to create custom assertions in C# with MSTesthttps://dev.to/canro91/how-to-create-custom-assertions-in-c-with-mstest-4831
[Unit Test Tricks] 如何驗證兩個自訂型別物件集合相等https://dotblogs.com.tw/hatelove/2014/06/06/how-to-assert-two-collection-equal
Retrieving Property name from lambda expressionhttps://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression

 

Write By Charley Chang 


新手發文,若有錯誤還請指教,
歡迎留言或Mail✉給我

創用 CC 授權條款


本著作係採用創用 CC 姓名標示-非商業性-相同方式分享 4.0 國際 授權條款授權.