VS2012 Unit Test(Void, Action, Func) —— 對無返回值、使用Action或Func作為參數、多重載的方法進行單元測試


【提示】

1. 閱讀文本前希望您具備如下知識:了解單元測試,了解Dynamic,熟悉泛型(協變與逆變)和Lambda,熟悉.NET Framework提供的
Action與Func委托。
2.如果您對單元測試無興趣請止步。

3.本文將使用一些我自己的測試公共代碼,位於 https://idletest.codeplex.com/,此處亦非常歡迎來訪。

4.關於本人之前單元測試的文章可參閱

在Visual Studio 2012使用單元測試》、

VS2012 單元測試之泛型類(Generics Unit Test)》、

VS2012 Unit Test —— 我對接口進行單元測試使用的技巧

 【修改IdleTest】

 為了適應本次單元測試的編碼,對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類新增方法如下

        /// <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作為參數的幾個方法

    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/,願與您攜手同進步。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM