擴充Assert,比對物件是否相等
前言
在測試的時候時常遇到需要比對Model物件是否相同,
此時使用Assert.AreEqual
就會比對失敗,
翻開程式碼會發現這個Method實際上是使用Object的Equals
比對兩物件,
而一般物件因為Equals
是比對Reference,
所以兩個物件不同Reference即使值一樣也會比對失敗。
以下為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);
}
實際測試
如同預期的會比對失敗。
著手修改
知道起因之後就可以開始進行調整,
可以將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,
何不就寫一個專門用於比對的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);
}
實際測試
排除比對部分成員
這裡修改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 MSTest | https://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 expression | https://stackoverflow.com/questions/671968/retrieving-property-name-from-lambda-expression |
Write By Charley Chang
新手發文,若有錯誤還請指教,
歡迎留言或Mail✉給我
本著作係採用創用 CC 姓名標示-非商業性-相同方式分享 4.0 國際 授權條款授權.