C# 在 xUnit.net 中的所有测试之前和之后运行一次代码
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13829737/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Run code once before and after ALL tests in xUnit.net
提问by George Mauer
TL;DR - I'm looking for xUnit's equivalent of MSTest's AssemblyInitialize(aka the ONE feature it has that I like).
TL; DR - 我正在寻找 xUnit 的等价于 MSTest 的AssemblyInitialize(也就是我喜欢的 ONE 功能)。
Specifically I'm looking for it because I have some Selenium smoke tests which I would like to be able to run with no other dependencies. I have a Fixture that will launch IisExpress for me and kill it on disposal. But doing this before every test hugely bloats runtime.
具体来说,我正在寻找它,因为我有一些 Selenium 烟雾测试,我希望能够在没有其他依赖项的情况下运行。我有一个 Fixture 可以为我启动 IisExpress 并在处置时终止它。但是在每次测试之前这样做会极大地增加运行时间。
I would like to trigger this code once at the start of testing, and dispose of it (shutting down the process) at the end. How could I go about doing that?
我想在测试开始时触发一次此代码,并在最后处理它(关闭进程)。我怎么能这样做呢?
Even if I can get programmatic access to something like "how many tests are currently being run" I can figure something out.
即使我可以通过编程访问诸如“当前正在运行多少测试”之类的内容,我也可以弄清楚。
采纳答案by gwenzek
As of Nov 2015 xUnit 2 is out, so there is a canonical way to share features between tests. It is documented here.
截至 2015 年 11 月,xUnit 2 已发布,因此有一种在测试之间共享功能的规范方式。它记录在此处。
Basically you'll need to create a class doing the fixture:
基本上,您需要创建一个类来执行夹具:
public class DatabaseFixture : IDisposable
{
public DatabaseFixture()
{
Db = new SqlConnection("MyConnectionString");
// ... initialize data in the test database ...
}
public void Dispose()
{
// ... clean up test data from the database ...
}
public SqlConnection Db { get; private set; }
}
A dummy class bearing the CollectionDefinitionattribute.
This class allows Xunit to create a test collection, and will use the given fixture for all test classes of the collection.
带有CollectionDefinition属性的虚拟类。这个类允许 Xunit 创建一个测试集合,并将为集合的所有测试类使用给定的夹具。
[CollectionDefinition("Database collection")]
public class DatabaseCollection : ICollectionFixture<DatabaseFixture>
{
// This class has no code, and is never created. Its purpose is simply
// to be the place to apply [CollectionDefinition] and all the
// ICollectionFixture<> interfaces.
}
Then you need to add the collection name over all your test classes. The test classes can receive the fixture through the constructor.
然后您需要在所有测试类上添加集合名称。测试类可以通过构造函数接收夹具。
[Collection("Database collection")]
public class DatabaseTestClass1
{
DatabaseFixture fixture;
public DatabaseTestClass1(DatabaseFixture fixture)
{
this.fixture = fixture;
}
}
It's a bit more verbose than MsTests AssemblyInitializesince you have to declare on each test class which test collection it belongs, but it's also more modulable (and with MsTests you still need to put a TestClass on your classes)
它比 MsTests 更冗长,AssemblyInitialize因为您必须在每个测试类上声明它属于哪个测试集合,但它也更具模块化(并且使用 MsTests 您仍然需要在您的类上放置一个 TestClass )
Note: the samples have been taken from the documentation.
注意:样本取自文档。
回答by Brad Wilson
It's not possible to do in the framework today. This is a feature planned for 2.0.
在今天的框架中是不可能做到的。这是 2.0 计划的功能。
In order to make this work before 2.0, it would require you to perform significant re-architecture on the framework, or write your own runners that recognized your own special attributes.
为了在 2.0 之前完成这项工作,您需要对框架进行重大的重新架构,或者编写自己的运行程序来识别您自己的特殊属性。
回答by Vincent
Does your build tool provide such a feature?
您的构建工具是否提供这样的功能?
In the Java world, when using Mavenas a build tool, we use the appropriate phases of the build lifecycle. E.g. in your case (acceptance tests with Selenium-like tools), one can make good use of the pre-integration-testand post-integration-testphases to start/stop a webapp before/after one's integration-tests.
在 Java 世界中,当使用Maven作为构建工具时,我们使用构建生命周期的适当阶段。例如,在您的情况下(使用类似 Selenium 的工具进行验收测试),您可以充分利用pre-integration-test和post-integration-test阶段在integration-tests之前/之后启动/停止 web 应用程序。
I'm pretty sure the same mechanism can be set up in your environment.
我很确定可以在您的环境中设置相同的机制。
回答by Jared Kells
Create a static field and implement a finalizer.
创建一个静态字段并实现一个终结器。
You can use the fact that xUnit creates an AppDomain to run your test assembly and unloads it when it's finished. Unloading the app domain will cause the finalizer to run.
您可以使用 xUnit 创建一个 AppDomain 来运行您的测试程序集并在完成后卸载它的事实。卸载应用程序域将导致终结器运行。
I am using this method to start and stop IISExpress.
我正在使用这种方法来启动和停止 IISExpress。
public sealed class ExampleFixture
{
public static ExampleFixture Current = new ExampleFixture();
private ExampleFixture()
{
// Run at start
}
~ExampleFixture()
{
Dispose();
}
public void Dispose()
{
GC.SuppressFinalize(this);
// Run at end
}
}
Edit: Access the fixture using ExampleFixture.Currentin your tests.
编辑:ExampleFixture.Current在您的测试中使用访问装置。
回答by Khoa Le
You can use IUseFixture interface to make this happen. Also all of your test must inherit TestBase class. You can also use OneTimeFixture directly from your test.
您可以使用 IUseFixture 接口来实现这一点。此外,您的所有测试都必须继承 TestBase 类。您还可以直接从测试中使用 OneTimeFixture。
public class TestBase : IUseFixture<OneTimeFixture<ApplicationFixture>>
{
protected ApplicationFixture Application;
public void SetFixture(OneTimeFixture<ApplicationFixture> data)
{
this.Application = data.Fixture;
}
}
public class ApplicationFixture : IDisposable
{
public ApplicationFixture()
{
// This code run only one time
}
public void Dispose()
{
// Here is run only one time too
}
}
public class OneTimeFixture<TFixture> where TFixture : new()
{
// This value does not share between each generic type
private static readonly TFixture sharedFixture;
static OneTimeFixture()
{
// Constructor will call one time for each generic type
sharedFixture = new TFixture();
var disposable = sharedFixture as IDisposable;
if (disposable != null)
{
AppDomain.CurrentDomain.DomainUnload += (sender, args) => disposable.Dispose();
}
}
public OneTimeFixture()
{
this.Fixture = sharedFixture;
}
public TFixture Fixture { get; private set; }
}
EDIT: Fix the problem that new fixture create for each test class.
编辑:修复新夹具为每个测试类创建的问题。
回答by Shimmy Weitzhandler
I use AssemblyFixture(NuGet).
我使用AssemblyFixture( NuGet)。
What it does is it provides an IAssemblyFixture<T>interface that is replacing any IClassFixture<T>where you want the object's lifetime to be as the testing assembly.
它的作用是提供一个IAssemblyFixture<T>接口来替换IClassFixture<T>您希望对象的生命周期作为测试程序集的任何位置。
Example:
例子:
public class Singleton { }
public class TestClass1 : IAssemblyFixture<Singleton>
{
readonly Singletone _Singletone;
public TestClass1(Singleton singleton)
{
_Singleton = singleton;
}
[Fact]
public void Test1()
{
//use singleton
}
}
public class TestClass2 : IAssemblyFixture<Singleton>
{
readonly Singletone _Singletone;
public TestClass2(Singleton singleton)
{
//same singleton instance of TestClass1
_Singleton = singleton;
}
[Fact]
public void Test2()
{
//use singleton
}
}
回答by Rolf Kristensen
To execute code on assembly initialize, then one can do this (Tested with xUnit 2.3.1)
要在程序集初始化时执行代码,则可以执行此操作(使用 xUnit 2.3.1 测试)
using Xunit.Abstractions;
using Xunit.Sdk;
[assembly: Xunit.TestFramework("MyNamespace.MyClassName", "MyAssemblyName")]
namespace MyNamespace
{
public class MyClassName : XunitTestFramework
{
public MyClassName(IMessageSink messageSink)
:base(messageSink)
{
// Place initialization code here
}
public new void Dispose()
{
// Place tear down code here
base.Dispose();
}
}
}
See also https://github.com/xunit/samples.xunit/tree/master/AssemblyFixtureExample
另见https://github.com/xunit/samples.xunit/tree/master/AssemblyFixtureExample
回答by Siderite Zackwehdex
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:
我很恼火,因为没有选择在所有 xUnit 测试结束时执行事情。这里的一些选项不是那么好,因为它们涉及更改所有测试或将它们放在一个集合中(意味着它们同步执行)。但是 Rolf Kristensen 的回答为我提供了获取此代码所需的信息。有点长,但是你只需要将它添加到你的测试项目中,不需要其他代码更改:
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<ITestCase> _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<ITestCase> 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
- 程序集:TestFramework 指示 xUnit 使用您的框架,该框架代理默认框架
- SideriteTestFramework 还将 executor 包装成一个自定义类,然后包装消息接收器
- 最后,执行 Finished 方法,运行测试列表和 xUnit 消息的结果
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.
在这里可以做更多的工作。如果您想在不关心测试运行的情况下执行某些内容,您可以从 XunitTestFramework 继承并包装消息接收器。

