简易扩展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

posted on   jack_Meng  阅读(66)  评论(0编辑  收藏  举报

编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
历史上的今天:
2020-10-12 在bat脚本if中多条件判断
2020-10-12 如何在批处理中使用FOR /F输出空行
2020-10-12 bat文件修改或替换文件内容
2020-10-12 如何查看并确定已安装的 .NET Framework 版本
2019-10-12 C#中的事件的订阅与发布
2019-10-12 c# Winform 开发分屏显示应用程序

导航

< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8
点击右上角即可分享
微信分享提示

喜欢请打赏

扫描二维码打赏

支付宝打赏

主题色彩