简易扩展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;
        }
    }
}
时间: 2024-12-19 06:50:06

简易扩展Visual Studio UnitTesting支持TestMethodCase的相关文章

如何扩展 Visual Studio 编辑器

在 Visual Studio 2010 的时代,扩展 Visual Studio 的途径有很多,开发者可以选择宏.Add-in.MEF 和 VSPackages 进行自定义的扩展.但是宏在 Visual Studio 2012 的时候被阉割了,Add-in 也在 Visual Studio 2013 里被抹杀了,这样的调整对于 Visual Studio 来说是好的,但是对于那些习惯了使用宏和Add-in的团队可能就郁闷了. 本文将一步步教你如何实现对 Visual Studio 代码编辑器的

VS2015提示:未安装Style的Visual Studio语言支持,代码编辑Intellisense将不可用。服务器控件的标记Intellisense可能不起作用

一.问题 最近在VS2015打开文件,提示未安装Style的Visual Studio语言支持,代码编辑Intellisense将不可用.服务器控件的标记Intellisense可能不起作用. Intellisense是智能感知,判断代码语法错误,不需要运行编译器再修正. 二.解决方法 vs2015中 工具--扩展和更新 搜索Microsoft ASP.NET and Web Tools 点击下载 安装.注意:安装时把VS2015关闭,不然安装会中止

简介Gulp, Grunt, Bower, 和 Npm 对Visual Studio的支持

[原文发表地址]Introducing Gulp, Grunt, Bower, and npm support for Visual Studio Web 开发,特别是前端 Web 开发,正迅速变得像传统的后端开发一样复杂和精密.大多数项目不仅仅是通过 FTP上传一些 JS 和 CSS 文件.而现在的前端生成过程,可以囊括SASS 和LESS扩展.CSS/JS的压缩包.JSHint 或 JSLint的运行时 .或者更多.这些生成任务和进程都和像Gulp和Grunt这样的工具一起协调工作.此外,类

Visual Studio Code 支持 iOS Web 应用调试

微软JavaScript Diagnostics项目经理Kenneth Auchenberg在一篇文章中写道,有一个新的Visual Studio Code扩展,允许开发人员直接在他们的Mac和Windows编辑器上调试在iOS设备上运行的JavaScript Web应用和网站. Auchenberg解释说,以前,调试iOS Web应用需要在Mac上运行Safari Web查看器,或者使用一个专用的跨浏览器兼容性检查器,如BrowserStack.新的Visual Studio Code扩展“i

.NET 开源了,Visual Studio 开始支持 Android 和 iOS 程序编写并自带 Android 模拟器【转载】

北京时间今天(2014年11月12日)凌晨的--.NET 开源.集成 Clang 和 LLVM 并且自带 Android 模拟器,这意味着 Visual Studio 这个当下最好没有之一的 IDE 正式支持编写 Android 和 iOS 程序. 微软今天宣布,在所有的主要平台上将对开发者开放 Visual Studio 和 .NET.从 Core .NET Server stack,新的免费且功能完整的 Visual Studio 版本,以及下一代 Visual Studio 和 .NET

Visual Studio 2015支持为Linux构建应用

点这里 微软著名的集成开发环境有可能是首次在其产品页提及了竞争对手Linux.Visual Studio 2015的页面声称,“Build for iOS, Android, Windows devices, Windows Server or Linux”,也就是说微软的IDE能生成Linux二进制程序.这并不令人感到太意外,因为过去的一年微软已经向Linux和开源社区伸出了橄榄枝,正逐步开源它的.NET框架和加入跨平台支持.未来有一天微软可能会将它的闭源Visual Studio带到Linu

.NET开源了,Visual Studio开始支持 Android 和 iOS 编程并自带Android模拟器

北京时间今天凌晨的大会上,多少程序员的假想成为现实..NET 开源,集成 Clang 和 LLVM 并且自带 Android 模拟器,这意味着 Visual Studio 这个当下最好没有之一的 IDE 正式支持编写 Android 和 iOS 程序. 这个开始前多次通过邮件向核心用户预告的会议果然没让人失望:Visual Studio 和 .NET 真正开始走向跨平台化.Nadella 说的“移动为先,云为先”和“找到微软最初的本质”终于连成一线,这家提供开发者工具 / 平台起家的公司在继用户

【转】让Visual Studio 2015 支持ASP.NET MVC4.0.0.1

近日装上了Visual Studio 2015 ,打开之前vs2013创建的MVC4的项目发现无法编译通过,提示System.Web.MVC,System.Web.WebPages 等找不到,网上搜索无果,遂想是否可以通过独立安装包实现. 从下边这个链接下载了 用于 Visual Studio 2010 SP1 和 Visual Web Developer 2010 SP1 的 ASP.NET MVC 4 安装后果然有几个地方没问题了,但唯独System.Web.MVC这个引用还有个黄色叹号,不

让Visual Studio 2015 支持ASP.NET MVC4.0.0.1

近日装上了Visual Studio 2015 ,打开之前vs2013创建的MVC4的项目发现无法编译通过,提示System.Web.MVC,System.Web.WebPages 等找不到,网上搜索无果,遂想是否可以通过独立安装包实现. 从下边这个链接下载了 用于 Visual Studio 2010 SP1 和 Visual Web Developer 2010 SP1 的 ASP.NET MVC 4 安装后果然有几个地方没问题了,但唯独System.Web.MVC这个引用还有个黄色叹号,不