如何使用 Xunit 2.0 动态跳过测试?
How to dynamically skip a test with Xunit 2.0?
Xunit 1.9.x 为用户提供 DynamicSkipExample.cs 示例来帮助他设置 [Fact]
的动态跳过。
事实证明,这在执行某些跨平台开发时非常有用。这允许在由于底层上下文(操作系统、文件系统等)而无法正确运行时暂时忽略测试。
但是,这个例子已经在 2.0 版本的提交 2deeff5 中删除了。
如何通过 Xunit 2.0 的扩展点之一重新实现这样的功能?
注意: xUnit 跟踪器中已提出有关此主题的问题。参见 xunit/xunit#250。
[更新:XUnit v2.0 (RTM) 现已可用,它直接支持可跳过的测试。
使用 [Fact (Skip = "specific reason")]
]
请注意,XUnit v2.0 尚未发布。此示例与 Xunit 2.0 beta5 兼容,您可以在 nuget.org 上找到它。可能还有其他方法可以做到这一点(因为这只是我来的例子)。
1) 定义一个属性来装饰您的测试。
/// <inheritdoc/>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = false )]
[XunitTestCaseDiscoverer( "SkippableTestCaseDiscoverer", "assemblynamehere" )]
public sealed class SkippableTestAttribute : FactAttribute
{
public SkippableTestAttribute() { }
}
2) 创建您的发现者。 (我们遵循 https://github.com/xunit/xunit/blob/2d9ce6fbd75e91a69a0cc83e1bc3d4eab18b2c6c/src/xunit.execution/Sdk/Frameworks/TheoryDiscoverer.cs 处的代码示例)
/// <summary>
/// Implementation of <see cref="IXunitTestCaseDiscoverer"/> that supports finding test cases
/// on methods decorated with <see cref="SkippableTestAttribute"/>.
/// </summary>
public class SkippableTestCaseDiscoverer : IXunitTestCaseDiscoverer
{
/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Needs to return test case." )]
public IEnumerable<IXunitTestCase> Discover( ITestMethod testMethod, IAttributeInfo factAttribute )
{
// some of this code is from https://github.com/xunit/xunit/blob/2d9ce6fbd75e91a69a0cc83e1bc3d4eab18b2c6c/src/xunit.execution/Sdk/Frameworks/TheoryDiscoverer.cs
if ( factAttribute.GetNamedArgument<string>( "Skip" ) != null )
return new[] { new XunitTestCase( testMethod ) };
var dataAttributes = testMethod.Method.GetCustomAttributes( typeof( DataAttribute ) );
try
{
var results = new List<XunitTestCase>();
foreach ( var dataAttribute in dataAttributes )
{
var discovererAttribute = dataAttribute.GetCustomAttributes( typeof( DataDiscovererAttribute ) ).First();
var discoverer = ExtensibilityPointFactory.GetDataDiscoverer( discovererAttribute );
if ( !discoverer.SupportsDiscoveryEnumeration( dataAttribute, testMethod.Method ) )
return new XunitTestCase[] { new XunitTheoryTestCase( testMethod ) };
// These lines are our "custom dynamic logic" that determines if we should skip the test.
IEnumerable<object[]> data = discoverer.GetData( dataAttribute, testMethod.Method );
if ( data == null )
{
var test = new SkippableTestCase( testMethod );
test.SkipTest( "Test not configured with any " );
return new[] { test };
}
foreach ( var dataRow in data )
{
// Attempt to serialize the test case, since we need a way to uniquely identify a test
// and serialization is the best way to do that. If it's not serializable, this will
// throw and we will fall back to a single theory test case that gets its data
// at runtime.
var testCase = new XunitTestCase( testMethod, dataRow );
SerializationHelper.Serialize( testCase );
results.Add( testCase );
}
}
if ( results.Count == 0 )
results.Add( new LambdaTestCase( testMethod,
() => { throw new InvalidOperationException( String.Format( "No data found for {0}.{1}", testMethod.TestClass.Class.Name, testMethod.Method.Name ) ); } ) );
return results;
}
catch
{
return new XunitTestCase[] { new XunitTheoryTestCase( testMethod ) };
}
}
}
3) 创建一个实现 IXunitTestCase 的 class(因为默认基础 class 不允许修改跳过原因)。
// Class is similar to XunitTestCase
[Serializable]
public class SkippableTestCase : TestMethodTestCase, IXunitTestCase
{
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer", error: true)]
public SkippableTestCase() { }
/// <summary>
/// Initializes a new instance of the <see cref="SkippableTestCase"/> class.
/// </summary>
/// <param name="testMethod">The test method this test case belongs to.</param>
/// <param name="testMethodArguments">The arguments for the test method.</param>
public SkippableTestCase(ITestMethod testMethod, object[] testMethodArguments = null)
: base(testMethod, testMethodArguments) { }
/// <inheritdoc />
protected SkippableTestCase(SerializationInfo info, StreamingContext context)
: base(info, context) { }
public void SkipTest( string reason )
{
base.SkipReason = reason;
}
/// <inheritdoc/>
public virtual Task<RunSummary> RunAsync( IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource )
{
return new XunitTestCaseRunner( this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource ).RunAsync();
}
}
您有多种设置 base.SkipReason 的选项。在此示例中,创建了一个 public 方法。
此示例将跳过具有 MemberDataAttribute
而 return 没有数据行的测试。您可以根据您的条件将其修改为 return 和 SkippableTestCase
。例如,此发现会跳过周日的测试。
/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Needs to return test case." )]
public IEnumerable<IXunitTestCase> Discover( ITestMethod testMethod, IAttributeInfo factAttribute )
{
if ( DateTime.Today.DayOfWeek == DayOfWeek.Sunday )
{
var test = new SkippableTestCase( testMethod );
test.SkipTest( "Test not configured with any " );
return new[] { test };
}
else
{
return new[] { new XunitTestCase( testMethod ) };
}
}
这最终已在问题 xunit/samples.xunit#8.
中得到解决
惊人的 @BradWilson made this available as a complete and detailed sample in the samples.xunit 存储库。
您也可以在 Nugget Manager 中搜索 SkippableFact,然后您可以使用 [SkippableFact] 的所有功能
Xunit 1.9.x 为用户提供 DynamicSkipExample.cs 示例来帮助他设置 [Fact]
的动态跳过。
事实证明,这在执行某些跨平台开发时非常有用。这允许在由于底层上下文(操作系统、文件系统等)而无法正确运行时暂时忽略测试。
但是,这个例子已经在 2.0 版本的提交 2deeff5 中删除了。
如何通过 Xunit 2.0 的扩展点之一重新实现这样的功能?
注意: xUnit 跟踪器中已提出有关此主题的问题。参见 xunit/xunit#250。
[更新:XUnit v2.0 (RTM) 现已可用,它直接支持可跳过的测试。
使用 [Fact (Skip = "specific reason")]
]
请注意,XUnit v2.0 尚未发布。此示例与 Xunit 2.0 beta5 兼容,您可以在 nuget.org 上找到它。可能还有其他方法可以做到这一点(因为这只是我来的例子)。
1) 定义一个属性来装饰您的测试。
/// <inheritdoc/>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = false )]
[XunitTestCaseDiscoverer( "SkippableTestCaseDiscoverer", "assemblynamehere" )]
public sealed class SkippableTestAttribute : FactAttribute
{
public SkippableTestAttribute() { }
}
2) 创建您的发现者。 (我们遵循 https://github.com/xunit/xunit/blob/2d9ce6fbd75e91a69a0cc83e1bc3d4eab18b2c6c/src/xunit.execution/Sdk/Frameworks/TheoryDiscoverer.cs 处的代码示例)
/// <summary>
/// Implementation of <see cref="IXunitTestCaseDiscoverer"/> that supports finding test cases
/// on methods decorated with <see cref="SkippableTestAttribute"/>.
/// </summary>
public class SkippableTestCaseDiscoverer : IXunitTestCaseDiscoverer
{
/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Needs to return test case." )]
public IEnumerable<IXunitTestCase> Discover( ITestMethod testMethod, IAttributeInfo factAttribute )
{
// some of this code is from https://github.com/xunit/xunit/blob/2d9ce6fbd75e91a69a0cc83e1bc3d4eab18b2c6c/src/xunit.execution/Sdk/Frameworks/TheoryDiscoverer.cs
if ( factAttribute.GetNamedArgument<string>( "Skip" ) != null )
return new[] { new XunitTestCase( testMethod ) };
var dataAttributes = testMethod.Method.GetCustomAttributes( typeof( DataAttribute ) );
try
{
var results = new List<XunitTestCase>();
foreach ( var dataAttribute in dataAttributes )
{
var discovererAttribute = dataAttribute.GetCustomAttributes( typeof( DataDiscovererAttribute ) ).First();
var discoverer = ExtensibilityPointFactory.GetDataDiscoverer( discovererAttribute );
if ( !discoverer.SupportsDiscoveryEnumeration( dataAttribute, testMethod.Method ) )
return new XunitTestCase[] { new XunitTheoryTestCase( testMethod ) };
// These lines are our "custom dynamic logic" that determines if we should skip the test.
IEnumerable<object[]> data = discoverer.GetData( dataAttribute, testMethod.Method );
if ( data == null )
{
var test = new SkippableTestCase( testMethod );
test.SkipTest( "Test not configured with any " );
return new[] { test };
}
foreach ( var dataRow in data )
{
// Attempt to serialize the test case, since we need a way to uniquely identify a test
// and serialization is the best way to do that. If it's not serializable, this will
// throw and we will fall back to a single theory test case that gets its data
// at runtime.
var testCase = new XunitTestCase( testMethod, dataRow );
SerializationHelper.Serialize( testCase );
results.Add( testCase );
}
}
if ( results.Count == 0 )
results.Add( new LambdaTestCase( testMethod,
() => { throw new InvalidOperationException( String.Format( "No data found for {0}.{1}", testMethod.TestClass.Class.Name, testMethod.Method.Name ) ); } ) );
return results;
}
catch
{
return new XunitTestCase[] { new XunitTheoryTestCase( testMethod ) };
}
}
}
3) 创建一个实现 IXunitTestCase 的 class(因为默认基础 class 不允许修改跳过原因)。
// Class is similar to XunitTestCase
[Serializable]
public class SkippableTestCase : TestMethodTestCase, IXunitTestCase
{
[EditorBrowsable(EditorBrowsableState.Never)]
[Obsolete("Called by the de-serializer", error: true)]
public SkippableTestCase() { }
/// <summary>
/// Initializes a new instance of the <see cref="SkippableTestCase"/> class.
/// </summary>
/// <param name="testMethod">The test method this test case belongs to.</param>
/// <param name="testMethodArguments">The arguments for the test method.</param>
public SkippableTestCase(ITestMethod testMethod, object[] testMethodArguments = null)
: base(testMethod, testMethodArguments) { }
/// <inheritdoc />
protected SkippableTestCase(SerializationInfo info, StreamingContext context)
: base(info, context) { }
public void SkipTest( string reason )
{
base.SkipReason = reason;
}
/// <inheritdoc/>
public virtual Task<RunSummary> RunAsync( IMessageBus messageBus, object[] constructorArguments, ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource )
{
return new XunitTestCaseRunner( this, DisplayName, SkipReason, constructorArguments, TestMethodArguments, messageBus, aggregator, cancellationTokenSource ).RunAsync();
}
}
您有多种设置 base.SkipReason 的选项。在此示例中,创建了一个 public 方法。
此示例将跳过具有 MemberDataAttribute
而 return 没有数据行的测试。您可以根据您的条件将其修改为 return 和 SkippableTestCase
。例如,此发现会跳过周日的测试。
/// <inheritdoc/>
[System.Diagnostics.CodeAnalysis.SuppressMessage( "Microsoft.Reliability", "CA2000:Dispose objects before losing scope", Justification = "Needs to return test case." )]
public IEnumerable<IXunitTestCase> Discover( ITestMethod testMethod, IAttributeInfo factAttribute )
{
if ( DateTime.Today.DayOfWeek == DayOfWeek.Sunday )
{
var test = new SkippableTestCase( testMethod );
test.SkipTest( "Test not configured with any " );
return new[] { test };
}
else
{
return new[] { new XunitTestCase( testMethod ) };
}
}
这最终已在问题 xunit/samples.xunit#8.
中得到解决惊人的 @BradWilson made this available as a complete and detailed sample in the samples.xunit 存储库。
您也可以在 Nugget Manager 中搜索 SkippableFact,然后您可以使用 [SkippableFact] 的所有功能