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;
}
}
}

最新文章

  1. mysql 数据类型拾遗
  2. 用读写锁三句代码解决多线程并发写入文件 z
  3. C++ 纯虚函数接口,标准 C 导出 DLL 函数的用法
  4. oracle实现自增列
  5. WPF下的一个Socket
  6. Mesos编译步骤及部署注意事项(Ubuntu)
  7. UNIX网络编程---传输层:TCP、UDP、SCTP(二)
  8. WSGIweb框架--1
  9. [知了堂学习笔记]_纯JS制作《飞机大战》游戏_第1讲(实现思路与游戏界面的实现)
  10. python中csv文件的读取问题
  11. 用Inferno代替React开发高性能响应式WEB应用
  12. mongo中用嵌套结构优势是什么
  13. centos 网卡自动连接
  14. Windows环境使用Nexus-3.x搭建Maven私服
  15. Django-website 程序案例系列-11 验证装饰器
  16. JAVA-大白话探索JVM-类加载过程(二)
  17. 【UML】NO.48.EBook.5.UML.1.008-【UML 大战需求分析】- 组件图(Component Diagram)
  18. chrome浏览器network面板出现:Provisional headers are shown 提示
  19. struts2中的文件上传和下载
  20. PAT 1132 Cut Integer[简单]

热门文章

  1. cocos2d - Changing the image of a CCSprite
  2. ScrollView子控件高度设置无效
  3. asp.net导出EXCEL的好方法!(好用,导出全部数据)
  4. Thinkphp5笔记七:设置错误页面①
  5. 【Objective-C】Http常用API、同步请求与异步请求[转]
  6. geoserver REST使用
  7. zookeeper入门系列讲解
  8. 运行jsp常犯的错误
  9. ubuntu alsa2
  10. 微信企业号OAuth2验证接口实例(使用SpringMVC)