是否可以在不修改每个测试 and/or 夹具的代码的情况下模拟 NUnit 测试的失败?
Is it possible to simulate a failure of an NUnit test without modifying the code of every test and/or fixture?
我希望能够模拟任意测试的失败,以检查我的 TearDown 逻辑是否正常工作。单元测试中的单元测试,如果你愿意的话。
但实际上,我在一些会在失败时生成附件的装置中安装了 TearDown。我必须能够炫耀这个功能,但在你最需要它的时候很难产生故障。
所以,我想创建一个测试参数,指定我希望失败的测试的名称。现在,我可以轻松编写实现 IWrapTestMethod
或 IApplyToContext
的属性,但随后我需要将其应用于每个测试方法。
有没有办法在不触及每个测试 and/or 装置的情况下实现它?通过某种装配级别属性或装配级别设置方法,在每次测试之前 运行?
这一逻辑不会阻止 运行ning 中的 TearDown
方法是至关重要的,因此 ITestAction
从 BeforeTest
中抛出异常不符合要求。
这可以做到吗?
我找到了解决方案:
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using System;
using System.Reflection;
[assembly: Common.EnableFailureSimulation]
namespace Common
{
public class SimulateFailureMethodInfoWrapper : IMethodInfo
{
private readonly IMethodInfo m_mi;
public SimulateFailureMethodInfoWrapper(IMethodInfo mi)
{
m_mi = mi;
}
public ITypeInfo TypeInfo => m_mi.TypeInfo;
public MethodInfo MethodInfo => m_mi.MethodInfo;
public string Name => m_mi.Name;
public bool IsAbstract => m_mi.IsAbstract;
public bool IsPublic => m_mi.IsPublic;
public bool ContainsGenericParameters => m_mi.ContainsGenericParameters;
public bool IsGenericMethod => m_mi.IsGenericMethod;
public bool IsGenericMethodDefinition => m_mi.IsGenericMethodDefinition;
public ITypeInfo ReturnType => m_mi.ReturnType;
public T[] GetCustomAttributes<T>(bool inherit) where T : class => m_mi.GetCustomAttributes<T>(inherit);
public Type[] GetGenericArguments() => m_mi.GetGenericArguments();
public IParameterInfo[] GetParameters() => m_mi.GetParameters();
public object Invoke(object fixture, params object[] args)
{
var res = m_mi.Invoke(fixture, args);
Assert.Fail("Failure simulation");
return res;
}
public bool IsDefined<T>(bool inherit) where T : class => m_mi.IsDefined<T>(inherit);
public IMethodInfo MakeGenericMethod(params Type[] typeArguments) => m_mi.MakeGenericMethod(typeArguments);
}
[AttributeUsage(AttributeTargets.Assembly)]
public class EnableFailureSimulationAttribute : Attribute, ITestAction
{
private static string s_failTestMethod = GetParameterByName("!");
public ActionTargets Targets => ActionTargets.Test;
public void AfterTest(ITest test)
{
}
public void BeforeTest(ITest test)
{
if (test.MethodName == s_failTestMethod && test is Test testImpl)
{
testImpl.Method = new SimulateFailureMethodInfoWrapper(testImpl.Method);
s_failTestMethod = "!";
}
}
}
}
另一种方法是使用 Moq
并模拟 IMethodInfo
接口,而不是使用真正的 SimulateFailureMethodInfoWrapper
class.
无论如何,这似乎很完美。
我希望能够模拟任意测试的失败,以检查我的 TearDown 逻辑是否正常工作。单元测试中的单元测试,如果你愿意的话。
但实际上,我在一些会在失败时生成附件的装置中安装了 TearDown。我必须能够炫耀这个功能,但在你最需要它的时候很难产生故障。
所以,我想创建一个测试参数,指定我希望失败的测试的名称。现在,我可以轻松编写实现 IWrapTestMethod
或 IApplyToContext
的属性,但随后我需要将其应用于每个测试方法。
有没有办法在不触及每个测试 and/or 装置的情况下实现它?通过某种装配级别属性或装配级别设置方法,在每次测试之前 运行?
这一逻辑不会阻止 运行ning 中的 TearDown
方法是至关重要的,因此 ITestAction
从 BeforeTest
中抛出异常不符合要求。
这可以做到吗?
我找到了解决方案:
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using System;
using System.Reflection;
[assembly: Common.EnableFailureSimulation]
namespace Common
{
public class SimulateFailureMethodInfoWrapper : IMethodInfo
{
private readonly IMethodInfo m_mi;
public SimulateFailureMethodInfoWrapper(IMethodInfo mi)
{
m_mi = mi;
}
public ITypeInfo TypeInfo => m_mi.TypeInfo;
public MethodInfo MethodInfo => m_mi.MethodInfo;
public string Name => m_mi.Name;
public bool IsAbstract => m_mi.IsAbstract;
public bool IsPublic => m_mi.IsPublic;
public bool ContainsGenericParameters => m_mi.ContainsGenericParameters;
public bool IsGenericMethod => m_mi.IsGenericMethod;
public bool IsGenericMethodDefinition => m_mi.IsGenericMethodDefinition;
public ITypeInfo ReturnType => m_mi.ReturnType;
public T[] GetCustomAttributes<T>(bool inherit) where T : class => m_mi.GetCustomAttributes<T>(inherit);
public Type[] GetGenericArguments() => m_mi.GetGenericArguments();
public IParameterInfo[] GetParameters() => m_mi.GetParameters();
public object Invoke(object fixture, params object[] args)
{
var res = m_mi.Invoke(fixture, args);
Assert.Fail("Failure simulation");
return res;
}
public bool IsDefined<T>(bool inherit) where T : class => m_mi.IsDefined<T>(inherit);
public IMethodInfo MakeGenericMethod(params Type[] typeArguments) => m_mi.MakeGenericMethod(typeArguments);
}
[AttributeUsage(AttributeTargets.Assembly)]
public class EnableFailureSimulationAttribute : Attribute, ITestAction
{
private static string s_failTestMethod = GetParameterByName("!");
public ActionTargets Targets => ActionTargets.Test;
public void AfterTest(ITest test)
{
}
public void BeforeTest(ITest test)
{
if (test.MethodName == s_failTestMethod && test is Test testImpl)
{
testImpl.Method = new SimulateFailureMethodInfoWrapper(testImpl.Method);
s_failTestMethod = "!";
}
}
}
}
另一种方法是使用 Moq
并模拟 IMethodInfo
接口,而不是使用真正的 SimulateFailureMethodInfoWrapper
class.
无论如何,这似乎很完美。