我的单元测试 class 可以跟踪测试执行吗
Can my unit test class keep track of test execution
我们的代码在 List<Rule>
集合中定义了一些“规则”。每个规则都包含一些字符串形式的逻辑。规则连同一些数据一起传递给“规则引擎”。规则引擎依次根据规则评估数据,直到找到评估为真的规则,然后 returns Rule
.
我们想要自动测试每个规则。这些测试实际上是集成测试,而不是单元测试,因为它们将测试规则引擎和规则的组合。
如何编写一个测试“确保每个规则在至少一个单元测试中评估为真”?
我想出了一种方法来 运行 在所有测试都具有 运行(参见 https://xunit.github.io/docs/shared-context.html#class-fixture)之后,一些夹具拆卸代码,并通过使用静态变量来记录评估规则我可以在拆解期间检查是否所有规则都已在单元测试期间返回。但是这种方法有不良影响,它会导致单个测试报告为失败(在拆卸中),但实际上并没有失败。
TLDR:控制测试顺序。由于这些是集成测试,所以这不是最大的禁忌——如果它们是单元测试,那将是最大的禁忌。
我发现通过使用静态变量来记录评估为真的规则,并确保最后 have-all-rules-evaluated-as-true 测试,因为根据这里的建议 https://hamidmosalla.com/2018/08/16/xunit-control-the-test-execution-order/,实现我需要的东西变得轻而易举。
万一 link 死了,这里是实现这个方法的方法
首先,创建一个 AlphabeticalOrderer
实现 xUnit 的 ITestCaseOrderer
:
public class AlphabeticalOrderer : ITestCaseOrderer
{
public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases)
where TTestCase : ITestCase
{
var result = testCases.ToList();
result.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name));
return result;
}
}
然后在你的测试中使用新创建的属性class,并创建一个静态变量来记录进度:
[TestCaseOrderer("MyCompany.AlphabeticalOrderer", "MyTestsDLL")]
public class RulesTests
{
private static List<Rule> _untestedRules;
public RulesTests()
{
if (_untestedRules == null) // this will run once per [Fact]
{
_untestedRules = <some way of getting all the rules> // this will only run first time around
}
}
每次触发规则时,记录是哪条规则:
private void MarkRuleAsTested(LendingRule testedRule)
{
var rulesToRemove = _untestedRules.Where(r => r.Logic == testedRule.Logic).ToList();
foreach (var ruleToRemove in rulesToRemove)
{
_untestedRules.Remove(ruleToRemove);
}
}
然后到运行的最后一个测试应该检查集合:
[Fact]
public void ZZ_check_all_rules_have_been_triggered_by_other_tests()
{
// This test must run last because it validates that the
// OTHER tests taken together have resulted in each rule
// being evaluated as true at least once.
if (!_untestedRules.Any())
{
return;
}
var separator = "\r\n------------------------\r\n";
var untestedRules = string.Join(separator, _untestedRules.Select(ur => $"Logic: {ur.Logic}"));
throw new SomeRulesUntestedException($"The following rules have not been tested to resolve as True: {separator}{untestedRules}{separator}");
}
这会导致可读性很好的测试失败。
您同样可以从 _testedRules
的空静态集合开始,最后将该集合与完整的规则集进行比较。
我们的代码在 List<Rule>
集合中定义了一些“规则”。每个规则都包含一些字符串形式的逻辑。规则连同一些数据一起传递给“规则引擎”。规则引擎依次根据规则评估数据,直到找到评估为真的规则,然后 returns Rule
.
我们想要自动测试每个规则。这些测试实际上是集成测试,而不是单元测试,因为它们将测试规则引擎和规则的组合。
如何编写一个测试“确保每个规则在至少一个单元测试中评估为真”?
我想出了一种方法来 运行 在所有测试都具有 运行(参见 https://xunit.github.io/docs/shared-context.html#class-fixture)之后,一些夹具拆卸代码,并通过使用静态变量来记录评估规则我可以在拆解期间检查是否所有规则都已在单元测试期间返回。但是这种方法有不良影响,它会导致单个测试报告为失败(在拆卸中),但实际上并没有失败。
TLDR:控制测试顺序。由于这些是集成测试,所以这不是最大的禁忌——如果它们是单元测试,那将是最大的禁忌。
我发现通过使用静态变量来记录评估为真的规则,并确保最后 have-all-rules-evaluated-as-true 测试,因为根据这里的建议 https://hamidmosalla.com/2018/08/16/xunit-control-the-test-execution-order/,实现我需要的东西变得轻而易举。
万一 link 死了,这里是实现这个方法的方法
首先,创建一个 AlphabeticalOrderer
实现 xUnit 的 ITestCaseOrderer
:
public class AlphabeticalOrderer : ITestCaseOrderer
{
public IEnumerable<TTestCase> OrderTestCases<TTestCase>(IEnumerable<TTestCase> testCases)
where TTestCase : ITestCase
{
var result = testCases.ToList();
result.Sort((x, y) => StringComparer.OrdinalIgnoreCase.Compare(x.TestMethod.Method.Name, y.TestMethod.Method.Name));
return result;
}
}
然后在你的测试中使用新创建的属性class,并创建一个静态变量来记录进度:
[TestCaseOrderer("MyCompany.AlphabeticalOrderer", "MyTestsDLL")]
public class RulesTests
{
private static List<Rule> _untestedRules;
public RulesTests()
{
if (_untestedRules == null) // this will run once per [Fact]
{
_untestedRules = <some way of getting all the rules> // this will only run first time around
}
}
每次触发规则时,记录是哪条规则:
private void MarkRuleAsTested(LendingRule testedRule)
{
var rulesToRemove = _untestedRules.Where(r => r.Logic == testedRule.Logic).ToList();
foreach (var ruleToRemove in rulesToRemove)
{
_untestedRules.Remove(ruleToRemove);
}
}
然后到运行的最后一个测试应该检查集合:
[Fact]
public void ZZ_check_all_rules_have_been_triggered_by_other_tests()
{
// This test must run last because it validates that the
// OTHER tests taken together have resulted in each rule
// being evaluated as true at least once.
if (!_untestedRules.Any())
{
return;
}
var separator = "\r\n------------------------\r\n";
var untestedRules = string.Join(separator, _untestedRules.Select(ur => $"Logic: {ur.Logic}"));
throw new SomeRulesUntestedException($"The following rules have not been tested to resolve as True: {separator}{untestedRules}{separator}");
}
这会导致可读性很好的测试失败。
您同样可以从 _testedRules
的空静态集合开始,最后将该集合与完整的规则集进行比较。