[Unit Testing]如何驗證兩個自訂型別物件集合相等
前言
雖然標題上掛著測試,但其實比較兩個自訂型別物件是否相等,是屬於 C# 的基本觀念,然而卻是在寫測試程式時,困擾著很多朋友的一道門檻。
這篇文章只會簡單的帶過 C# 的基本觀念,重點會放在測試程式中,怎麼驗證兩個集合中物件的值是否相等。
AreSame() VS AreEqual()
在 MSTest Framework 中,Assert 中的 AreEqual()
與 AreSame()
是不一樣的。
AreSame()
指兩個物件是否為相同的物件,也就是變數的參考位址是否一致。如果是 value type ,那通常就是不一致的,因為 value type 是被存放在 stack 的記憶體中,如下面的測試結果會是失敗的。
[TestMethod]
public void Test_AreSame()
{
var expected = 1;
var actual = 1;
// 比較 expected 與 actual 必須是同一個位址參考的物件
Assert.AreSame(expected, actual);
}
同樣的例子,使用 AreEqual()
則會通過測試,原因是 value type 通常都會覆寫 Equals()
的相關方法。
[TestMethod]
public void Test_AreEqual()
{
var expected = 1;
var actual = 1;
Assert.AreEqual(expected, actual);
}
簡單地說就是:AreSame()
一定要是同一塊記憶體位址,也就是要同一個物件,才會是 true 。 AreEqual()
則看比較的物件型別如何定義 Equals()
,若無定義,則是使用繼承自 Object 型別上的 Equals()
方法,也就是比較位址。因此,如果是自己定義的 class ,沒有覆寫 Equals()
,那麼預設就是比記憶體位址是否相同,這時 AreSame()
與 AreEqual()
的結果會是一樣的。
比較自訂 Customer 型別的物件是否相等
有了上面對 AreSame()
與 AreEqual()
的了解,這邊第一個範例先來說明,當自訂一個 Customer 型別時,在測試中該如何比較兩個 Customer instance 是否相等,測試程式如下:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void 驗證Cusotmer是否相同_Same()
{
var expected = new Customer { Id = 1, Age = 10, Name = "Joey", Phone = "1999" };
var actual = new Customer { Id = 1, Age = 10, Name = "Joey", Phone = "1999" };
// 比較 expected 與 actual 必須是同一個位址參考的物件
Assert.AreSame(expected, actual);
}
[TestMethod]
public void 驗證Cusotmer是否相等_Equal()
{
var expected = new Customer { Id = 1, Age = 10, Name = "Joey", Phone = "1999" };
var actual = new Customer { Id = 1, Age = 12, Name = "Joey", Phone = "2000" };
// 即使 Age 與 Phone 值不一樣,但只要 Customer.Equals 回傳是 true, Assert.AreEqual() 就會是 true
Assert.AreEqual(expected, actual);
}
}
public class Customer
{
/// <summary>
/// 若 Id 與 Name 值一樣就代表相等
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object obj)
{
// 正常來說覆寫 Equals() 應該也要覆寫 GetHashCode()
// 一般的情況,如果只是要比較兩個 Customer 物件是否相同,不建議覆寫 Equals 與 GetHashCode(),而是使用 IEqualityComparer<T> 來定義怎麼比較 T 是否相等
var input = obj as Customer;
if (input == null)
{
return false;
}
else
{
var result = this.Id == input.Id
&& this.Name == input.Name;
return result;
}
}
public int Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
使用 AreSame()
驗證兩個 Customer instance 的測試方法,肯定會失敗,因為兩個物件本來就不相同。但是如果是要驗證兩個物件是不是相等,就可以依據需求覆寫 Customer 的 Equals()
。以上面的例子來說,雖然 Customer 上有 Id, Age, Name, Phone 四個 property ,但只要 Id 與 Name 的值相同,我們就視為這兩個 Customer instance 相等,那就可以如上面的範例一樣,直接覆寫 Equals()
方法,並比較 Id 與 Name 有沒相等即可。
然而,單一物件可以直接覆寫 Equals()
來定義何謂相等,但如果是兩個 Customer 的集合呢?
使用 CollectionAssert 比較兩個 Customer 集合是否相等
要怎麼比較兩個 Customer 集合是否相等呢?這邊的例子我使用的集合是 List<T> , 這也就代表它也是 ICollection<T>, 也是 IEnumerable<T> 。範例如下:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void 驗證Cusotmer集合是否相等_Assert_AreEqual()
{
var expected = GetCustomers();
var actual = GetCustomers();
// 因為兩個 List<Customer> 的 instance 不是同一個物件,所以驗證失敗
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void 驗證Cusotmer集合是否相等_CollectionAssert_AreEqual()
{
var expected = GetCustomers();
var actual = GetCustomers();
CollectionAssert.AreEqual(expected, actual);
}
private List<Customer> GetCustomers()
{
return new List<Customer>
{
new Customer{Id=1, Age= 10, Name = "Joey", Phone="1999"},
new Customer{Id=2, Age= 20, Name = "Kevin", Phone="2999"},
new Customer{Id=3, Age= 30, Name = "Demo", Phone="3999"},
};
}
}
public class Customer
{
public int Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public override bool Equals(object obj)
{
// 正常來說覆寫 Equals() 應該也要覆寫 GetHashCode()
// 一般的情況,如果只是要比較兩個 Customer 物件是否相同,不建議覆寫 Equals 與 GetHashCode(),而是使用 IEqualityComparer<T> 來定義怎麼比較 T 是否相等
var input = obj as Customer;
if (input == null)
{
return false;
}
else
{
var result = this.Id == input.Id
&& this.Name == input.Name;
return result;
}
}
}
如上一段所提,使用 AreEqual()
直接拿來比較兩個 List<Customer> 結果一定會是 false ,因為兩個 List<Customer> 是不同的 instance ,位址當然就不一樣。
但如果使用 CollectionAssert.AreEqual() 來比較兩個 List<Customer> 時,則測試的結果會是通過,因為讀者可以想像 CollectionAssert.AreEqual()
背後運作,就是將兩個集合攤開來跑,每一個 element 都必須相等,而這個相等會呼叫該 element 的 Equals()
方法來比較。
請留意 MSDN 繁體中文上備註的解釋,有一段是翻譯錯誤的。英文原文:「Elements are equal if their values are equal, not if they refer to the same object.」,中文的意思應該是:「當結果為相等時,代表每個項目的值都相等,而不是指每個比較的項目都必須是相同物件。」,目前 MSDN 上錯誤的中文翻譯是:「如果項目的值相等,它們就相等,但是如果它們參考相同的物件,則不相等。」
然而, Customer 物件可能是在 production code 中定義的,我們無法直接覆寫其 Equals()
,又或者是在不同情境中或是在測試案例中, Customer 物件相等的定義可能不同,此時一般來說應該使用 IEqualityComparer<Customer> 來定義特定的相等條件。因為是使用介面,所以當需要不同相等的定義時,只需要傳入不同的實作即可。
然而 CollectionAssert 的 AreEqual()
多載中,傳入的參數並非 IEqualityComparer ,而是 IComparer ,筆者自己從 MSDN 上說明的推測,是因為 CollectionAssert 還要比較順序, IComparer 除了比較相等以外,還能進行排序。另外一個比較不方便的地方是,傳入的 IComparer 並非泛型的 ICollection<T> ,且尷尬的是 ICollection<T> 又剛好沒有繼承自 ICollection,想來應該是因為整個 CollectionAssert 是基於 ICollection 而非 ICollection<T> 的原因。
沒關係,來看一下該怎麼使用 CollectionAssert.AreEqual(ICollection expected, ICollection actual, IComparer comparer)
的方法,測試程式碼如下所示:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void 驗證Cusotmer集合是否相等_CollectionAssert_AreEqual()
{
var expected = GetCustomers();
var actual = GetCustomers();
//CollectionAssert.AreEqual(expected, actual);
CollectionAssert.AreEqual(expected, actual, new MyCustomerComparer());
}
[TestMethod]
public void 驗證Cusotmer集合是否相等_CollectionAssert_AreEqual_只有Id跟Name相等()
{
var expected = new List<Customer>
{
new Customer{Id=1, Age= 10, Name = "Joey", Phone="1999"},
new Customer{Id=2, Age= 20, Name = "Kevin", Phone="2999"},
new Customer{Id=3, Age= 30, Name = "Demo", Phone="3999"},
};
var actual = new List<Customer>
{
new Customer{Id=1, Age= 40, Name = "Joey", Phone="1000"},
new Customer{Id=2, Age= 50, Name = "Kevin", Phone="2000"},
new Customer{Id=3, Age= 60, Name = "Demo", Phone="3000"},
};
//CollectionAssert.AreEqual(expected, actual);
CollectionAssert.AreEqual(expected, actual, new MyCustomerComparer());
}
private List<Customer> GetCustomers()
{
return new List<Customer>
{
new Customer{Id=1, Age= 10, Name = "Joey", Phone="1999"},
new Customer{Id=2, Age= 20, Name = "Kevin", Phone="2999"},
new Customer{Id=3, Age= 30, Name = "Demo", Phone="3999"},
};
}
}
public class Customer
{
public int Id { get; set; }
public int Age { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
}
internal class MyCustomerComparer : IComparer, IComparer<Customer>
{
public int Compare(object x, object y)
{
if (x is Customer && y is Customer)
{
return this.Compare((Customer)x, (Customer)y);
}
else
{
throw new ArgumentException("傳入參數非Customer型別");
}
}
public int Compare(Customer x, Customer y)
{
if (x.Id.CompareTo(y.Id) != 0)
{
return x.Id.CompareTo(y.Id);
}
else if (x.Name.CompareTo(y.Name) != 0)
{
return x.Name.CompareTo(y.Name);
}
else
{
return 0;
}
}
}
首先留意到 Customer 類別已經沒有覆寫 Equals()
了,這個例子是透過 IComparer 來比較兩個 ICollection<Customer> ,也由於 IComparer<T> 並沒有繼承自 IComparer ,所以這邊定義了一個 MyCustomerComparer 同時實作 IComparer 與 IComparer<T> ,讓非泛型介面的實作方法,去呼叫泛型介面的方法即可。在這個例子中,定義了兩個 Customer 物件只要 Id 與 Name 相等,就代表這兩個 Customer 物件相等。因此,這兩個測試案例都會通過。
使用 IComparer 來比較相等的好處是,可以獨立於原本的 Customer class 定義之外,且允許多種不同的比較方式。
但是,可以看到使用 CollectionAssert 似乎還是有點麻煩,尤其是卡東卡西的 ICollection 。接下來介紹另一種比較方式,是透過 LINQ 的 SequenceEqual()
來比較兩個集合是否相等。
使用 SequenceEqual() 比較兩個 Customer 集合是否相等
雖然以嚴謹的單元測試定義來說,在測試程式中,要盡量避免相依於其他的 API ,因為當測試失敗或發生錯誤時,才能更精準、明確地定義就是這個測試案例發生問題,而不是被其他因素影響了。但 LINQ 的方法基本上不算外部的 API ,把 LINQ 的方法當作不會有錯也還算是一個合理的假設。因此,接下來的例子,就是透過 IEnumerable<T> 的擴充方法 SequenceEqual()
來比較兩個 IEnumerable<Customer> 是否相等。測試程式如下所示:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void 驗證Cusotmer集合是否相等_SequenceEqual()
{
var expected = GetCustomers();
var actual = GetCustomers();
//CollectionAssert.AreEqual(expected, actual, new MyCustomerComparer());
Assert.IsTrue(expected.SequenceEqual(actual, new MyCustomerEqualityComparer()));
}
[TestMethod]
public void 驗證Cusotmer集合是否相等_SequenceEqual_只有Id跟Name相等()
{
var expected = new List<Customer>
{
new Customer{Id=1, Age= 10, Name = "Joey", Phone="1999"},
new Customer{Id=2, Age= 20, Name = "Kevin", Phone="2999"},
new Customer{Id=3, Age= 30, Name = "Demo", Phone="3999"},
};
var actual = new List<Customer>
{
new Customer{Id=1, Age= 40, Name = "Joey", Phone="1000"},
new Customer{Id=2, Age= 50, Name = "Kevin", Phone="2000"},
new Customer{Id=3, Age= 60, Name = "Demo", Phone="3000"},
};
//CollectionAssert.AreEqual(expected, actual, new MyCustomerComparer());
Assert.IsTrue(expected.SequenceEqual(actual, new MyCustomerEqualityComparer()));
}
private List<Customer> GetCustomers()
{
return new List<Customer>
{
new Customer{Id=1, Age= 10, Name = "Joey", Phone="1999"},
new Customer{Id=2, Age= 20, Name = "Kevin", Phone="2999"},
new Customer{Id=3, Age= 30, Name = "Demo", Phone="3999"},
};
}
}
public class MyCustomerEqualityComparer : EqualityComparer<Customer>
{
public override bool Equals(Customer x, Customer y)
{
return x.Id == y.Id
&& x.Name == y.Name;
}
public override int GetHashCode(Customer obj)
{
// 一樣,這邊只focus在 Equals, 沒有要給 Dictionary 拿來當 Key 使用
return 0;
}
}
IEnumerable<T>.SequenceEqual()
若沒傳入 IEqualityComparer<T> 參數,則預設也是使用 T 的 Equals() 方法來比較是否相等。因此這裡自訂一個 MyCustomerEqualityComparer 的 class 繼承自 EqualityComparer<Customer> ,自然也就實作了 IEqualityComparer<Customer> 。一樣覆寫 Equals()
,要注意一下這個 Equals 並不是 Object 的 Equals()
方法,只要簡單的定義當這兩個 Customer 物件的 Id 與 Name 值相等,就代表相等。
在原本使用 CollectionAssert.AreEqual()
的部分,就可以改用 Assert.IsTrue(expected.SequenceEqual(actual))
來比較了。相較於 CollectionAssert 的 AreEqual()
,我個人比較喜歡用 SequenceEqual()
來比較集合。
但每次要比較不同 property 的值,就需要自訂一個 EqualityComparer 的類別,感覺好麻煩,有沒有更簡單的方式?有,接下來要介紹兩個偷吃步的方式,當只是在測試程式中要驗證兩個集合中物件的某些值是否相等時,可以不需定義 class 的方式。
使用匿名型別來比較兩個 Customer 集合是否相等
是的,你沒看錯,用匿名型別來比。因為匿名型別的 Equals()
與 GetHashCode()
是依據匿名型別上每個 property 的 Equals()
與 GetHashCode()
來比較,只有完全相等才叫相等。(請參考:匿名類型 (C# 程式設計手冊))來看一下怎麼用匿名型別來改寫剛剛的測試程式,如下所示:
[TestClass]
public class UnitTest1
{
[TestMethod]
public void 驗證Cusotmer集合是否相等_SequenceEquals_使用匿名型別()
{
var expected = GetCustomers();
var actual = GetCustomers();
// 使用匿名型別取得放置 Id 與 Name 的容器,就可以針對 Id 與 Name 進行驗證是否相等
var expectedValues = expected.Select(x => new { Id = x.Id, Name = x.Name });
var actualValues = actual.Select(x => new { Id = x.Id, Name = x.Name });
//Assert.IsTrue(expected.SequenceEqual(actual, new MyCustomerEqualityComparer()));
Assert.IsTrue(expectedValues.SequenceEqual(actualValues));
}
[TestMethod]
public void 驗證Cusotmer集合是否相等_SequenceEquals_只有Id跟Name相等_使用匿名型別()
{
var expected = new List<Customer>
{
new Customer{Id=1, Age= 10, Name = "Joey", Phone="1999"},
new Customer{Id=2, Age= 20, Name = "Kevin", Phone="2999"},
new Customer{Id=3, Age= 30, Name = "Demo", Phone="3999"},
};
var actual = new List<Customer>
{
new Customer{Id=1, Age= 40, Name = "Joey", Phone="1000"},
new Customer{Id=2, Age= 50, Name = "Kevin", Phone="2000"},
new Customer{Id=3, Age= 60, Name = "Demo", Phone="3000"},
};
// 使用匿名型別取得放置 Id 與 Name 的容器,就可以針對 Id 與 Name 進行驗證是否相等
var expectedValues = expected.Select(x => new { Id = x.Id, Name = x.Name });
var actualValues = actual.Select(x => new { Id = x.Id, Name = x.Name });
//Assert.IsTrue(expected.SequenceEqual(actual, new MyCustomerEqualityComparer()));
Assert.IsTrue(expectedValues.SequenceEqual(actualValues));
}
private List<Customer> GetCustomers()
{
return new List<Customer>
{
new Customer{Id=1, Age= 10, Name = "Joey", Phone="1999"},
new Customer{Id=2, Age= 20, Name = "Kevin", Phone="2999"},
new Customer{Id=3, Age= 30, Name = "Demo", Phone="3999"},
};
}
}
透過匿名型別相等的定義,這樣測試程式寫起來是不是更加輕鬆愉快了呢?
這個方式仍有些討人厭的地方,就是那一段透過 Select()
來產生匿名型別的部分,要自己定義匿名型別的 property ,感覺起來也好囉唆,有沒有更偷吃步的方式?有的,要偷吃步到一個極限,我們還可以用 Tuple
來做。
對 Tuple 還不是很瞭解的讀者,可以參考:[C#]Tuple 簡介。
使用 Tuple 來比較兩個 Customer 集合是否相等
會想使用 Tuple 來簡化比較相等的動作,當然是因為 Tuple 的相等比較跟匿名型別一樣,會使用各個型別參數物件的 Equals()
來比較。但在測試程式中,用起來會比匿名型別更簡單一些,測試程式如下:
[TestMethod]
public void 驗證Cusotmer集合是否相等_SequenceEquals_使用Tuple()
{
var expected = GetCustomers();
var actual = GetCustomers();
//var expectedValues = expected.Select(x => new { Id = x.Id, Name = x.Name });
//var actualValues = actual.Select(x => new { Id = x.Id, Name = x.Name });
var expectedTuple = expected.Select(x => Tuple.Create(x.Id, x.Name));
var actualTuple = actual.Select(x => Tuple.Create(x.Id, x.Name));
//Assert.IsTrue(expectedValues.SequenceEqual(actualValues));
Assert.IsTrue(expectedTuple.SequenceEqual(actualTuple));
}
[TestMethod]
public void 驗證Cusotmer集合是否相等_SequenceEquals_只有Id跟Name相等_使用Tuple()
{
var expected = new List<Customer>
{
new Customer{Id=1, Age= 10, Name = "Joey", Phone="1999"},
new Customer{Id=2, Age= 20, Name = "Kevin", Phone="2999"},
new Customer{Id=3, Age= 30, Name = "Demo", Phone="3999"},
};
var actual = new List<Customer>
{
new Customer{Id=1, Age= 40, Name = "Joey", Phone="1000"},
new Customer{Id=2, Age= 50, Name = "Kevin", Phone="2000"},
new Customer{Id=3, Age= 60, Name = "Demo", Phone="3000"},
};
//var expectedValues = expected.Select(x => new { Id = x.Id, Name = x.Name });
//var actualValues = actual.Select(x => new { Id = x.Id, Name = x.Name });
var expectedTuple = expected.Select(x => Tuple.Create(x.Id, x.Name));
var actualTuple = actual.Select(x => Tuple.Create(x.Id, x.Name));
//Assert.IsTrue(expectedValues.SequenceEqual(actualValues));
Assert.IsTrue(expectedTuple.SequenceEqual(actualTuple));
}
不管是匿名型別的方式或是 Tuple 的方式,這邊的例子剛好是使用 Lambda 的方式來舉例,這不代表匿名型別或 Tuple 的比較方式,就不能重用。別忘了,這都只是 Select()
參數傳入的方式罷了。要重用相等的定義條件時,就自訂一個參數能滿足 Func<TSource, TResult>
來重用即可。這邊舉了兩個方式當例子,一個是 Func ,一個則是單純滿足 delegate 簽章的 private function ,如下所示:
[TestMethod]
public void 驗證Cusotmer集合是否相等_SequenceEquals_使用Tuple()
{
var expected = GetCustomers();
var actual = GetCustomers();
//var expectedValues = expected.Select(x => new { Id = x.Id, Name = x.Name });
//var actualValues = actual.Select(x => new { Id = x.Id, Name = x.Name });
var expectedTuple = expected.Select(x => Tuple.Create(x.Id, x.Name));
var actualTuple = actual.Select(x => Tuple.Create(x.Id, x.Name));
//Assert.IsTrue(expectedValues.SequenceEqual(actualValues));
Assert.IsTrue(expectedTuple.SequenceEqual(actualTuple));
}
[TestMethod]
public void 驗證Cusotmer集合是否相等_SequenceEquals_只有Id跟Name相等_使用Tuple()
{
var expected = new List<Customer>
{
new Customer{Id=1, Age= 10, Name = "Joey", Phone="1999"},
new Customer{Id=2, Age= 20, Name = "Kevin", Phone="2999"},
new Customer{Id=3, Age= 30, Name = "Demo", Phone="3999"},
};
var actual = new List<Customer>
{
new Customer{Id=1, Age= 40, Name = "Joey", Phone="1000"},
new Customer{Id=2, Age= 50, Name = "Kevin", Phone="2000"},
new Customer{Id=3, Age= 60, Name = "Demo", Phone="3000"},
};
//var expectedTuple = expected.Select(x => Tuple.Create(x.Id, x.Name));
//var actualTuple = actual.Select(x => Tuple.Create(x.Id, x.Name));
var expectedTuple = expected.Select(x => this.ComparerIdAndName(x));
var actualTuple = actual.Select(x => this.ComparerIdAndName(x));
Assert.IsTrue(expectedTuple.SequenceEqual(actualTuple));
var expectedTupleByFunc = expected.Select(funcComparerIdAndName);
var actualTupleByFunc = actual.Select(funcComparerIdAndName);
Assert.IsTrue(expectedTupleByFunc.SequenceEqual(actualTupleByFunc));
}
private Func<Customer, Tuple<int, string>> funcComparerIdAndName = customer => { return Tuple.Create(customer.Id, customer.Name); };
private Tuple<int, string> ComparerIdAndName(Customer customer)
{
return Tuple.Create(customer.Id, customer.Name);
}
對 Func<T> 不熟悉的讀者,請參考:[.NET]快快樂樂學LINQ系列前哨戰-Func, Action, Predicate ,對 Select() 不熟悉的讀者,請參考:[.NET]快快樂樂學LINQ系列-Select() 簡介
結論
歸納一下幾個重點:
AreSame()
指兩個變數必須是同一個物件,也就是相同,也就是指到同一塊記憶體。AreEqual()
則是會使用變數型別的Equals()
來進行比較相等,背後運作有可能是先使用 Object.Equals() 這個靜態方法來比較 expected 與 actual 。- 針對集合,可以使用 CollectionAssert.AreEqual() 搭配 IComparer ,好處是允許不同的相等定義,且無須覆寫原本自訂型別的
Equals()
。壞處是並不直接支援泛型,而且多個不同的相等定義,需要撰寫多組 IComparer 的類別。 - 針對集合,可以使用 LINQ 的 SequenceEqual() 搭配 IEqualityComparer 來比較兩個集合是否相等,有
CollectionAssert.AreEqual()
的好處,又可以享有泛型的支援。壞處是針對多個不同的相等定義,仍要撰寫多組 EqualityComparer 的類別。 - 使用匿名型別,搭配 LINQ 的
Select()
將 expected 與 actual 的集合 projection 成只需要比較特定值的匿名型別集合。好處是因為匿名型別的相等比較子會針對每一個 property 來比較是否相等,因此就不需要再額外撰寫多組相等比較子。壞處是匿名型別的 property 名稱還是要自己定義,但其實 property 名稱在測試中根本沒有意義。 - 使用 Tuple ,搭配 LINQ 的
Select()
將 expected 與 actual 的集合 projection 成只需要比較特定值的 Tuple 集合,好處是 Tuple 的相等比較子與匿名型別的方式一樣,而且連 property 名稱都可以省下來,因為 Tuple 的 property 名稱就是 Item1, Item2, ..., ItemN。壞處是當要比較的 property 超過 7 個以上時,就會長得比較複雜,需要用鏈狀的 Tuple 來串接。
雖然介紹了一些偷吃步的方式,但在實務測試上,這些都是可以簡化測試程式撰寫的眉角,希望對大家有所幫助,雖然用到了不少 C# 基礎知識。對匿名型別、Tuple、LINQ、Func<T> 跟 Lambda 還不熟的朋友,建議趁機把這些知識補起來,在實務設計上,一定會發揮很大的效用。
Reference
- Tuple<T1, T2>.Equals 方法
- 匿名類型 (C# 程式設計手冊)
- Assert.AreSame 方法
- Assert.AreEqual 方法 (Object, Object)
- IEqualityComparer<T> 介面
- IComparer<T> 介面
- IComparer 介面
- CollectionAssert.AreEqual 方法 (ICollection, ICollection, IComparer)
- Enumerable.SequenceEqual<TSource> 方法 (IEnumerable<TSource>, IEnumerable<TSource>)
- [C#]Tuple 簡介
- [.NET]快快樂樂學LINQ系列前哨戰-var與匿名型別
- [.NET]快快樂樂學LINQ系列前哨戰-Func, Action, Predicate
- [.NET]快快樂樂學LINQ系列-Select() 簡介
blog 與課程更新內容,請前往新站位置:http://tdd.best/