• 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/,愿与您携手同进步。

  • 相关阅读:
    java.lang.IllegalStateException: You need to use a Theme.AppCompat theme (or.....
    cas单点登录防止登出退出后刷新后退ticket失效报500错
    IDEA远程调试服务器代码
    阿里的fastJson.jar jsonArray 和 list 互转
    获取多<a/>标签id值的点击事件
    redis常用命令
    SuperDiamond在JAVA项目中的三种应用方法实践总结
    Redis 集群环境的搭建
    eclipse中一些常见svn图标的含义
    【转】Windows下PATH等环境变量详解
  • 原文地址:https://www.cnblogs.com/FreeDong/p/3297034.html
Copyright © 2020-2023  润新知