【发布时间】:2023-03-24 16:55:01
【问题描述】:
有没有办法在 NUnit 中进行有条件的 TearDown?
我有一个 TestFixture,它只需要为几个测试运行清理代码,我真的不想这样做:
- 对每个测试运行 TearDown 方法
- 如果可以避免的话,创建一个私有帮助方法并从需要清理的测试中调用它
【问题讨论】:
标签: unit-testing nunit
有没有办法在 NUnit 中进行有条件的 TearDown?
我有一个 TestFixture,它只需要为几个测试运行清理代码,我真的不想这样做:
【问题讨论】:
标签: unit-testing nunit
遗憾的是没有。
你不能在 [TestFixtureTearDown] 中进行清理,所以一旦所有测试都完成了吗?我想这取决于在下一次测试运行之前是否需要进行清理。
或者,将那些需要清理的测试放在另一个类/TextFixture 中,远离其他测试。然后你可以在那里使用不需要有条件的 TearDown。
编辑: 我刚刚想到的一件事,可以用来实现目标,尽管对于这个特殊的需求可能实际上并不值得,那就是你可以扩展 NUnit - 创建你自己的自定义属性,你可以随心所欲地处理它。这是提到here。就像我说的那样,我认为你真的不应该为此走这条路,但了解一点很有用
【讨论】:
您可以在基类中拥有主要的 TearDown:
[TearDown]
public virtual void TearDown()
{
// Tear down things here
}
然后在您拥有不应运行拆卸代码的测试的类中覆盖它:
[TearDown]
public override void TearDown()
{
// By not calling base.TearDown() here you avoid tearing down
}
【讨论】:
使用 BaseTest 中的测试扩展您的所有课程
public class BaseTest
{
[SetUp]
public void BeforeTest()
{
GetService<NUnitHooksController>().ExecuteBeforeTestHooks(this);
}
[TearDown]
public void AfterTest()
{
GetService<NUnitHooksController>().ExecuteAfterTestHooks(this);
}
}
使用 AfterTest 和 BeforeTest 挂钩。无论有无类别都适用。
public class ExampleTest : BaseTest
{
[Test, Category("asdasd")]
public void Test01()
{
...
}
[AfterTest("asdasd")]
public void ExampleHook()
{
...
}
}
public class NUnitHooksController
{
private readonly ILogger _log;
public NUnitHooksController(ILogger log)
{
_log = log;
}
public void ExecuteBeforeTestHooks(object testClass)
{
ExecuteHooks(testClass, typeof(BeforeTestAttribute));
}
public void ExecuteAfterTestHooks(object testClass)
{
ExecuteHooks(testClass, typeof(AfterTestAttribute));
}
private MethodInfo[] GetHookMethods(object currentTestClass, Type attributeType)
{
return currentTestClass
.GetType()
.GetMethods()
.Where(m => m.GetCustomAttributes(attributeType, false).Length > 0)
.ToArray();
}
private void ExecuteHooks(object testClass, Type requiredAttributeType)
{
var hooks = GetHookMethods(testClass, requiredAttributeType);
var testCategories = GetTestCategories();
foreach (var hook in hooks)
{
var allAttributes = hook.GetCustomAttributes(requiredAttributeType, true);
foreach (var attribute in allAttributes)
{
if (!attribute.GetType().IsEquivalentTo(requiredAttributeType))
{
continue;
}
var hookCategories = GetCategoriesFromAttribute(attribute);
// if we do not have specific category on hook
// or we have at least one same category on hook and test
if (!hookCategories.Any() || hookCategories.Intersect(testCategories).Any())
{
ExecuteHookMethod(testClass, hook);
}
}
}
}
private object[] GetTestCategories()
{
return TestContext.CurrentContext.Test.Properties["Category"].ToArray();
}
private void ExecuteHookMethod(object testClass, MethodInfo method)
{
var hookName = method.Name;
_log.Information($"Executing - '{hookName}' hook");
try
{
method.Invoke(testClass, Array.Empty<object>());
}
catch (Exception e)
{
_log.Error($"Executing of - '{hookName}' hook failed - {e}");
}
}
private string[] GetCategoriesFromAttribute(object attribute)
{
if (attribute is BeforeTestAttribute beforeTestAttribute)
{
return beforeTestAttribute.Categories;
}
if (attribute is AfterTestAttribute afterTestAttribute)
{
return afterTestAttribute.Categories;
}
throw new ArgumentException($"{attribute.GetType().FullName} - does not have categories");
}
}
【讨论】: