zoukankan      html  css  js  c++  java
  • 简易扩展Visual Studio UnitTesting支持TestMethodCase

    NUnit的TestCaseAttribute可以简化大量的测试参数输入用例的编写,如果基于Visual Studio Unit Test Project开发则默认没有类似的功能,看一段对比代码:

    复制代码
    public class MyClass
    {
        public Int32 DoWork(String name, Int32 n)
        {
            if (String.IsNullOrWhiteSpace(name))
                throw new ArgumentOutOfRangeException("name");
    
            if (n < 0)
                throw new ArgumentOutOfRangeException("n");
    
            return name.Length / n;
        }
    }
    复制代码
    复制代码
    [TestClass]
    public class MyClassTest
    {
        [TestMethod]
        public void DoWork()
        {
            var name = "test";
            var n = 5;
    
            var myClass = new MyClass();
            var result = myClass.DoWork(name, n);
    
            Assert.IsTrue(result == name.Length / n);
        }
    
        [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void DoWork_NameIsNull()
        {
            var n = 5;
    
            var myClass = new MyClass();
            myClass.DoWork(null, n);
        }
    
        [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void DoWork_NameIsEmpty()
        {
            var n = 5;
    
            var myClass = new MyClass();
            myClass.DoWork(String.Empty, n);
        }
    
        [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void DoWork_NameIsWhiteSpace()
        {
            var n = 5;
    
            var myClass = new MyClass();
            myClass.DoWork(" ", n);
        }
    
        [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void DoWork_NLessThanZero()
        {
            var name = "test";
    
            var myClass = new MyClass();
            myClass.DoWork(name, -1);
        }
    }
    复制代码

    可以发现为了测试参数输入验证是否达到预期的效果,额外编写了4个测试用例。如果使用NUnit的TestCase可以简化如下:

    复制代码
    [TestFixture]
    public class MyClassTest
    {
        [TestCase("Test", 5)]
        [TestCase(null, 5, ExpectedException = typeof(ArgumentOutOfRangeException))]
        [TestCase("", 5, ExpectedException = typeof(ArgumentOutOfRangeException))]
        [TestCase(" ", 5, ExpectedException = typeof(ArgumentOutOfRangeException))]
        [TestCase("Test", -1, ExpectedException = typeof(ArgumentOutOfRangeException))]
        public void DoWork(String name, Int32 n)
        {
            var myClass = new MyClass();
            var result = myClass.DoWork(name, n);
    
            Assert.IsTrue(result == name.Length / n);
        }
    }
    复制代码

    要让Visual Studio Test支持类似的方式可以自己扩展,参考Visual Studio Team Test的Extending the Visual Studio Unit Test Type文章。不过我选择了更为简单的在原有的用例中扩展一个TestMethodCaseAttribute,例如:

    复制代码
    [TestClass]
    public class MyClassTest
    {
        [TestMethod]
        [TestMethodCase("Test", 5)]
        [TestMethodCase(null, 5, ExpectedException = typeof(ArgumentOutOfRangeException))]
        [TestMethodCase("", 5, ExpectedException = typeof(ArgumentOutOfRangeException))]
        [TestMethodCase(" ", 5, ExpectedException = typeof(ArgumentOutOfRangeException))]
        [TestMethodCase("Test", -1, ExpectedException = typeof(ArgumentOutOfRangeException))]
        public void DoWork()
        {
            TestMethodCaseHelper.Run(context => 
            {
                var name = context.GetArgument<String>(0);
                var n = context.GetArgument<Int32>(1);
    
                var myClass = new MyClass();
                var result = myClass.DoWork(name, n);
    
                Assert.IsTrue(result == name.Length / n);
            });
        }
    }
    复制代码

    只要有一个TestMethodCase未通过,当前的TestMethod既为失败。使用这种方式进行Code Coverage统计并不受影响,可以正确评估

    复制代码
    public static class TestMethodCaseHelper
    {
        public static void Run(Action<TestMethodCaseContext> body)
        {
            var testMethodCases = FindTestMethodCaseByCallingContext();
    
            foreach (var testMethodCase in testMethodCases)
                RunTest(testMethodCase, body);
        }
    
        internal static IEnumerable<TestMethodCaseAttribute> FindTestMethodCaseByCallingContext()
        {
            var stackFrames = StackFrameHelper.GetCurrentCallStack();
            var forTestFrame = stackFrames.FirstOrDefault(p => GetTestMethodCaseAttributes(p).Any());
    
            return forTestFrame != null ? GetTestMethodCaseAttributes(forTestFrame) : new TestMethodCaseAttribute[0];
        }
    
        private static IEnumerable<TestMethodCaseAttribute> GetTestMethodCaseAttributes(StackFrame stackFrame)
        {
            return GetTestMethodCaseAttributes(stackFrame.GetMethod());
        }
    
        private static IEnumerable<TestMethodCaseAttribute> GetTestMethodCaseAttributes(MethodBase method)
        {
            return method.GetCustomAttributes(typeof(TestMethodCaseAttribute), true).OfType<TestMethodCaseAttribute>();
        }
    
        private static void RunTest(TestMethodCaseAttribute testMethodCase, Action<TestMethodCaseContext> body)
        {
            TestSettings.Output.WriteLine("Run TestMethodCase {0} started", testMethodCase.Name);
            var stopwatch = Stopwatch.StartNew();
            RunTestCore(testMethodCase, body);
            stopwatch.Stop();
            TestSettings.Output.WriteLine("Run TestMethodCase {0} finished({1})", testMethodCase.Name, stopwatch.ElapsedMilliseconds);
        }
    
        private static void RunTestCore(TestMethodCaseAttribute testMethodCase, Action<TestMethodCaseContext> body)
        {
            var testContext = new TestMethodCaseContext(testMethodCase);
    
            if (testMethodCase.ExpectedException != null)
                RunTestWithExpectedException(testMethodCase.ExpectedException, () => body(testContext));
            else
                body(testContext);
        }
    
        private static void RunTestWithExpectedException(Type expectedExceptionType, Action body)
        {
            try
            {
                body();
            }
            catch (Exception ex)
            {
                if (ex.GetType() == expectedExceptionType)
                    return;
    
                throw;
            }
        }
    }
    复制代码
    复制代码
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class TestMethodCaseAttribute : Attribute
    {
        public TestMethodCaseAttribute(params Object[] arguments)
        {
            this.Arguments = arguments;
        }
    
        public String Name { get; set; }
    
        public Type ExpectedException { get; set; }
    
        public Object[] Arguments { get; private set; }
    }
    复制代码
    复制代码
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class TestMethodCaseAttribute : Attribute
    {
        public TestMethodCaseAttribute(params Object[] arguments)
        {
            this.Arguments = arguments;
        }
    
        public String Name { get; set; }
    
        public Type ExpectedException { get; set; }
    
        public Object[] Arguments { get; private set; }
    }
    复制代码
    复制代码
    public class TestMethodCaseContext
    {
        private readonly TestMethodCaseAttribute _testMethodCase;
    
        internal TestMethodCaseContext(TestMethodCaseAttribute testMethodCase)
        {
            _testMethodCase = testMethodCase;
        }
    
        public T GetArgument<T>(Int32 index)
        {
            return (T)_testMethodCase.Arguments.ElementAtOrDefault(index);
        }
    }
    复制代码
    复制代码
    internal static class StackFrameHelper
    {
        public static IEnumerable<StackFrame> GetCurrentCallStack()
        {
            var frameIndex = 0;
    
            while (true)
            {
                var stackFrame = new StackFrame(frameIndex, false);
    
                if (stackFrame.GetILOffset() == StackFrame.OFFSET_UNKNOWN)
                    break;
    
                yield return stackFrame;
    
                ++frameIndex;
            }
        }
    }
    复制代码

    出处:https://www.cnblogs.com/junchu25/p/4271592.html

    您的资助是我最大的动力!
    金额随意,欢迎来赏!
    款后有任何问题请给我留言。

    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的推荐按钮。
    如果,您希望更容易地发现我的新博客,不妨点击一下绿色通道的关注我。(●'◡'●)

    如果你觉得本篇文章对你有所帮助,请给予我更多的鼓励,求打             付款后有任何问题请给我留言!!!

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,我是【Jack_孟】!

  • 相关阅读:
    Open-E DSS V7 应用系列之 9 主动/主动 iSCSI群集部署
    Open-E DSS V7 应用系列 7~8
    Open-E DSS V7 应用系列之4~6
    Open-E DSS V7 应用系列之1~3
    kbmmw 5.18.0 发布
    Spring笔记--@ConditionalOnBean坑
    Kafka 3.0新特性
    如何让Git记住你的GitHub Token,避免每次都要重复输入?
    特征值和特征向量到底是个啥?能做什么用?
    ICCV 2021 | BN-NAS: 只训练BN层来自动搜索模型
  • 原文地址:https://www.cnblogs.com/mq0036/p/15396278.html
Copyright © 2011-2022 走看看