簡易拡張Visual Studio UnitTestingはTestMethodCaseをサポート
21497 ワード
NUnitのTestCaseAttributeは大量のテストパラメータ入力例の作成を簡略化することができ、Visual Studio Unit Test Projectに基づいて開発された場合、デフォルトでは類似の機能はありません.比較コードを見てください.
パラメータ入力検証が所望の効果を達成するかどうかをテストするために、4つのテスト例が追加的に記述されていることがわかります.NUnitのTestCaseを使用すると、以下のように簡略化できます.
Visual Studio Testが同様の方法で拡張できるようにするには、Visual Studio Team TestのExtending the Visual Studio Unit Test Typeを参照してください.しかし、従来の例ではTestMethodCaseAttributeをより簡単に拡張することを選択しました.例えば、
TestMethodCaseが1つ通過しない限り、現在のTestMethodは失敗します.この方式によるCode Coverage統計は影響を受けず正確に評価できる
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が1つ通過しない限り、現在の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;
}
}
}