1. 閱讀文本前但願您具有以下知識:瞭解單元測試,瞭解Dynamic,熟悉泛型(協變與逆變)和Lambda,熟悉.NET Framework提供的
Action與Func委託。
2.若是您對單元測試無興趣請止步。html
3.本文將使用一些我本身的測試公共代碼,位於 https://idletest.codeplex.com/,此處亦很是歡迎來訪。數組
4.關於本人以前單元測試的文章可參閱閉包
《在Visual Studio 2012使用單元測試》、ide
《VS2012 單元測試之泛型類(Generics Unit Test)》、post
《VS2012 Unit Test —— 我對接口進行單元測試使用的技巧》單元測試
爲了適應本次單元測試的編碼,對IdleTest進行了一些更新,本文只描述更新部分,具體源碼請移步https://idletest.codeplex.com/。測試
1.重構TestCommon類的ArrayEqual方法,變成了編碼
#region Equal /// <summary> /// 判斷兩個數組項相等(順序必須一致),對數組項使用"Equal方法"校驗, /// 若是非CTS類型(即自定義),則應在使用本方法前對Equal方法進行重載 /// </summary> public static bool ArrayEqual(Array array1, Array array2) { bool isCountEqual = CollectionCountEqual(array1, array2); if (!isCountEqual || array1 == null || array2 == null) { return isCountEqual; } for (int i = 0; i < array1.Length; i++) { if (!object.Equals(array1.GetValue(i), array2.GetValue(i))) { return false; } } return true; } /// <summary> /// 判斷兩個集合項相等(順序必須一致),對集合項使用"Equal方法"校驗, /// 若是非CTS類型(即自定義),則應在使用本方法前對Equal方法進行重載 /// </summary> public static bool ListEqual(IList list1, IList list2) { bool isCountEqual = CollectionCountEqual(list1, list1); if (!isCountEqual || list1 == null || list2 == null) { return isCountEqual; } for (int i = 0; i < list1.Count; i++) { if (!object.Equals(list1[i], list2[i])) { return false; } } return true; } /// <summary> /// 判斷兩個集合項相等(順序必須一致),對集合項使用"Equal方法"校驗, /// 若是非CTS類型(即自定義),則應在使用本方法前對Equal方法進行重載 /// </summary> public static bool CollectionEqual(object collection1, object collection2) { if (collection1 == null && collection2 == null) { return true; } if (collection1 is Array && collection2 is Array) { return ArrayEqual(collection1 as Array, collection2 as Array); } if (collection1 is IList && collection2 is IList) { return ListEqual(collection1 as IList, collection2 as IList); } return false; } /// <summary> /// 驗證兩個集合的長度是否一致 /// </summary> /// <param name="collection1">要判斷的集合1</param> /// <param name="collection2">要判斷的集合2</param> /// <returns>長度相等(兩個集合爲null或者長度爲0以及一個爲null另外一個長度爲0均認爲相等) /// 返回true,不然返回false</returns> public static bool CollectionCountEqual(ICollection collection1, ICollection collection2) { if ((collection1 == null || collection1.Count < 1) && (collection2 == null || collection2.Count < 1)) { return true; } else if ((collection1 == null || collection1.Count < 1) || (collection2 == null || collection2.Count < 1)) { return false; } return collection1.Count == collection2.Count; } #endregion
2. AssertCommon類新增方法以下url
/// <summary> /// 斷言爲Empty /// </summary> /// <typeparam name="TParameter1">方法參數類型1</typeparam> /// <typeparam name="TParameter2">方法參數類型2</typeparam> /// <typeparam name="TReturn">方法返回類型</typeparam> /// <param name="action">調用的方法</param> /// <param name="args1">需斷言的參數集1</param> /// <param name="args2">需斷言的參數集2</param> /// <param name="funcs">測試的方法集合</param> /// <param name="assertNull">是否斷言爲空</param> public static void AssertEmpty<TParameter1, TParameter2, TReturn>( TParameter1[] args1, TParameter2[] args2, bool assertEmpty = true, params Func<TParameter1, TParameter2, TReturn>[] funcs) { AssertHandle<TParameter1, TParameter2, TReturn>((TReturn actual) => { AssertEmpty<TReturn>(actual, assertEmpty); }, args1, args2, funcs); }
如標題所說,我如今有以下無返回值以及使用Action和Func做爲參數的幾個方法spa
public class UtilityCommon { #region Foreach Handle /// <summary> /// 進行遍歷 /// </summary> /// <typeparam name="T">類型</typeparam> /// <param name="array">遍歷的集合</param> /// <param name="action">遍歷到每一項執行的方法</param> /// <param name="breakBeforeFunc">跳出循環的方法(action執行前)</param> /// <param name="breakAfterFunc">跳出循環的方法(action執行後)</param> public static void ForeachHandle<T>(IEnumerable<T> array, Action<T> action, Func<T, bool> breakBeforeFunc, Func<T, bool> breakAfterFunc) { if (array == null || action == null) { return; } foreach (T item in array) { if (breakBeforeFunc != null && breakBeforeFunc(item)) { break; } action(item); if (breakAfterFunc != null && breakAfterFunc(item)) { break; } } } /// <summary> /// 進行遍歷 /// </summary> /// <typeparam name="T">類型</typeparam> /// <param name="array">遍歷的集合</param> /// <param name="action">遍歷到每一項執行的方法</param> public static void ForeachHandle<T>(IEnumerable<T> array, Action<T> action) { ForeachHandle<T>(array, action, null, null); } /// <summary> /// 進行遍歷,若是迭代器中的項不爲T類型,則跳過不執行操做(action) /// </summary> /// <typeparam name="T">類型</typeparam> /// <param name="array">遍歷的集合</param> /// <param name="action">遍歷到每一項執行的方法</param> /// <param name="breakBeforeFunc">跳出循環的方法(action執行前)</param> /// <param name="breakAfterFunc">跳出循環的方法(action執行後)</param> public static void ForeachHandle<T>(IEnumerable array, Action<T> action, Func<T, bool> breakBeforeFunc, Func<T, bool> breakAfterFunc) { if (array == null || action == null) { return; } foreach (var item in array) { if (item is T) { T t = (T)item; if (breakBeforeFunc != null && breakBeforeFunc(t)) { break; } action(t); if (breakAfterFunc != null && breakAfterFunc(t)) { break; } } } } /// <summary> /// 進行遍歷,若是迭代器中的項不爲T類型,則不執行操做(action) /// </summary> /// <typeparam name="T">類型</typeparam> /// <param name="array">遍歷的集合</param> /// <param name="action">遍歷到每一項執行的方法</param> public static void ForeachHandle<T>(IEnumerable array, Action<T> action) { ForeachHandle<T>(array, action, null, null); } #endregion }
這正體現着單元測試中三個難點:
(1)void返回類型。因爲沒有返回值,因此只能模擬方法內部操做的需求進行測試。做爲無返回值的方法,其確定改變了外部的一些信息,不然該方法基本上沒有意義,這就使得其具備可測試性。好比下面的代碼,將方法對外界的影響轉變成了對「arrayActual」這個變量的影響,使用該方式須要注意閉包產生影響。
(2)以委託對象做爲參數,因爲調用的委託未知,故而對其作單元測試很難徹底作到客觀上絕對的100%覆蓋率,儘管用VS運行覆蓋率分析時達到了100%。這個你們能夠看個人代碼找到未覆蓋的模塊,很容易看出來的,呵呵,不是我預留,而是我聽從VS的覆蓋分析結果就懶得去改罷了。
(3)方法有重載時,消除重複測試代碼。個人作法通常是把全部的重載測試代碼的數據構造提煉成一個方法,而後在各測試中以實際調用的方法做爲參數傳入,能作到這步田地真的很是感謝dynamic。
具體關於我對以上三點的作法,請看以下測試代碼
[TestClass()] public class UtilityCommonTest { /// <summary> ///ForeachHandle 的測試 ///</summary> public void ForeachHandleTestHelper(dynamic actionTest, bool hasBreakBefore = false, bool hasBreakAfter = false) { bool notBreak = !hasBreakAfter && !hasBreakBefore; IEnumerable<int> array = new int[] { 1, 2, 3, 4, 5 }; List<int> arrayActual = new List<int>(); Action<int> action = p => arrayActual.Add(p); Func<int, bool> breakBeforeFunc = null; // TODO: 初始化爲適當的值 Func<int, bool> breakAfterFunc = null; // TODO: 初始化爲適當的值 //UtilityCommon.ForeachHandle<int>(array, action, breakBeforeFunc, breakAfterFunc); if (notBreak) { actionTest(array, action); } else { actionTest(array, action, breakBeforeFunc, breakAfterFunc); } AssertCommon.AssertEqual(array, arrayActual); //************************************************************************************* arrayActual = new List<int>(); AssertCommon.AssertEmpty<IEnumerable<int>, List<int>, List<int>>( new IEnumerable<int>[] { null, new int[] { }, new List<int>() }, new List<int>[] { arrayActual }, true, (pIn1, pIn2) => { //UtilityCommon.ForeachHandle<int>(pIn1, p => pIn2.Add(p)); if (notBreak) actionTest(pIn1, new Action<int>(p => pIn2.Add(p))); else actionTest(pIn1, new Action<int>(p => pIn2.Add(p)), breakBeforeFunc, breakAfterFunc); return pIn2; }); if (notBreak) return; //************************************************************************************* // If Has Break Function breakBeforeFunc = p => p > 4; breakAfterFunc = p => p > 3; arrayActual = new List<int>(); actionTest(array, action, breakBeforeFunc, null); AssertCommon.AssertEqual<IList>(new int[] { 1, 2, 3, 4 }, arrayActual); arrayActual = new List<int>(); actionTest(array, action, null, breakAfterFunc); AssertCommon.AssertEqual<IList>(new int[] { 1, 2, 3, 4 }, arrayActual); arrayActual = new List<int>(); breakBeforeFunc = p => p > 3; actionTest(array, action, breakBeforeFunc, breakAfterFunc); AssertCommon.AssertEqual<IList>(new int[] { 1, 2, 3 }, arrayActual); arrayActual = new List<int>(); breakAfterFunc = p => p > 1; actionTest(array, action, breakBeforeFunc, breakAfterFunc); AssertCommon.AssertEqual<IList>(new int[] { 1 }, arrayActual); } [TestMethod()] public void ForeachHandleTest() { ForeachHandleTestHelper(new Action<IEnumerable, Action<int>>(UtilityCommon.ForeachHandle<int>)); } [TestMethod()] public void ForeachHandleGenericTest() { ForeachHandleTestHelper(new Action<IEnumerable<int>, Action<int>>(UtilityCommon.ForeachHandle<int>)); } [TestMethod()] public void ForeachHandleHasBreakTest() { ForeachHandleTestHelper(new Action<IEnumerable, Action<int>, Func<int, bool>, Func<int, bool>>( UtilityCommon.ForeachHandle<int>), true, true); } [TestMethod()] public void ForeachHandleGenericHasBreakTest() { ForeachHandleTestHelper(new Action<IEnumerable<int>, Action<int>, Func<int, bool>, Func<int, bool>>( UtilityCommon.ForeachHandle<int>), true, true); } }
運行經過測試後執行代碼覆蓋率分析,結果爲100%,如圖中選中行所示
本文代碼在IdleTest的位置如圖中選中的文件所示
能力有限文中難免有漏,還望各位前輩同仁後浪海涵並及時指正,不盡感激。我將會堅持寫關於單元測試的文章以及更新https://idletest.codeplex.com/,願與您攜手同進步。