How to dynamically skip a test with Xunit 2.0?

2020-05-29 16:11发布

Xunit 1.9.x provides the user with the DynamicSkipExample.cs example to help him setting up dynamic skipping of a [Fact].

This has proven to be quite useful when performing some cross-platform development. This allows a test to be temporarily ignored when it cannot be properly run because of the underlying context (OS, filesystem, ...).

However, this example has been dropped in commit 2deeff5 on the road to version 2.0.

How can one re-implement such a functionality through one of the extensibility points of Xunit 2.0?

Note: An issue about this topic has been raised in the xUnit tracker. See xunit/xunit#250.

标签: xunit.net
3条回答
看我几分像从前
2楼-- · 2020-05-29 16:34

[Update: XUnit v2.0 (RTM) is now available and skippable tests are supported by it directly. Use [Fact (Skip = "specific reason")] ]

Note that XUnit v2.0 has not shipped. This example is compatible with Xunit 2.0 beta5 which you can find on nuget.org. There may be other ways to accomplish this (as this is just the example I came to).

1) Define an attribute that will decorate your tests.

/// <inheritdoc/>
[AttributeUsage( AttributeTargets.Method, AllowMultiple = false )]
[XunitTestCaseDiscoverer( "SkippableTestCaseDiscoverer", "assemblynamehere" )]
public sealed class SkippableTestAttribute : FactAttribute
{
    public SkippableTestAttribute() { }
}    

2) Create your discoverer. (We followed the code example at 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) Create a class which implements IXunitTestCase (as the default base class doesn't allow modifying the skip reason).

// 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();
    }
}

You have many options for how you want to set the base.SkipReason. In this sample, a public method was created.

This example will skip tests that have a MemberDataAttribute that returns no data rows. You can modify it to return the SkippableTestCase based on your criteria. For example, this discover skips tests on Sunday.

/// <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 ) };
    }
}
查看更多
混吃等死
3楼-- · 2020-05-29 16:57

You can also search for SkippableFact in the Nugget Manager, then you can use all functions with [SkippableFact]

查看更多
仙女界的扛把子
4楼-- · 2020-05-29 16:57

This has eventually been fixed in issue xunit/samples.xunit#8.

The amazing @BradWilson made this available as a complete and detailed sample in the samples.xunit repository.

查看更多
登录 后发表回答