Run code once before and after ALL tests in xUnit.net

后端 未结 8 849
野趣味
野趣味 2020-12-02 15:15

TL;DR - I\'m looking for xUnit\'s equivalent of MSTest\'s AssemblyInitialize (aka the ONE feature it has that I like).

Specifically I\'m looking for it

8条回答
  •  一向
    一向 (楼主)
    2020-12-02 15:46

    I was quite annoyed for not having the option to execute things at the end of all the xUnit tests. Some of the options here are not as great, as they involve changing all your tests or putting them under one collection (meaning they get executed synchronously). But Rolf Kristensen's answer gave me the needed information to get to this code. It's a bit long, but you only need to add it into your test project, no other code changes necessary:

    using Siderite.Tests;
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Reflection;
    using System.Text;
    using Xunit;
    using Xunit.Abstractions;
    using Xunit.Sdk;
    
    [assembly: TestFramework(
        SideriteTestFramework.TypeName,
        SideriteTestFramework.AssemblyName)]
    
    namespace Siderite.Tests
    {
        public class SideriteTestFramework : ITestFramework
        {
            public const string TypeName = "Siderite.Tests.SideriteTestFramework";
            public const string AssemblyName = "Siderite.Tests";
            private readonly XunitTestFramework _innerFramework;
    
            public SideriteTestFramework(IMessageSink messageSink)
            {
                _innerFramework = new XunitTestFramework(messageSink);
            }
    
            public ISourceInformationProvider SourceInformationProvider
            {
                set
                {
                    _innerFramework.SourceInformationProvider = value;
                }
            }
    
            public void Dispose()
            {
                _innerFramework.Dispose();
            }
    
            public ITestFrameworkDiscoverer GetDiscoverer(IAssemblyInfo assembly)
            {
                return _innerFramework.GetDiscoverer(assembly);
            }
    
            public ITestFrameworkExecutor GetExecutor(AssemblyName assemblyName)
            {
                var executor = _innerFramework.GetExecutor(assemblyName);
                return new SideriteTestExecutor(executor);
            }
    
            private class SideriteTestExecutor : ITestFrameworkExecutor
            {
                private readonly ITestFrameworkExecutor _executor;
                private IEnumerable _testCases;
    
                public SideriteTestExecutor(ITestFrameworkExecutor executor)
                {
                    this._executor = executor;
                }
    
                public ITestCase Deserialize(string value)
                {
                    return _executor.Deserialize(value);
                }
    
                public void Dispose()
                {
                    _executor.Dispose();
                }
    
                public void RunAll(IMessageSink executionMessageSink, ITestFrameworkDiscoveryOptions discoveryOptions, ITestFrameworkExecutionOptions executionOptions)
                {
                    _executor.RunAll(executionMessageSink, discoveryOptions, executionOptions);
                }
    
                public void RunTests(IEnumerable testCases, IMessageSink executionMessageSink, ITestFrameworkExecutionOptions executionOptions)
                {
                    _testCases = testCases;
                    _executor.RunTests(testCases, new SpySink(executionMessageSink, this), executionOptions);
                }
    
                internal void Finished(TestAssemblyFinished executionFinished)
                {
                    // do something with the run test cases in _testcases and the number of failed and skipped tests in executionFinished
                }
            }
    
    
            private class SpySink : IMessageSink
            {
                private readonly IMessageSink _executionMessageSink;
                private readonly SideriteTestExecutor _testExecutor;
    
                public SpySink(IMessageSink executionMessageSink, SideriteTestExecutor testExecutor)
                {
                    this._executionMessageSink = executionMessageSink;
                    _testExecutor = testExecutor;
                }
    
                public bool OnMessage(IMessageSinkMessage message)
                {
                    var result = _executionMessageSink.OnMessage(message);
                    if (message is TestAssemblyFinished executionFinished)
                    {
                        _testExecutor.Finished(executionFinished);
                    }
                    return result;
                }
            }
        }
    }
    

    The highlights:

    • assembly: TestFramework instructs xUnit to use your framework, which proxies to the default one
    • SideriteTestFramework also wraps the executor into a custom class that then wraps the message sink
    • in the end, the Finished method is executed, with the list of tests run and the result from the xUnit message

    More work could be done here. If you want to execute stuff without caring about the tests run, you could inherit from XunitTestFramework and just wrap the message sink.

提交回复
热议问题