TL; DR - Ich suche nach xUnits Äquivalent zu MSTests AssemblyInitialize
(auch bekannt als die ONE-Funktion, die mir gefällt).
Insbesondere suche ich danach, weil ich einige Selen-Rauchtests habe, die ich ohne andere Abhängigkeiten ausführen möchte. Ich habe ein Fixture, das IisExpress für mich startet und es bei der Entsorgung tötet. Aber dies zu tun, bevor jeder Test die Laufzeit enorm aufbläht.
Ich möchte diesen Code zu Beginn des Tests einmal auslösen und am Ende entsorgen (den Prozess herunterfahren). Wie könnte ich das machen?
Selbst wenn ich programmgesteuerten Zugriff auf etwas wie "Wie viele Tests werden derzeit ausgeführt" erhalten kann, kann ich etwas herausfinden.
c#
automated-tests
xunit.net
George Mauer
quelle
quelle
Antworten:
Ab November 2015 ist xUnit 2 verfügbar, daher gibt es eine kanonische Möglichkeit, Funktionen zwischen Tests auszutauschen. Es ist hier dokumentiert .
Grundsätzlich müssen Sie eine Klasse erstellen, die das Fixture ausführt:
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; } }
Eine Dummy-Klasse mit dem
CollectionDefinition
Attribut. Diese Klasse ermöglicht es Xunit, eine Testsammlung zu erstellen, und verwendet das angegebene Gerät für alle Testklassen der Sammlung.[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. }
Dann müssen Sie den Sammlungsnamen über alle Ihre Testklassen hinzufügen. Die Testklassen können das Gerät über den Konstruktor empfangen.
[Collection("Database collection")] public class DatabaseTestClass1 { DatabaseFixture fixture; public DatabaseTestClass1(DatabaseFixture fixture) { this.fixture = fixture; } }
Es ist etwas ausführlicher als MsTests,
AssemblyInitialize
da Sie für jede Testklasse angeben müssen, zu welcher Testsammlung sie gehört, aber es ist auch modulierbarer (und mit MsTests müssen Sie Ihren Klassen noch eine Testklasse hinzufügen).Hinweis: Die Proben wurden der Dokumentation entnommen .
quelle
[CollectionDefinition("Database collection")]
Attribut als auch dieICollectionFixture<DatabaseFixture>
Schnittstelle zurDatabaseFixture
Klasse hinzugefügt und alles funktioniert. Es entfernt eine leere Klasse und scheint mir sauberer!Erstellen Sie ein statisches Feld und implementieren Sie einen Finalizer.
Sie können die Tatsache nutzen, dass xUnit eine AppDomain erstellt, um Ihre Testassembly auszuführen und sie nach Abschluss zu entladen. Durch das Entladen der App-Domäne wird der Finalizer ausgeführt.
Ich verwende diese Methode, um IISExpress zu starten und zu stoppen.
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 } }
Bearbeiten: Greifen Sie
ExampleFixture.Current
in Ihren Tests auf das Gerät zu .quelle
AppDomain
Unload
könnte die Veranstaltung nützlicher sein? (Natürlich sind während des Herunterfahrens alle Wetten aus, aber es kann nur ein bisschen zuverlässiger aus dem Speicher sein)Um Code bei der Assembly-Initialisierung auszuführen, kann dies ausgeführt werden (Getestet mit 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(); } } }
Siehe auch https://github.com/xunit/samples.xunit/tree/master/AssemblyFixtureExample
quelle
Dies ist heute im Rahmen nicht möglich. Dies ist eine für 2.0 geplante Funktion.
Damit dies vor 2.0 funktioniert, müssen Sie eine erhebliche Neuarchitektur des Frameworks durchführen oder Ihre eigenen Läufer schreiben, die Ihre eigenen speziellen Attribute erkennen.
quelle
Ich benutze AssemblyFixture ( NuGet ).
Es bietet eine
IAssemblyFixture<T>
Schnittstelle, die alle ersetzt, anIClassFixture<T>
denen die Lebensdauer des Objekts als Testbaugruppe erfolgen soll.Beispiel:
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 } }
quelle
[assembly: TestFramework("Xunit.Extensions.Ordering.TestFramework", "Xunit.Extensions.Ordering")]
einer Klasse des Projekts hinzufügen . ReferenzIch war ziemlich verärgert darüber, dass ich am Ende aller xUnit-Tests nicht die Möglichkeit hatte, Dinge auszuführen. Einige der hier aufgeführten Optionen sind nicht so großartig, da sie alle Ihre Tests ändern oder unter eine Sammlung stellen (was bedeutet, dass sie synchron ausgeführt werden). Aber die Antwort von Rolf Kristensen gab mir die notwendigen Informationen, um an diesen Code zu gelangen. Es ist etwas lang, aber Sie müssen es nur zu Ihrem Testprojekt hinzufügen, es sind keine weiteren Codeänderungen erforderlich:
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; } } } }
Die Höhepunkte:
Hier könnte noch mehr Arbeit geleistet werden. Wenn Sie Dinge ausführen möchten, ohne sich um den Testlauf zu kümmern, können Sie von XunitTestFramework erben und einfach die Nachrichtensenke einpacken.
quelle
Sie können die IUseFixture-Schnittstelle verwenden, um dies zu erreichen. Außerdem muss Ihr gesamter Test die TestBase-Klasse erben. Sie können OneTimeFixture auch direkt aus Ihrem Test heraus verwenden.
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; } }
BEARBEITEN: Beheben Sie das Problem, das neue Geräte für jede Testklasse verursachen.
quelle
ApplicationFixture
werden vor und nach jedem Test ausgeführt, nicht nur einmal. Es ist auch ein schlechter Rat, immer von demselben zu erben, derTestBase
Ihren einen Vererbungslink verbraucht, und Sie können ihn nicht mehr verwenden, um gemeinsame Methoden zwischen einer Gruppe verwandter Klassen zu teilen. Genau deshalbIUseFixture
wurde es erfunden, um sich nicht auf Vererbung verlassen zu müssen. Schließlich werden Sie feststellen, dass der Ersteller von xUnit bereits die akzeptierte Antwort auf die Frage hat, dass dies bis zum Versand von 2.0 nicht ordnungsgemäß möglich ist.Bietet Ihr Build-Tool eine solche Funktion?
In der Java-Welt verwenden wir bei Verwendung von Maven als Build-Tool die entsprechenden Phasen des Build-Lebenszyklus . In Ihrem Fall (Abnahmetests mit Selen-ähnlichen Tools) kann man beispielsweise die Phasen
pre-integration-test
und gut nutzen,post-integration-test
um eine Webanwendung vor / nach der eigenen zu starten / zu stoppenintegration-test
.Ich bin mir ziemlich sicher, dass der gleiche Mechanismus in Ihrer Umgebung eingerichtet werden kann.
quelle