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 < )
throw new ArgumentOutOfRangeException("n"); return name.Length / n;
}
}
[TestClass]
public class MyClassTest
{
[TestMethod]
public void DoWork()
{
var name = "test";
var n = ; 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 = ; var myClass = new MyClass();
myClass.DoWork(null, n);
} [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void DoWork_NameIsEmpty()
{
var n = ; var myClass = new MyClass();
myClass.DoWork(String.Empty, n);
} [TestMethod, ExpectedException(typeof(ArgumentOutOfRangeException))]
public void DoWork_NameIsWhiteSpace()
{
var n = ; 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, -);
}
}

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

[TestFixture]
public class MyClassTest
{
[TestCase("Test", )]
[TestCase(null, , ExpectedException = typeof(ArgumentOutOfRangeException))]
[TestCase("", , ExpectedException = typeof(ArgumentOutOfRangeException))]
[TestCase(" ", , ExpectedException = typeof(ArgumentOutOfRangeException))]
[TestCase("Test", -, 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", )]
[TestMethodCase(null, , ExpectedException = typeof(ArgumentOutOfRangeException))]
[TestMethodCase("", , ExpectedException = typeof(ArgumentOutOfRangeException))]
[TestMethodCase(" ", , ExpectedException = typeof(ArgumentOutOfRangeException))]
[TestMethodCase("Test", -, ExpectedException = typeof(ArgumentOutOfRangeException))]
public void DoWork()
{
TestMethodCaseHelper.Run(context =>
{
var name = context.GetArgument<String>();
var n = context.GetArgument<Int32>(); 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[];
} 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 = ; while (true)
{
var stackFrame = new StackFrame(frameIndex, false); if (stackFrame.GetILOffset() == StackFrame.OFFSET_UNKNOWN)
break; yield return stackFrame; ++frameIndex;
}
}
}

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

  1. 如何扩展 Visual Studio 编辑器

    在 Visual Studio 2010 的时代,扩展 Visual Studio 的途径有很多,开发者可以选择宏.Add-in.MEF 和 VSPackages 进行自定义的扩展.但是宏在 Visu ...

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

    一.问题 最近在VS2015打开文件,提示未安装Style的Visual Studio语言支持,代码编辑Intellisense将不可用.服务器控件的标记Intellisense可能不起作用. Int ...

  3. 让Visual Studio x64 支持 __asm内联汇编

    目录 让Visual Studio x64 支持 __asm内联汇编 Intel Parallel Studio XE 2016安装 设置Interl C++ Compiler 使VS x64支持内联 ...

  4. 分享:扩展Visual Studio 的简单方法

    作为 MS 阵营的码农,相信Visual Studio 肯定是大家的主要武器了,但不知道大家有没有扩展Visual Studio 的需求. 最近我需要做一个工具,发现最好是实现在VS里面,于是,Goo ...

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

    .NET 开源了,Visual Studio 开始支持 Android 和 iOS 程序编写并自带 Android 模拟器 北京时间今天凌晨的 Connect(); 大会上,多少程序员的假想成为现实. ...

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

    [原文发表地址]Introducing Gulp, Grunt, Bower, and npm support for Visual Studio Web 开发,特别是前端 Web 开发,正迅速变得像 ...

  7. [转]简介Gulp, Grunt, Bower, 和 Npm 对Visual Studio的支持

    本文转自:http://www.cnblogs.com/whitewolf/p/4009199.html [原文发表地址]Introducing Gulp, Grunt, Bower, and npm ...

  8. Visual Studio 2013支持Xamarin的解决方案

    转自博客园[遗忘的代码] Xamarin的Visual Studio插件目前还不支持VS 2013,所以需要在安装Xamarin的VS插件时把2010和2012全选上 (由于我的电脑里只剩2013,而 ...

  9. Visual Studio 2013 支持MVC3不完善,Razor智能提示不完整或者不提示

    以下只是针对MVC3. 前天试用Orchard 1.8,用VS2013新建C#类库项目(ClassLibrary project),然后新建Views文件夹,新建cshtml,然后引用MVC3的相关d ...

随机推荐

  1. shell脚本重启tomcat

    1. 新建shell空脚本文件,如 /home/tr.sh,并设置权限 # chmod 750 /home/tr.sh 2. 设置文件形式: # sed -e 's/\^M//g' /home/tr. ...

  2. CCSprite: fade 效果切换图片

    //CCSprite+Animation.h #import "CCSprite.h" @interface CCSprite (Animation) + (void)fadeWi ...

  3. [转]python的requests发送/上传多个文件

    1.需要的环境 Python2.X Requests 库 2.单字段发送单个文件 在requests中发送文件的接口只有一种,那就是使用requests.post的files参数, 请求形式如下:   ...

  4. Robot Framework分层、开发系统关键字

            开发系统关键字:http://www.cnblogs.com/fnng/p/4261293.html http://www.cnblogs.com/fnng/p/3969978.htm ...

  5. 6 Django系列之关于models的sql语句日常用法总结

    preface Django提供了强大的ORM,我们可以通过ORM快速的写出我们想要对数据做什么样操作的代码.下面就说说我在日常工作中的用法: 外键关联精确查询 应用场景:表A host字段关联到了表 ...

  6. VC6.0在win 8.1中的安装使用

    http://blog.csdn.net/liups/article/details/14646663 一.首先是win8.1的安装 本人选择的是win 8.1简体中文专业N版,文件名: cn_win ...

  7. “NHibernate.Cfg.Configuration 的类型初始值设定项引发异常。”的解决方法【备忘】

    今天搞到NHibernate时,突然报了一个“NHibernate.Cfg.Configuration 的类型初始值设定项引发异常.”的异常. 详细异常信息“System.IO.FileLoadExc ...

  8. golang-defer坑的本质

    https://blog.csdn.net/hittata/article/details/77836435

  9. 详解js中的apply与call的用法

    前言 call 和 apply 都是为了改变某个函数运行时的 context 即上下文而存在的,换句话说,就是为了改变函数体内部 this 的指向.call 和 apply二者的作用完全一样,只是接受 ...

  10. Java Jdk1.8 HashMap源代码阅读笔记二

    三.源代码阅读 3.元素包括containsKey(Object key) /** * Returns <tt>true</tt> if this map contains a ...