Nunit: Instanz-pro-Testfall-Funktion

Erstellt am 24. Nov. 2017  ·  112Kommentare  ·  Quelle: nunit/nunit

Das mentale Modell eines jeden scheint zu sein, dass bei der Parallelisierung innerhalb eines Fixtures pro Testfall eine Instanz erstellt wird (https://github.com/nunit/nunit/issues/2105, https://github.com/nunit/nunit/issues /2252, https://github.com/nunit/nunit/issues/2573, obwohl dies nie der Fall war. Ich neige dazu, dieses Problem zu lösen, indem ich immer statische Klassen verwende und eine verschachtelte Einweg-Fixture-Klasse definiere, die in gewisser Weise eine reichere Erfahrung bietet ( Beispiel ), aber das ist vielleicht nicht das, was wir für alle wollen. Der kleine Nachteil hier ist in der Terminologie, dass die statische Klasse das ist, was NUnit als Fixture ansieht, aber die eigentliche Fixture ist die verschachtelte Klasse.

Es ist keine Option, Instanz pro Testfall als Standard festzulegen, da dies nicht parallele Fixtures unterbricht, die darauf angewiesen sind, dass ein Test auf den Zustand eines anderen Tests zugreifen kann. Personen, die das Attribut [Order] , sind wahrscheinlich die einzigen Personen, die davon betroffen sind, aber es würde jeden auftragsabhängigen Test, den ich mir vorstellen kann, vollständig zerstören.

Ich mag Charlies Vorschlag:

  • Ein Attribut auf Baugruppenebene, das es ermöglicht, die Art und Weise festzulegen, wie Testvorrichtungen erstellt werden, um eine einzelne Instanz oder eine Instanz pro Vorrichtung zu sein.
  • Single Instance würde genauso funktionieren wie jetzt
  • Instanz pro Gerät würde für jeden Test ein neues Gerät erstellen
  • ENTWEDER Führen Sie einmalig Setup- und Teardown-Aktivitäten für jeden Test durch (dh nicht mehr "einmalig") ODER veranlassen Sie, dass OneTimeSetUp und OneTimeTearDown als Fehler gekennzeichnet werden.

Mögliche Erweiterungen:

  • Machen Sie diese Klasse ebenfalls auf Ebene, sodass einzelne Leuchten unterschiedliche Lebenszyklen haben können.
  • Haben Sie eine dritte Einstellung auf Assembly-Ebene, die automatisch separate Instanzen für jedes Gerät ausführt, das einen oder mehrere Tests enthält, die parallel ausgeführt werden. (Schwierig, immer im Voraus zu wissen)

Ich würde jedoch sagen, zuerst die Grundlagen.

Im Moment ist es garantiert ein Fehler, wenn es einen veränderlichen Instanzstatus gibt und ein Fixture parallel zu sich selbst läuft, also könnten wir eine Auto-Option haben, die auf Instanz-pro-Testfall umschaltet, wenn Testfälle aus dem Fixture für die Ausführung konfiguriert sind parallel und vermeiden Sie Breaking Changes . Ich denke, Auto hat das Potenzial, verwirrend zu sein, aber es könnte auch einfach die vernünftigste Standardeinstellung sein, damit nicht jeder routinemäßig [assembly: InstancePerTestCase] hinzufügt.

Ich bin für Leitplanken. OneTimeSetUp und OneTimeTearDown sollten als Fehler gekennzeichnet werden, wenn das Gerät eine Instanz pro Testfall durchführt. Vielleicht, es sei denn, sie sind statisch?

done feature normal docs releasenotes

Hilfreichster Kommentar

Besteht die Möglichkeit, dass wir das überprüfen lassen? fühlt sich an, als würden wir uns sehr nahe kommen

Alle 112 Kommentare

Derzeit __können__ Sie den Status zwischen parallelen Tests teilen, vorausgesetzt, (1) Sie legen den Status im Konstruktor oder einmaligen Setup fest und (2) ändern ihn in keinem Test. In manchen Fällen kommt man sogar damit zurecht, wenn der Zustand im Test oder Setup auf eine Konstante gesetzt wird, vorausgesetzt, Sie setzen ihn nie auf einen anderen Wert. Letzteres ist eklig, obwohl ich denke, dass wir einige NUnit-Tests haben, die dies tun.

Mein Gedanke ist, dass wir die Standardeinstellung in absehbarer Zeit nicht ändern können, wenn überhaupt. Der veränderliche Status garantiert derzeit keinen Fehler. Es ist nur ein Fehler, wenn Sie es tatsächlich mutieren, und wir wissen das nicht, es sei denn, wir führen eine Codeanalyse durch - was wir nicht tun!

Wenn Sie innerhalb eines Tests, Setups oder Teardowns keinen Instanzstatus ändern, werden Sie nicht gebrochen. Wenn Sie dies tun, sind Sie bereits kaputt, solange Ihre Tests parallel zu sich selbst laufen können. Ich kann mir auf keinen Fall vorstellen, dass du nicht gebrochen bist. Das ist die Rechtfertigung für die Option Auto, wenn wir der Meinung sind, dass die resultierende Komplexität für den Benutzer das Ergebnis "vernünftige Voreinstellungen" wert ist.

Sie haben Recht, aber ich würde weiterhin dafür plädieren, dass dies eine mögliche Verlängerung ist, über die wir jetzt nicht entscheiden müssen. Lassen Sie uns dies iterativ tun, anstatt zu versuchen, alle Entscheidungen jetzt zu treffen.

Ich bin bei @CharliePoole, die

vielleicht ein Attribut auf Fixture-Ebene, um eine Instanz pro Thread auszulösen, anstatt eine für alle Threads geteilte Instanz.

@BlythMeister Keine Sorge! Wir ziehen keine Änderung in Betracht, die dazu führen würde, dass der vorhandene Code kaputt geht.

Das ist interessant. Ich denke, Instanz-pro-Testfall ist einfacher zu verstehen als Instanz-pro-Thread. Die Art und Weise, wie Tests Threads zugewiesen werden, wäre unzuverlässig, daher kann ich mir keinen Vorteil vorstellen, der gegenüber Instanz-pro-Testfall hätte.

Ja, ich verwende den Begriff Thread, um parallele Ausführung zu bedeuten .... was faktisch ungenau ist.
Entschuldigen Sie.

In Bezug auf XP (und ich hoffe, jeder weiß, dass das immer meine Perspektive ist) haben wir drei Geschichten, die aufeinander aufbauen...

  1. Der Benutzer kann Instanz-pro-Testfall-Verhalten global für alle Fixtures in einer Baugruppe festlegen.

  2. Der Benutzer kann Instanz-pro-Test-Case-Verhalten auf einem Fixture spezifizieren.

  3. Benutzer können Instanz-pro-Testfall-Verhalten für Fixtures festlegen, die Tests enthalten, die parallel ausgeführt werden können.

Ich würde gerne sehen, dass (1) in dieser Ausgabe vollständig implementiert ist. Ich denke, wir sollten (2) für den Moment einfach aus unseren Köpfen – oder zumindest aus dem Teil, der Code schreibt – streichen. Ich denke, (3) ist eine Möglichkeit, hat aber einige Fallstricke, auf die wir bereits bei anderen Problemen gestoßen sind. (Ein Fixture weiß nicht, ob es Testfälle parallel laufen lassen wird!) Lassen Sie uns über das eine JustInTime sprechen, das nach (1) und (2) abgeschlossen und zusammengeführt wäre. FWIW, ich denke, wir neigen dazu, uns zu verzetteln, indem wir versuchen, mehrere Züge vorauszudenken. Ich habe (2) und (3) erwähnt, damit wir sie in keiner Weise blockieren, was leicht zu vermeiden scheint.

Ich denke, da sind wir uns alle einig. Ich habe jedoch ein paar NUnit-Prioritäten davor, also überlasse ich es anderen, sie zu kommentieren und möglicherweise aufzugreifen.

Das Schöne daran, dies mit einem Attribut statt einer Befehlszeileneinstellung zu tun, ist, dass es für jeden Runner funktioniert.

Ich stimme @CharliePoole zu, dass es ein Attribut sein sollte. Tests müssen unter Berücksichtigung der Instanz pro Test oder Instanz pro Fixture entworfen werden, daher sollten sie immer so ablaufen. Ich ziehe es auch vor, nur Option 1 durchzuführen. Ich denke, wenn Sie diese Art des Testens wünschen, sollten Sie sich für Ihre gesamte Testsuite dafür entscheiden. Die Möglichkeit, sich auf niedrigeren Ebenen anzumelden, führt zu Komplexität, die meiner Meinung nach keinen guten ROI hat.

Nur neugierig, ob es irgendwelche Pläne gibt, diese Funktion zu implementieren?

@rprouse Dies ist immer noch als Design erfordernd gekennzeichnet, aber es sieht so aus, als ob die Diskussion bereits alles behandelt hätte.

@CharliePoole stimmte zu, dass dies vom Design @pfluegs Wir haben noch nicht damit begonnen, darüber hinaus zu entwerfen, also gibt es noch keine Pläne.

Ich bin mir nicht sicher, ob jemand dem so viel Aufmerksamkeit schenkt, aber das Fehlen von Diskussionen oder Designlabels deutet darauf hin, dass sich jemand freiwillig melden könnte. 😄

Ich wurde gerade von diesem Problem schwer gebissen - +1 zur Behebung.

Falls wir jemals mehr Optionen wollen, würden wir wollen:

```c#
[Baugruppe: FixtureCreation(FixtureCreationOptions.PerTestCase)]

And:
```diff
namespace NUnit.Framework
{
+   public enum FixtureCreationOptions
+   {
+       Singleton, // = default
+       PerTestCase
+   }
}

Meine eigene Meinung dazu hat sich ein wenig geändert, zumindest im Kontext eines neuen Frameworks.

Ich denke jetzt, dass es zwei Attribute auf Klassenebene geben sollte, eines für jedes Verhalten. Ich würde TestFixture mit einer Instanz pro Test arbeiten lassen und Shared Fixture funktioniert wie jetzt NUnit. Offensichtlich ist dies eine bahnbrechende Änderung im Kontext von NUnit, aber der allgemeine Ansatz könnte eine Überlegung wert sein.

@CharliePoole [TestFixture(FixtureOptions.PerTestCase)] scheint eine schöne Syntax zu sein, aber ich möchte unbedingt auch ein Attribut auf Assemblyebene, damit ich mich bei jedem Testprojekt anmelden kann. Wie soll ein Attribut auf Assemblyebene aussehen?

Richtig, deshalb habe ich die Vorbehalte gegenüber WRT geäußert, was ich bei einem neuen Framework tun würde. Im Fall von NUnit möchten Sie jedoch die Semantik von TestFixture nicht ändern, und mir fällt kein wirklich guter Name für das Instanz-pro-Fall-Verhalten ein. Wenn Sie zwei Namen hätten, müssten Sie lediglich eine globale Bearbeitung Ihrer Quelldatei vornehmen, um das gewünschte Verhalten aller Geräte zu erreichen.

Ich mag globale Einstellungen auf Assembly-Ebene nicht, weil sie einige Entwickler verwirren, die möglicherweise nicht wissen, was in der AssemblyInfo-Datei festgelegt ist. Derzeit erlauben wir einige wenige, daher kann es nicht schaden, noch eine weitere hinzuzufügen. Ich habe keine Gedanken über einen Namen für das Assembly-Level-Attribut, außer dass es kommunizieren sollte, dass wir eine Option (Standard?) für alle Fixtures in der Assembly setzen.

Aus genau dem Grund, den Sie erwähnt haben, mag ich bis zu einem gewissen Grad auch keine Voreinstellungen auf Assembly-Ebene. Im Vergleich zum Hinzufügen eines neuen Attributs zu jeder meiner derzeit attributlosen Geräteklassen ist das Attribut auf Assembly-Ebene die Route, die ich bevorzugen würde.

@nunit/framework-team Was denkst du? Zur ersten Einführung der Funktion:

  • ja/nein zur Einstellung auf Baugruppenebene?
  • ja/nein zur Fixture-Level-Einstellung?
  • ja/nein zur Einstellung auf Methodenebene?
  • ja/nein zur Einstellung auf Testfallebene?

Guter Hinweis zu attributlosen Geräteklassen.

Ich würde sagen ja, ja, nein, nein

Ich stimme Charlie zu - mit der Klarstellung, dass "Fixture-Level" "Klassen-Level" bedeutet. (Im Gegensatz zu zB einer Testfall-Quellmethode. 😊)

Das hilft! Das Problem bei [TestFixture(FixtureCreationOptions.PerTestCase)] ist also, dass mehrere TestFixture-Attribute in derselben Klasse erlaubt sind und sich dann widersprechen können. Besser und einfacher vielleicht, ein neues benanntes Attribut zu haben, das auf Assemblys und Klassen anwendbar ist?

```c#
[Assembly: ShareFixtureInstances(false)]

[ShareFixtureInstances(false)]
öffentliche Klasse SomeFixture
{
// ...
}

Framework API:

```diff
namespace NUnit.Framework
{
+   [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
+   public sealed class ShareFixtureInstancesAttribute : PropertyAttribute
+   {
+       public bool ShareFixtureInstances { get; }

+       public ShareFixtureInstancesAttribute(bool shareFixtureInstances);
+   }
}

Hört sich gut an. Wie ist das Vererbungsverhalten davon - wenn ich das Attribut auf einer Basisklasse mit eigenen Tests habe und dann auch von dieser Klasse erbe? 😊

Da es sich vermutlich um ein 'Klassen'-Attribut handelt - muss es sich um die tatsächliche Klasse handeln, von der aus die fraglichen Tests ausgeführt werden?

Außerdem bin ich nicht ganz begeistert von dem Namen - da ein Fixture nicht immer eine Klasse in Nunit-Begriffen ist, denke ich nicht (zB alle Tests in einer Testfallquelle - es sei denn, 'suite' und 'fixture' haben eine stärkere Semantik Bedeutung, als ich denke, in Nunit-Begriffen!)

Wie wäre es mit etwas in der Art von InstancePerTest...aber besser benannt...

Über Versammlung und Klassenstufe vereinbart, aber nicht tiefer. Ich bevorzuge auch etwas in der Art von @ChrisMaddocks Vorschlag ClassInstancePerTest(bool) . Ich gehe auch davon aus, dass das Attribut auf Klassenebene die Assemblyebene überschreiben würde und ich würde erwarten, dass es vererbt wird. Ich denke, Vererbung ist wichtig, da Sie normalerweise eine Instanz pro Test basierend auf den Mitgliedern einer Klasse in Kombination mit Parallelität wünschen. Sie möchten daher, dass dies automatisch auf abgeleitete Klassen angewendet wird.

Ich war einmal wirklich überrascht NUnit nicht eine Instanz pro Test erstellen , wenn eine Testvorrichtung Typ konstruierbar ist. Ich dachte immer an Unit-Tests wie, ich weiß nicht, Unit- Test: Sie bauen ein Gerüst auf, führen den Test durch, und das Gerüst ist weg. Keine unbeabsichtigte Zustandsübertragung zwischen den Tests.

Das habe ich gelernt. Ich instanziiere wie @jnm2 Tests manuell. Aber wenn die Tests zB Dateien erstellen und löschen, werden Fallläufer wirklich umständlich: Sie müssen IDisposable sein; Keine magischen [TearDown] für dich. :( Es gibt auch keine konsistente Möglichkeit, Testfehler von Gerüstfehlern zu unterscheiden. Dies ist ein wirklich schmerzhafter Anwendungsfall.

Wenn ich mich in das Design einmischen darf, bin ich bei

Der Name PerTestInstance , InstancePerTest oder so ähnlich klingt viel besser als ShareFixtureInstances . Ich mag das Wort Class im Namen des Attributs leicht nicht, da ich beim Testen von F#-Code nicht an Typinstanzen denke, die Klassen sind . Aber damit kann ich sicher leben! :)

Oh Junge, kann diese Funktion kaum erwarten!

@kkm000 Ich stimme der Verwendung von "Klasse" zu. NUnit verwendet normalerweise Namen, die sich von der Implementierung unterscheiden. Selbst dann machen die Leute Annahmen, aber es ist besser, solche Dinge konsequent zu vermeiden.

Beispiele wovon ich spreche: Test, TestFixture, SetUpFixture, Worker (kein Thread), etc. Im Prinzip kann eine Klasse __könnte__ einen einzelnen Test darstellen, kein Fixture... Ich sage nicht, dass wir das tun oder gar sollte, aber __wir könnten__.

@kkm000

Danke für den Kommentar! Du platzt gar nicht rein. Wir haben das Ziel, möglichst sprachunabhängig zu sein.
Neben dem Fokus auf C# noch eine Sache zum Wort class : Was wäre, wenn wir eines Tages Fixtures unterstützen würden, die keine Klassen sind?

[InstancePerTestCase] würde das Problem umgehen, aber es könnte die intuitive Klarheit verlieren.

Heutzutage weichen Fixtures und Klassen nicht sehr stark voneinander ab, aber wenn sie divergieren, bin ich mir ziemlich sicher, dass sich dieses Attribut auf Fixtures und nicht auf Klassen bezieht.

Suites sind ein generisches Organisationskonzept, während Fixtures Dinge sind, die die Setup-/Teardown-Logik und den Teststatus enthalten und an Testmethoden übergeben werden (derzeit als this Argument der Instanzmethoden). Geräte müssen nicht Teil der Organisationshierarchie sein. Stellen wir uns Fixtures vor, die vom Konzept der Klassen und vom Konzept der Testsuiten abweichen:

```c#
öffentliche statische Klasse SomeTestSuite // Kein Fixture: keine Setup-/Teardown-Logik und kein Teststatus
{
[Test] // Einzeltest, keine parametrisierte Suite;
// Fixture wird über einen Parameter injiziert anstatt über den impliziten this Parameter
public static void SomeTest(SomeFixture Fixture)
{
Fixture.SomeService.SomeProperty = true;
Fixture.SomeAction();
Fixture.AssertSomething();
}
}

[TestFixture]
public struct SomeFixture : IDisposable
{
public SomeService SomeService { get; } // Bundesland

public SomeFixture() // This was almost legal syntax in C# 6, but I'm making a point 😁
{
    // Setup
}

public void Dispose()
{
    // Teardown
}

public void SomeAction()
{
    // Helper method
}

public void AssertSomething()
{
    // Helper method
}

}
```

Im obigen Beispiel ist [FixtureInstancePerTestCase] oder [SharedFixtureInstances(false)] nicht auf Klassen und nicht auf Hierarchien (Suiten) fokussiert: es konzentriert sich auf Fixtures, und Fixtures sind die wiederverwendbare Setup/Teardown-Logik und der Status. Insbesondere geht es darum, ob für jeden Testfall ein neues SomeFixture erstellt wird oder ob dasselbe für jeden Test bestanden wird.

Da dieses Attribut steuert, ob verschiedene ITests ITest.Fixture-Instanzen teilen, und das gesteuert wird, was wir mit [TestFixture] dekorieren (ein weiteres Attribut mit Fixture im Namen), scheint es wie ein _consistent_ Option, um das Wort Fixture zu verwenden.

(Ich bringe Punkte mit, die wichtig zu diskutieren scheinen, aber ich bin nicht sehr daran interessiert, wie der Name letztendlich lautet.)

@jnm2 Sie haben völlig Recht, dass das Konzept einer Suite und eines Fixtures trennbar ist. NUnit folgte jedoch dem Beispiel aller xunit-Testframeworks, die zum Zeitpunkt seiner Erstellung existierten, indem es die beiden kombinierte. Es wich von anderen Frameworks ab, indem es das Gerät gemeinsam nutzte.

Meines Wissens war xunit.net das erste (einzige?) Framework, das das Fixture zu einer separaten Entität von der Testvererbungshierarchie machte.

Als ich parametrisierte Tests erstellt habe, hätte ich TestCaseSource als separates "Fixture" einführen können. Ich habe das nicht getan und ich denke, die Änderungen, die erforderlich sind, um es zu wiederholen, würden jetzt kaputt gehen.

Ich denke, der Grund, warum Leute gelegentlich (vor der Parallelität) nach Instanz-pro-Fall-Verhalten fragen, ist, dass andere Frameworks dies tun. Aus einem ähnlichen Grund haben sie __nicht__ nach trennbaren Leuchten gefragt.

Zusammenfassend denke ich also...

  1. Wir brauchen Instanzen pro Testfallverhalten.

  2. Aus Gründen der Abwärtskompatibilität kann dies nicht der Standard sein.

  3. Eine trennbare Halterung ist eine coole Idee, aber auch ein separates Thema.

  4. Trennbare Fixtures sehen mindestens so komplex zu implementieren aus wie Instanz-Fixtures. Natürlich müssten Sie sich in diesem Fall nicht mit der Abwärtskompatibilität befassen, was es einfacher macht.

Nur um die Punkte 3 und 4 zu bestätigen, ich habe mein Codebeispiel nicht als Vorschlag für neue Funktionen gedacht, sondern als Gedankenexperiment, das uns helfen soll, den richtigen Namen für dieses Attribut zu finden. Das Gedankenexperiment sagt mir, dass wir uns wirklich auf die Idee einer Einrichtung konzentrieren, nicht so sehr auf eine Klasse oder eine Suite, auch wenn sie gerade zufällig zusammenfallen.

Unabhängig von ihrer Form sind Fixtures wiederverwendbare Definitionen von Setup/Teardown/State. Ich glaube, das ist die Bedeutung des Wortes. Fixture-Instanzen (Auftreten von Setup/Teardown/Zustand) sind das, was unser neues Attribut zwischen Testfällen freigibt oder nicht teilt. Ich denke, FixtureInstances irgendwo im Namen würde maximale Klarheit bringen.

Scheint das jedem richtig zu sein? Wenn nicht, welche weiteren Punkte können wir berücksichtigen?

Ah! Entschuldigung, ich dachte, Sie hätten einen sehr guten Punkt erreicht, der normalerweise übersehen wird, aber ich hatte auch Angst, dass wir abschweifen könnten. :leicht_die Stirn runzeln:

Ich denke, der vollständige Name (was auch immer Sie eingeben) sollte sowohl Fixture als auch Instance enthalten. Wie es gemacht wird, hängt davon ab, ob es sich um einen Attributnamen oder eine Enumeration handelt, die beim Erstellen verwendet wird. Ich bin mir nicht sicher, ob Sie sich für das eine oder andere entschieden haben.

Ja, guter Punkt.

Wenn wir eine Aufzählung machen, wird die Eingabe länger. Dies ist für klassenbezogene Attribute wichtiger als für Assemblys. Wir sollten auch davon Abstand nehmen, eine Aufzählung als Parameter zu TestFixtureAttribute hinzuzufügen, da mehrere TestFixtureAttributes in derselben Klasse zulässig sind und der Parameter des einen dem Parameter des anderen widersprechen könnte. Wir können dies über einen Testzeitfehler handhaben, aber es ist normalerweise schön, ungültige Zustände nicht darstellbar zu machen.

Lassen Sie uns einige Optionen aufstellen und wir können von dort aus optimieren.
Wir suchen nach etwas, das Intuitivität, Konsistenz mit dem Stil von NUnit und das richtige Maß an Flexibilität in Einklang bringt. (Diese Dinge sollten sich zum größten Teil schon überschneiden. 😄)


Vorschlag A

```c#
[assembly: FixtureOptions(InstancePerTestCase = true)]

[FixtureOptions(InstancePerTestCase = false)]
Klasse SomeFixture
{
}

Pro: extending later if there's ever more options for fixture handling (doubtful?)
Pro: two names so there's less stuffed into a single name
Con: `[FixtureOptions]` would be legal but highly misunderstandable syntax. (Does it reset the assembly-wide setting to the default, `false`, or does it have no effect, e.g. `null`?)


### Proposal B

```c#
[assembly: FixtureOptions(FixtureCreationOptions.PerTestCase)]

[FixtureOptions(FixtureCreationOptions.Singleton)]
class SomeFixture
{
}

public enum FixtureCreationOptions
{
    Singleton, // = default
    PerTestCase,
    // Doubtful there will be other options?
    // Maybe Pool, where we reset and reuse between nonconcurrent tests?    
}

Pro: Ungültige Zustände sind nicht darstellbar
Pro: später verlängern, wenn es immer mehr Optionen für das Fixture-Handling gibt (zweifelhaft?)
Nachteil: Redundanz bei der Eingabe sowohl des Attributnamens als auch des Enum-Namens

Vorschlag C

```c#
[Baugruppe: FixtureInstancePerTestCase]

[FixtureInstancePerTestCase(false)]
Klasse SomeFixture
{
}
```

Pro: Ungültige Zustände sind nicht darstellbar
Pro: schwer zu missverstehen (aber ich habe hier einen blinden Fleck, weil ich kein neuer Benutzer bin)
Contra: langer Name
Vielleicht con: Sehr spezifischer Name, der sowohl die Option als auch den Wert angeben kann. (Aber wir haben auch [NonParallelizable] die ich mag.)


(Alternative Vorschläge willkommen!)

Zwei Attribute sprechen für sich: eines für die Assembly und eines für die Klasse selbst.

Pro: Es ist einfacher, zwei einfache Attribute anstelle eines Attributs mit unterschiedlichem Verhalten im Kontext zu implementieren. (Lektion aus ParallelizableAttribute)
Vorteil: Für Benutzer wahrscheinlich leichter verständlich, da jeder entsprechend benannt werden kann. Die Baugruppenebene kann beispielsweise das Wort "Standard" enthalten.
Con: Zwei Attribute, die man sich merken sollte.

Gehen Sie mit jeder Ihrer Alternativen vor:

```C#
[Assembly: DefaultFixtureOptions(InstancePerTestCase = true)]

[FixtureOptions(InstancePerTestCase = false)]
Klasse SomeFixture
{
}

[Assembly: DefaultFixtureOptions(FixtureCreationOptions.PerTestCase)]

[FixtureOptions(FixtureCreationOptions.Singleton)]
Klasse SomeFixture
{
}

[Assembly: DefaultFixtureInstancePerTestCase]

[FixtureInstancePerTestCase(false)]
Klasse SomeFixture
{
}
```

Andere Kommentare:

  • Ich mag A und B besser als C
  • Sie sollten das gemeinsame Fixture-Verhalten nicht "Singleton" nennen, denn es ist keins. :smile: Wenn Sie erben, erhalten Sie mehrere Instanzen des Basisgeräts.
  • Wenn Sie separate Attribute auf Assembly- und Fixture-Ebene verwenden, können Sie Fixtures aus dem Namen auf Fixture-Ebene entfernen.
  • Wir haben nicht spezifiziert, wie SetUpFixtures überhaupt dazu passen. Sie sind problematisch und erfordern möglicherweise einen Laufzeitfehler, wenn das Attribut für sie verwendet wird. Tatsächlich besteht ein Vorteil eines völlig separaten Attributs darin, dass wir uns damit nicht befassen müssen. (Es ist auch ein Vorteil für eine Eigenschaft von TestFixture, BTW)
  • Wir müssen die Leute warnen, dass die Instanz pro Testfall nicht für statische Klassen gilt. Es ist für uns alle offensichtlich, aber es gibt Leute, die es nicht sofort verstehen.

Übrigens, ich habe mir oft gewünscht, wir hätten DefaultTestCaseTimeOut für die Assembly- und Fixture-Ebenen verwendet und Timeout nur für Methoden beibehalten.

Gut, danke, dass du diese Möglichkeit angesprochen hast.

Was mich im Moment stört ist, dass dies theoretisch Sinn machen könnte, auch wenn wir nicht in diese Richtung gehen:

c# [DefaultFixtureOptions(InstancePerTestCase = true)] class SomeFixture { [FixtureOptions(InstancePerTestCase = false)] public void SomeTest() { } }

Denn die Ebene, auf der dies tatsächlich abläuft, ist nicht die Fixture-Ebene selbst, sondern die Testfallebene.
Als Beispiel: Ein abgeleitetes Fixture kann die Einstellung des Basis-Fixtures überschreiben, aber es wirkt sich nur auf die Einstellung für jeden Testfall aus, der von der abgeleiteten Klasse erzeugt wird.

Oder:

```c#
[Assembly: DefaultFixtureOptions(InstancePerTestCase = true)]

// Warum sollte dies nicht auch als Standard angesehen werden?
[DefaultFixtureOptions(InstancePerTestCase = true)]
öffentliche abstrakte Klasse BaseFixture
{
}

öffentliche versiegelte Klasse SomeFixture : BaseFixture
{
}
```

Wie finden wir eine Begründung dafür, wann wir Default und wann nicht?

Ich denke, die Implementierung dieses Attributs in jedem Testfall würde viel Komplexität mit nur geringem Nutzen bringen.

Bei "normalen" Test-Fixtures werden wir die Instanz weiterhin als Teil der mit dem Test-Fixture verknüpften Befehlskette instanziieren. Für Instanz-pro-Test-Fixtures werden wir es dort nicht instanziieren, sondern müssen einen neuen Befehl als Teil der Testkette implementieren, der so ziemlich das gleiche macht, was jetzt in der Fixture-Kette gemacht wird.

Wenn ein Fixture beide Arten von Tests enthalten kann, müssen wir ihn zweimal ausführen, einmal für jedes Modell.

Der Vorteil scheint gering, da der Benutzer das gleiche leicht erreichen kann, indem er Tests in separate Fixtures aufteilt. Fixtures sind schließlich ein NUnit-Artefakt und es ist vernünftig, von Benutzern zu verlangen, Tests, die das gleiche Fixture-Verhalten benötigen, in demselben Fixture zu platzieren.

In Bezug auf die Verwendung von "Standard" ... es scheint mir, dass Sie es __nicht__ auf jeder TestFixxture verwenden würden und Sie es nur auf Assembly-Ebene verwenden würden. Basisklassen sind keine Ausnahme, da das Attribut aufgrund der Vererbung tatsächlich in der abgeleiteten Klasse gefunden wird. Das Einstellen des bestimmten Verhaltens auf der Basis ist also kein Standard. Es ist vielmehr die Einstellung für diese Klasse und alle davon abgeleiteten. Widersprüchliche Angaben sollten entweder einen Fehler verursachen oder ignoriert werden, wie wir es derzeit in allen Fällen von vererbten Attributen tun. Das heißt, das Platzieren von Attribut A auf der Basisklasse und B auf der abgeleiteten Klasse unterscheidet sich nicht vom Platzieren von A und B direkt auf der Basisklasse. Wir haben es so entworfen, weil es für den Benutzer überraschend wäre, etwas anderes zu tun.

WRT-Setup-Fixtures, übrigens, ich denke, es sollte einen Laufzeitfehler erzeugen, wenn dort eine der beiden Instanziierungsoptionen angegeben wird. SetUpFixtures sind keine TestFixtures.

Das heißt, das Platzieren von Attribut A auf der Basisklasse und B auf der abgeleiteten Klasse unterscheidet sich nicht vom Platzieren von A und B direkt auf der Basisklasse. Wir haben es so entworfen, weil es für den Benutzer überraschend wäre, etwas anderes zu tun.

Oh je, ich hätte erwartet, dass [Apartment] oder [Parallelizable] die Einstellung der Basisklasse oder Basismethode ersetzen!

Für mich macht das alles Sinn.

Können wir eine Aufzählung wie FixtureCreationOptions ? PerTestCase , PerFixture , hypothetisch ReusedPerTestCase ?

Ich tendiere zur Einfachheit von:

```c#
[Assembly: DefaultFixtureOptions(InstancePerTestCase = true)]

[FixtureOptions(InstancePerTestCase = false)]
Klasse SomeFixture
{
}

Hypothetical other creation options would then be added this way:
```c#
[FixtureOptions(InstancePerTestCase = false, ReuseInstances = true)]

@nunit/framework-team Was sind deine Vorlieben?

Bezüglich

[assembly: DefaultFixtureOptions(InstancePerTestCase = true)]

Vielleicht nur ich, aber im Allgemeinen verstehe ich Attribute als Hinweis darauf, dass das markierte Objekt ein bestimmtes Merkmal besitzt, das sonst nicht vorhanden ist; Argumente für das Attribut, falls vorhanden, verfeinern das Merkmal. Nehmen Sie zum Beispiel Serializable - das Objekt ist serialisierbar, aber nicht dekoriert, ist es nicht; InternalsVisibleTo – Interne, die für etwas Bestimmtes sichtbar sind, und die Argumente verfeinern dies, ansonsten sind internal für nichts sichtbar usw. Einige Attribute folgen nicht dem Muster, aber diese sind normalerweise auf niedriger Ebene, z. B. CompilationRepresentation, MethodImpl. Diese werden normalerweise mit erforderlichen Konstruktorargumenten geliefert.

Überlege jetzt
c# [assembly: DefaultFixtureOptions]
Dies ist ein wohlgeformtes C#, da die Zuweisung zu InstancePerTestCase optional ist. Was drückt es semantisch aus?

Für mich erscheint [assembly: DefaultFixtureInstancePerTestCase] natürlicher (mit Ausnahme des sesquipedalischen Namens, aber das ist nur eine Meinung, die außer ästhetisch nicht gerechtfertigt ist). Ein Attribut, eine Eigenschaft. Vielleicht benötigt dieser keine Parameter oder optional einstellbare Eigenschaften.

Außerdem finde ich es als Benutzer überraschend, dass es zwei Attribute mit unterschiedlichen Namen gibt, eines gilt nur auf Baugruppenebene ( DefaultSomething ) und ein anderes nur auf Typebene (nur Something ). . Natürlich verstehe ich nicht, was die "gelernte Lektion" war, die @CharliePoole in Bezug auf das parallelisierbare Attribut erwähnt hat , aber von dieser Seite der Insel aus scheint ein Paar von Attributen zu lernen, wo man (in der oberflächlichen Denkweise dieses Benutzers) tun würde verwirrend.

@kkm000 Ich stimme Ihrem Kommentar zu einem Attribut zu, das ein einzelnes Merkmal ausdrückt.

WRT zwei verschiedene Attribute für das "gleiche" Merkmal auf verschiedenen Ebenen, ich __würde__ zustimmen, wenn ich dachte, sie wären tatsächlich gleich. Aber ich glaube nicht, dass sie es sind. Ich werde Timeout für ein Beispiel verwenden.

Wenn Timeout auf eine Testmethode gesetzt wird, bedeutet dies "Diese Testmethode wird ablaufen, wenn die Ausführung länger als die angegebene Anzahl von Millisekunden dauert." Wenn Timeout jedoch auf einem Fixture oder einer Baugruppe platziert wird, hat es eine andere Bedeutung: "Das Standard-Timeout für alle in diesem Element enthaltenen Testfälle liegt bei der angegebenen Anzahl von Millisekunden." Dieser Standard kann natürlich auf niedrigeren Ebenen überschrieben werden. Zwei ziemlich unterschiedliche Bedeutungen, die übrigens zwei verschiedene Implementierungen haben. Der erste wendet das Timeout auf den Test an, auf dem es erscheint. Der zweite speichert den Standardwert im Testkontext, wo er gefunden und als Standardwert verwendet wird, wenn enthaltene Testfälle gefunden werden. ParallelizableAttribute funktioniert ähnlich.

In diesem speziellen Fall, auf einem Fixture platziert, bedeutet das Attribut "Instanziiere dieses Fixture einmal pro Testfall". Auf Baugruppenebene bedeutet dies „Sofern nicht anders angegeben, Testvorrichtungen in dieser Baugruppe einmal pro Vorrichtung instanziieren“.

In diesem speziellen Fall, auf einem Fixture platziert, bedeutet das Attribut "Instanziiere dieses Fixture einmal pro Testfall". Auf Baugruppenebene bedeutet dies „Sofern nicht anders angegeben, Testvorrichtungen in dieser Baugruppe einmal pro Vorrichtung instanziieren“.

Angenommen, Sie haben sich im letzten Satz falsch ausgesprochen - war _"Instantiate Test Fixtures in this Assembly einmal pro ~fixture~ Testfall, sofern nicht anders angegeben."_ beabsichtigt? Oder verstehe ich den Umfang und die Absicht ernsthaft falsch?

Angenommen, ich bin es nicht, für mich als Benutzer sieht die Semantik überhaupt nicht anders aus: „Instanziiere eine Klasse pro Test innerhalb des durch das Attribut markierten Geltungsbereichs “, vielleicht mit „es sei denn, ihr innerer Geltungsbereich ist anders gekennzeichnet, falls vorhanden“ ist in der Tat so ein innerer Bereich“, sei es eine Vorrichtung oder die Baugruppe. Nur meine Meinung, aber zwei Attribute sehen etwas verwirrend aus. Vielleicht ist es nur meine Denkweise, ich weiß es nicht wirklich.

zwei separate Implementierungen

In der Gedankenwelt der Einhörner und Regenbogen wirkt sich die Implementierung nicht auf die Benutzerseite aus. Aber wir (sowohl Sie der NUnit-Designer als auch ich einer seiner Benutzer) sind alle Ingenieure, und bei der Entwicklung geht es immer um Kompromisse. Wenn die Verwendung eines Attributs für verschiedene Bereiche tatsächlich zu internem Klugheit und Wartungsaufwand führen würde, würde ich überhaupt nicht meckern. :)

Letztendlich ist dies für mich eine Nebensache, und ich habe kein starkes Gefühl für das gleiche Attribut und kann damit leben, dass die Attribute so oder so angeordnet sind.

@kkm000 Ja, ich wollte "einmal pro Testfall".

Ich stimme zu, dass dies alles absolut sinnvoll ist, wenn Sie es sich als eine Reihe verschachtelter Bereiche vorstellen. Die Erfahrung zeigt jedoch, dass Benutzer entweder (a) nicht immer so denken oder (b) die Definition des Geltungsbereichs nicht richtig verstehen.

Betrachten Sie als Beispiel für letzteres verschachtelte Klassen und Basisklassen. Keiner von ihnen wird von NUnit zu Testzwecken als verschachtelte "Bereiche" behandelt, aber sie können Benutzern so erscheinen.

Ich gebe zu, dass das von mir erwähnte Problem mit Timeout / DefaultTimeout etwas schwerwiegender ist, da Timeout auf einem Gerät leicht bedeuten kann, dass das Timeout für das gesamte Gerät gilt, was natürlich nicht der Fall ist. Aus diesem Grund gilt das Timeout für die Assembly möglicherweise für die gesamte Assembly, was nicht der Fall ist.

In diesem Fall scheint es weniger Mehrdeutigkeit zu geben, da wir im Namen "pro Testfall" verwenden, sodass ich leicht in beide Richtungen gehen kann. Ich arbeite sowieso nicht daran. :Lächeln:

Denken Sie darüber nach, wenn Timeout TestCaseTimeout genannt worden wäre, wäre das besser gewesen!

Ich bin mit einem einzigen [FixtureInstancePerTestCase] zufrieden. Scheint die geringste psychische Belastung für die Benutzer zu sein.

@nunit/framework-team Können Sie uns helfen, eine Richtung festzulegen, indem Sie von https://github.com/nunit/nunit/issues/2574#issuecomment -426347735 nach unten blättern?

[FixtureInstancePerTestCase] ist auch meine am wenigsten unbeliebte Option. 😄 Einziges Manko ist die fehlende Erweiterbarkeit.

Ich mag das [FixtureOptions] Problem nicht - ich finde das zu verwirrend. Ich persönlich würde ein einzelnes Attribut sowohl für Assembly als auch für Klasse bevorzugen - ich weiß zu schätzen, dass uns das schon früher Probleme mit Timeout und Paraleliizable verursacht hat - aber wenn dieses "Fixture" im Namen hat, erscheint es mir weniger zweideutig.

Ich persönlich zitiere gerne die Aufzählungsoption ... außer ich kann mir nicht vorstellen, wie ich sie angemessen benennen soll, um sie prägnanter zu machen!

Gibt es dazu ein Update?

Alle Updates werden zu dieser Ausgabe veröffentlicht.

Kann bitte jemand dieses Problem lösen??
Derzeit müssen wir unseren gesamten Testcode wie folgt verpacken:
csharp [Test] public void TestExample(){ using(var tc = new MyTestContext()){ //code goes here } }
Mit dieser Funktion können wir den Testkontext beim Einrichten initialisieren und beim Abbauen verwerfen lassen und trotzdem Thread-sicher sein.

Hallo, wir haben dieses Problem auch in unserem Unternehmen. Wir migrieren von einem Framework, das diese Funktion unterstützt (Py.Test), daher ist dies ein bisschen ein Problem. Wir würden uns sehr freuen, wenn Sie uns den Quellcode zeigen könnten, der diese Instanziierung steuert, damit wir ihn forken und die notwendigen Änderungen vornehmen können.

@MChiciak
Wenn dieses Problem nicht bald behoben wird, ziehen wir eine Migration auf xUnit in Betracht, da dies in xUnit das Standardverhalten ist: "xUnit.net erstellt für jeden ausgeführten Test eine neue Instanz der Testklasse "

@LirazShay , @MChiciak Ich bin auch auf dieses Problem gestoßen , stimme aber den Punkten von @CharliePoole zu . Also habe ich meine testspezifischen Felder erstellt, die bei SetUp [ThreadStatic] initialisiert werden und es funktioniert gut.

@dfal
Danke für den netten Workaround!

Ich würde das lieben. Ich habe derzeit eine vorhandene Testsuite mit Tausenden von Tests, die auf Nunit ausgeführt werden, die aufgrund des gemeinsamen Status nicht einfach parallelisiert werden können. Wenn diese Option bereitgestellt würde, könnten wir unsere Testsuite viel einfacher parallelisieren.

Stellen Sie sich als Gedankenexperiment vor, wir hätten einfach die Funktionsweise von TestFixture geändert. Was würde das kaputt machen?

  1. Alle Tests, die eine Reihenfolge verwenden und sich auf Zustandsänderungen eines Tests verlassen, um andere Tests zu beeinflussen. Dies ist bei (angeblich) unabhängigen Unit-Tests eine sehr schlechte Praxis. Wir haben die Leute immer davor gewarnt, dies zu tun, und es würde mir nichts ausmachen, Tests zu brechen (natürlich mit einer Nachricht), die auf einer einzigen Instanz auf diese Weise beruhten.

  2. Je nachdem, was innerhalb des einmaligen Setups gemacht wird, ist es möglich, dass die einzelnen Fixture-Instanzen nicht parallel laufen können. Das ist ironisch, da die Motivation für die Änderung darin besteht, die Scheinwerfer parallel laufen zu lassen! Dies ist kein Breaking Change für Leute, die das Feature benötigen, da sie bereits nicht in der Lage sind, parallel zu laufen, aber es ist eine signifikante Breaking Change für diejenigen, die bereits darauf angewiesen sind, die Tests __nach__ einem nicht parallelisierbaren `OneTimeSetUp' parallel auszuführen.

  3. Jeder OneTimeSetUp Fixture-Ebene würde nun mehrmals ausgeführt. Dies würde jegliche Effizienzgewinne aus dem einmaligen Setup eliminieren und könnte in einigen Fällen die Semantik ändern. Insbesondere wenn die Initialisierung den globalen Zustand in irgendeiner Weise beeinflusst (zB beim Einrichten einer Datenbank), kann dies zu Fehlern führen. Dieser hier scheint mir ein inakzeptabler Bruch zu sein.

Ich fand dieses Gedankenexperiment hilfreich, um darüber nachzudenken, wie die Instanziierung von Fixtures pro Testfall implementiert werden kann. Es muss eindeutig ein neues, nicht standardmäßiges Feature sein, wenn es in NUnit 3 eingeführt wird. Für NUnit 4 oder jedes andere neue Framework (zB TestCentric) könnte das neue Verhalten leicht das Standardverhalten sein.

Als Anmerkung, ich habe sehr einfach einige MSTests TestClass und TestMethod durch TestFixture bzw. Test ersetzt und festgestellt, dass NUnit und MSTest hier unterschiedliche Semantiken haben. MSTest erstellt für jeden Test neue Instanzen, wobei NUnit die TestFixture-Instanzen wiederverwendet, wie mir sicher jeder, der dies liest, bewusst ist.

Es wäre schön, wenn ich TestFixture(lifeCycle = OneTestPerInstance)] oder ähnliches schreiben könnte, um diese Migration zu erleichtern.

_edit:_ Nachdem die Tests gerade portiert wurden, um eine Setup Methode zu verwenden, stellte sich tatsächlich heraus, dass der vorhandene Code-Initialisierungs-/Vorher-/Teardown-Code unter MSTest ziemlich gequält wurde und jetzt einfache Eigenschaftsinitialisierer-Konstruktoren ist, die ist viel schöner und vollständiger, daher bin ich froh, dass ich die Übersetzung gemacht habe. Aus Kompatibilitätsgründen wäre es dennoch wünschenswert.

@CharliePoole

Alle Tests, die eine Reihenfolge verwenden und sich auf Zustandsänderungen eines Tests verlassen, um andere Tests zu beeinflussen.

Aah, ja, brechen, brechen diese Tests ab, bitte schön!!! 🤣 Das ist der schlimmste Missbrauch eines Unit-Testing-Frameworks, den ich mir vorstellen kann.

Jedes Fixture-Level OneTimeSetUp

Äh, ich habe immer gedacht, dass dies einmal pro Prozess ausgeführt wird ... Ich habe mich immer gefragt, warum es nicht statisch ist.

Aber ja, Leistung ist auch ein sehr gültiger Punkt.

Dieses Feature wird jetzt schon seit über 2 Jahren diskutiert, ich möchte nicht beleidigend klingen, aber gibt es konkrete und realistische Pläne, es in naher Zukunft tatsächlich zu implementieren?

Ich würde gerne wissen, ob mein Team eine Migration auf ein anderes Framework planen sollte oder dies in den kommenden Monaten gelöst wird, denn dieses Thema ist für uns ein Showstopper.

Dies ist eine wesentliche Änderung in der Art und Weise, wie Tests ausgeführt werden, und müsste die volle Auswirkung berücksichtigen, um diejenigen, die von der aktuellen Funktionalität abhängig sind, nicht vollständig zu zerstören.

Der beste Weg, dies zu "lösen", besteht darin, Ihren Testklassen-Thread sicher zu machen. Wie bei jeder guten Multithread-Anwendung sollte die Thread-Sicherheit an erster Stelle stehen.
Wenn Sie beispielsweise Felder verwenden, die Sie in einer Setup-Methode einrichten und dann in vielen Testmethoden verwenden, ist Ihre Klasse nicht threadsicher. das ist kein Fehler von NUnit. Sie haben ein nicht threadsicheres Singleton geschrieben.

Indem Sie eine Unterklasse in Ihrem TestFixture erstellen, die kontextbezogene Informationen enthält, die Sie als Teil jedes Tests erstellen, stellen Sie dann sicher, dass Ihr Singleton-TestFixture threadsicher ist ... also ... "lösen" Sie das Problem.
Anstatt also Zeit mit der "Migration zu einem anderen Framework" zu verbringen, könnte ich vorschlagen, dass Sie diese Zeit damit verbringen, Ihre Singleton-TestFixtures threadsicher zu machen ... Sie werden dies wahrscheinlich als viel schnellere Übung empfinden.

Als ich zum Beispiel zum ersten Mal auf die Tatsache stieß, dass ein NUnit TestFixture ein Singleton ist, habe ich meine über 100 Testadapter mit über 500 Tests auf threadsicher umgestellt (und musste von RhinoMocks zu Moq wechseln, da RhinoMocks auch nicht threadsicher ist) und das dauerte mir ungefähr 4 Wochen (um ehrlich zu sein, Rhino -> Moq hat die meiste Zeit gebraucht!)

Sie haben im Prinzip 100% Recht, aber wenn Sie eine vorhandene Testsuite mit mehr als 7000 Unit-Tests haben, ist es eine große Investition, sie einfach umzuschreiben, damit sie Thread-sicher sind.

Ich bin mir nicht sicher, ob es eine größere Investition ist als der Wechsel zu einem anderen Framework (möglicherweise gibt es ein Tool, um das zu automatisieren), aber ich denke nicht, dass diese Funktionsanfrage grundsätzlich verworfen werden sollte, IMO

Ich befürworte nicht, dass diese Funktion nicht betrachtet werden sollte.

Aber eine so große Änderung in der Funktionsweise von Nunit sollte meiner Meinung nach entweder wegen der Abwärtskompatibilität (was ziemlich schwierig sein kann) oder der neuen Hauptversion (auch keine kleine Aufgabe) in Betracht gezogen werden.

Ich schlage vor, dass es eine einfachere Form ist, den Code zu einem Benutzer von Nunit zu machen, der threadsafe schreibt, um in einer Multi-Thread-Umgebung ausgeführt zu werden. Sie könnten das möglicherweise sogar skripten.

Ich bin kein Mitwirkender an diesem Repo, daher habe ich keinen Kontext zur Implementierung. Dies mag deshalb unglaublich naiv sein, aber wäre es nicht möglich, dies als optionalen Schalter zu implementieren? Damit das nichts kaputt macht?

In diesem ganzen Thema werden verschiedene Möglichkeiten diskutiert, wie ein optionaler Schalter implementiert werden könnte ... es gibt viele Möglichkeiten

Dies ist ein Open-Source-Projekt, das ausschließlich von Freiwilligen betrieben wird. Sobald eine Funktion akzeptiert wurde, wartet sie darauf, dass jemand aus dem Team oder von außen sie aufnimmt und erledigt. Es gibt keinen "Chef", der irgendjemandem die Arbeit zuweist. Es ist eigentlich eine gute Sache, dass das niemand getan hat, wenn er nicht die Zeit hat, sich dem zu widmen. Auf diese Weise steht es jedem zur Verfügung, der dazu in der Lage und interessiert ist.

PRs sind immer willkommen, aber dies ist wahrscheinlich kein erster Beitrag für jemanden, da er das Potenzial hat, viele andere Dinge in NUnit zu zerstören. Meine Meinung jedenfalls.

Ich trage nicht mehr so ​​aktiv bei wie in der Vergangenheit, daher wurden meine Kommentare, wie es meiner Meinung nach gemacht werden sollte, als Ratschlag für denjenigen gegeben, der es in die Hand nimmt.

Ich stimme zu, dass es es schon eine Weile gibt und ich würde es gerne selbst sehen.

@BlythMeister Ich denke, ein Schalter vereinfacht es ein wenig - wie Sie wahrscheinlich wissen, andere jedoch möglicherweise nicht.

Sicher, es ist ein Schalter, aber es ist nicht nur das Einschalten einer Glühbirne. Es ist eher so, als ob Sie Ihr Radio von AM auf FM umschalten würden ... völlig neue Schaltungen kommen ins Spiel.

In diesem Fall handelt es sich um einen ganz neuen Lebenszyklus bei der Ausführung eines Tests, sodass eine ganze Menge Code ersetzt werden muss. Keine Kleinigkeit. Das Risiko besteht darin, sicherzustellen, dass die neue "Schaltung" die alte in keiner Weise beeinträchtigt.

Entschuldigen Sie Ihr Recht, meine Erklärung war vage, warum ein "Umschalten" nicht einfach ist!

@BlythMeister Ich habe tatsächlich genau das getan, was Sie beschreiben - ein

Es ist nützlich, daran zu denken, dass TearDown nicht dafür gedacht ist, Dinge mit dem Testergebnis zu tun. Natürlich weiß ich, dass viele Leute es so verwenden und ich verstehe die Motivation, aber es gibt viele knifflige Eckfälle, die im Wesentlichen darauf zurückzuführen sind, dass TearDown Teil des Tests ist und erst dann vorbei ist, wenn er vorbei ist.

Mit NUnit 3 hatten wir gehofft, dass mehr Benutzer Ergebnisse außerhalb des Frameworks mit Hilfe von Engine-Erweiterungen untersuchen, aber die Arbeit innerhalb des Tests selbst scheint vertrauter und zugänglicher zu sein.

@CharliePoole

  1. Jeder OneTimeSetUp Fixture-Ebene würde nun mehrmals ausgeführt. Dies würde jegliche Effizienzgewinne aus dem einmaligen Setup eliminieren und könnte in einigen Fällen die Semantik ändern. Insbesondere wenn die Initialisierung den globalen Zustand in irgendeiner Weise beeinflusst (zB beim Einrichten einer Datenbank), kann dies zu Fehlern führen. Dieser hier scheint mir ein inakzeptabler Bruch zu sein.

Warum können wir die Instanz-pro-Testfall-Funktion nicht implementieren und das OneTimeSetUp trotzdem nur einmal ausführen (lassen Sie den Hauptthread daran denken, es aufzurufen oder so)?

Weil es jedes vorhandene OneTimeSetUp zerstören würde, das die Instanz initialisiert hat, was sehr häufig vorkommt.

Ich sehe dies als ein Problem bei jeder Switch-basierten Lösung. Es ist weniger problematisch, wenn wir eine völlig neue Alternative zum Testgerät definieren, ein Ansatz, den ich sehr verlockend finde.

Weil es jedes vorhandene OneTimeSetUp zerstören würde, das die Instanz initialisiert hat, was sehr häufig vorkommt.

Wie wäre es damit, nur statische OneTimeSetUp und OneTimeTearDown Methoden zuzulassen, wenn die Funktion "Instanz pro Testfall" aktiviert ist? Das würde dazu führen, dass Member, die innerhalb dieser Methoden initialisiert werden, statisch sind und somit offensichtlich von Fixture-Instanzen geteilt werden.

Während die Hauptmotivation dieses Github-Problems die Parallelisierung ist, möchte ich auch das _Prinzip der geringsten Überraschung_ und die _Vermeidung von Fehlern durch sich gegenseitig beeinflussende Tests_ als Motivationen einbringen. (Entschuldigung, wenn ich übersehen habe, dass dies bereits jemand anderes erwähnt hat.)

Falls Sie in diesem ziemlich alten Thread nicht weit genug zurückgelesen haben, meine Kommentare sprechen nicht gegen das Feature, sondern gegen eine bestimmte Implementierung, die vorgeschlagen wurde. Ich bevorzuge es, TestFixture durch eine völlig neue Art von Fixture zu ersetzen, die anders funktioniert.

Ich denke, das Prinzip der geringsten Überraschung funktioniert hier gut. Es überrascht nicht, wenn ein neues Gerät anders funktioniert.

Wenn wir andererseits ein Flag erstellen, das das vorhandene Gerät steuert, fallen mir fünf oder sechs verschiedene Stellen in der aktuellen Implementierung ein, an denen wir auf dieses Flag verzweigen müssen.

Das soll nicht heißen, dass die beiden Fixture-Typen keinen bestimmten Code teilen. Aber das wäre außerhalb der Sicht der Benutzer.

@fschmied Übrigens , wenn wir ein neues Framework (oder vielleicht sogar NUnit 4) machen würden, würde ich anders argumentieren ... Ich denke, wir hätten es den Tests in NUnit 2 und 3 viel schwerer machen sollen, sich gegenseitig zu stören , solange wir Änderungen in NUnit 3 vornehmen, denke ich, dass Kompatibilitätsregeln gelten.

Ich bevorzuge es, TestFixture durch eine völlig neue Art von Fixture zu ersetzen, die anders funktioniert.
[…]
Ich denke, das Prinzip der geringsten Überraschung funktioniert hier gut. Es überrascht nicht, wenn ein neues Gerät anders funktioniert.

Du hast natürlich recht. Was ich meinte, ist, dass das Verhalten bei der ursprünglichen Art von Testgerät für die meisten Leute immer noch überraschend sein wird. (Zufällig überraschte es einen Kollegen vor zwei Wochen, als er einen Fall fand, in dem ein Test einen anderen beeinflusste. Er verwendet NUnit seit 2013, weshalb ich dieses Problem nachgeschlagen habe.)

Aber natürlich ist es ein Kompromiss zwischen Abwärtskompatibilität, Implementierungskomplexität und dem Wunsch, eine Erbsünde zu korrigieren [1].

Zurück zum Thema OneTimeSetUp : Ich denke, es ist immer noch wichtig, OneTimeSetUp pro Test-Fixture zu haben, auch wenn die Test-Fixture-Klasse pro Test instanziiert wird. Um Verwirrung zu vermeiden, falls OneTimeSetUp Instanzstatus manipuliert, würde ich vorschlagen, OneTimeSetUp (und OneTimeTearDown ) mit der Instanz-pro-Test-Funktion als statisch zu erzwingen. (Egal ob als Schalter oder als neuartige Prüfvorrichtung implementiert.)


[1] James Newkirk schrieb dies :

Ich denke, einer der größten Fehler, die wir beim Schreiben von NUnit V2.0 gemacht haben, bestand darin, nicht für jede enthaltene Testmethode eine neue Instanz der Test-Fixture-Klasse zu erstellen. Ich sage "wir", aber ich denke, das war meine Schuld. Ich habe die Begründung in JUnit zum Erstellen einer neuen Instanz des Testgeräts für jede Testmethode nicht ganz verstanden.

Er schreibt aber auch dies:

[...] es schwierig wäre, die Arbeitsweise von NUnit zu ändern, würden sich zu viele Leute [...]

:)

Ja, Jim und ich haben darüber jahrelang gestritten. :smile: Eine Sache, in der wir uns einig sind, ist der letzte Satz. Jim machte sich daran, ein neues Framework zu erstellen, bevor er seinen bevorzugten Ansatz umsetzte. Ich mache im Grunde dasselbe und SetUp/TearDown ist eine Sache, die ich wahrscheinlich anders machen werde.

Ich bin immer wieder überrascht, wie viele ziemlich erfahrene NUnit-Benutzer nicht wissen, dass für alle Testfälle dieselbe Instanz verwendet wird. Es kann sein, dass es bekannter war, als NUnit zum ersten Mal herauskam, weil es ein offensichtlicher Unterschied zu JUnit war. Jetzt ist das alles vergessen und neue Leute kommen zu NUnit mit Erwartungen, die NUnit nicht erfüllen. Etwas zum Nachdenken.

also .. ist das zu gewinnen? oder noch in der Designphase?

jetzt in .NET entweder XUnit ohne TestContext oder NUnit, mit parallelen Boilerplate-Tests

Das Merkmal wird akzeptiert und erhält eine normale Priorität. Es ist niemand zugeordnet und es ist nicht auf einem Meilenstein aufgeführt. Das bedeutet, dass jemand entscheiden muss, dass er daran arbeiten möchte. Das kann ein Committer oder ein Contributor sein.

Normalerweise führen wir keine Designphase durch - wenn wir wollen, wird das Designlabel aufgebracht - aber wer dies tut, ist gut beraten, einen Kommentar zu hinterlassen, der beschreibt, wie er es umsetzen möchte, insbesondere wie es aussehen wird Benutzer. Wenn Sie mit der Codierung beginnen würden, könnten Sie sowieso in Designdiskussionen enden, daher ist es wahrscheinlich am besten, dies zuerst hinter sich zu bringen.

Sie möchten also daran arbeiten?

Klar, ich habe gerade angefangen zu arbeiten bei
https://github.com/avilv/nunit/tree/instance-per-test

Ich bin neu in dieser Codebasis, also versuche ich, so weit wie möglich am ursprünglichen Codestil zu bleiben
Bisher habe ich ein InstancePerTestCase- Attribut eingeführt und plane, es auf Assembly- und Klassenebene zu unterstützen

Lass es mich wissen, wenn ich hier weit weg von der Basis bin

Ich habe Kommentare zu einigen Implementierungsdetails zu Ihrem letzten Commit hinzugefügt.

Was das Design angeht, finde ich ein von IApplyToContext abgeleitetes Attribut sinnvoll. Ich denke, ich würde etwas Allgemeineres bevorzugen als nur InstancePerTestCase, vielleicht

C# [FixtureLifeCycle(LifeCycle.InstancePerTestCase]

Dadurch können Sie es auf Baugruppenebene einstellen und auf einzelnen Geräten zurücksetzen.

CharliePoole du bist zu viel, vielen Dank für das Feedback und die Hilfe

ich glaub ich kriege langsam aber sicher hin wie die dinge aufgebaut sind / was macht was etc

hier ist mein Vorschlag für die LifeCycle-Enum (ich habe auch meinen Zweig mit Ihrem Vorschlag aktualisiert)

    /// <summary>
    /// Specifies the lifecycle for a fixture.
    /// </summary>
    public enum LifeCycle
    {
        /// <summary>
        /// A single instance is created and for all test cases.
        /// </summary>
        SingleInstance,

        /// <summary>
        /// A new instance is created for each test case for fixtures that are marked with <see cref="ParallelizableAttribute"/>.
        /// </summary>
        InstancePerTestCaseForParallelFixtures,

        /// <summary>
        /// A new instance is created for each test case.
        /// </summary>
        InstancePerTestCase
    }

@jnm2 Was halten Sie von diesem Ansatz - Sie sind in erster Linie eine Art "Sponsor" dieser Funktion. Bei InstancePerTestCaseForParallelFixtures bin ich mir etwas unsicher, was für eine erste Implementierung vielleicht etwas zu viel ist. Wie üblich, einmal implementiert, bleiben wir dabei.

Wenn wir die Parallelität __do__ akzeptieren, muss sie kontextbasiert und nicht attributbasiert sein. Das heißt, jeder Test kann parallel ausgeführt werden, also ist er eher wie `InstancePerTestCaseWhenCasesAreParallel. Parallele Geräte benötigen diese Funktion nicht besonders. Und vielleicht ist es besser, dem Benutzer zu überlassen, zu entscheiden.

Ich stimme auch zu, dass es in keiner Weise mit Parallelität gekoppelt werden sollte. Parallelität war der Grund, dieses Feature hinzuzufügen, aber sobald wir uns einig sind, dass dieses Feature nützlich ist, sollte es meiner Meinung nach nicht im Code widergespiegelt werden

Ich stimme zu, ich verstehe den Anwendungsfall für die Verwendung nur in parallelen Szenarien, da Sie dort das aktuelle Standardmodell von NUnit beißen werden, aber hoffentlich wird dies in Zukunft der Standardlebenszyklus sein (Nunit 4?), da ich denke, dass es wirklich so sein sollte war von anfang an so

vielleicht sollten wir es bei SingleInsance/InstancePerTestCase belassen

Eine andere Frage ist, ob wir hier mit dem IDisposable-Muster umgehen sollten, das SetUp/TearDown ziemlich überflüssig machen würde, sich aber im XUnit-Stil viel natürlicher anfühlen würde.

Wir verfügen bereits über jede Testvorrichtung, die IDisposable implementiert. Sie müssen nur sicherstellen, dass das weiterhin funktioniert.

super, wird gemacht.

Ich werde wahrscheinlich heute mit dem Hinzufügen von Testfällen beginnen, aber da es sich um einen solchen Kernmechanismus handelt, werden möglicherweise einige Fälle hinzugefügt, die nur sicherstellen, dass dasselbe funktioniert, wenn InstancePerTestCase aktiviert ist. Korrigiere mich, wenn ich falsch liege

Der DisposeFixtureCommand kümmert sich um den Aufruf von Dispose.

IMO besteht Ihr Hauptproblem darin, sicherzustellen, dass der gesamte Befehlsstrom für das Testgerät __für jeden Test__ aufgerufen wird. Die Änderungen, die ich bisher sehe, erstellen korrekt einen Befehlsstrom, der die Aufgabe zu erfüllen scheint, aber die Schlüsselfrage ist, wie sichergestellt werden kann, dass er für jeden Testfall aufgerufen wird. (Beachten Sie, dass sich die Befehlsstruktur für Fixtures von der für Testfälle unterscheidet.) Ich denke, dass compositeworkitem kontextsensitiv sein und den Befehl korrekt aufrufen muss (wie Sie es getan haben), aber ich bin mir nicht sicher, ob es eine Chance bekommen wird tun Sie dies für __jeden__ Testfall. (Ich lese nur den Code, also könnte ich mich irren.)

@CharliePoole sieht so aus, als ob Sie Recht haben, ich habe ein wenig grafisch dargestellt, was genau eine Testmethode ausführt:
image

Derzeit erstelle ich die Testobjektinstanz in der RunChildren-Schleife, aber das reicht nicht für Retry/Repeat-Befehle, ganz zu schweigen davon, ob ich andere Wrapper verpasst habe / jemand neue braucht.
es muss viel näher an TestMethodCommand sein

Ich habe die letzten Änderungen aktualisiert auf
https://github.com/avilv/nunit/tree/instance-per-test-2

InstancePerTestCaseWhenCasesAreParallel entfernt
FixtureLifeCycle implementiert, indem das Fixture Construct/Dispose in MakeTestCommand in SimpleWorkItem hinzugefügt und in CompositeWorkItem MakeOneTimeSetUpCommand übersprungen wurde

es scheint gut zu funktionieren, macht auch mehr Sinn, wie das Framework aufgebaut ist
Jetzt muss ich nur noch ein paar Unit-Tests hinzufügen, um sicherzustellen, dass alles richtig funktioniert

Gibt es eine Chance, dass dies vor der nächsten Version überprüft wird? Ich bin immer noch bereit für Verbesserungen/Kursänderungen

Dieser Ansatz klingt für mich gut und ich würde ihn gerne in der nächsten Version verwenden, wenn wir dies tun können!

Besteht die Möglichkeit, dass wir das überprüfen lassen? fühlt sich an, als würden wir uns sehr nahe kommen

Neugierig, ob sich bei dieser Funktion etwas bewegt hat? Scheint so, als ob @avilv es ziemlich nahe gekommen ist. Ich habe schon lange auf eine Funktion wie diese gewartet und freue mich darauf, sie zu nutzen!

Danke an alle!

Funktioniert diese Funktion jetzt?

@CharliePoole könnten Sie bitte die neueste Version von @avilv überprüfen? Es scheint vollständig umgesetzt zu sein. Ich denke, viele Leute warten auf diese Funktion.

@janissimsons Entschuldigung, aber ich bin nicht mehr an diesem Projekt beteiligt, daher würde meine Bewertung es nicht

Schätzen Sie all die Arbeit, die geleistet wurde, um #3427 zusammenzuführen. @rprousen Sie eine Idee, wann wir dies in einer Veröffentlichung erwarten können?

Wie nutzen wir die neue Funktion? Könnte jemand eine kurze Beschreibung zu den neuen Attributen schreiben und wie man sie richtig verwendet?

Ich plane die Veröffentlichung in den nächsten Wochen. Ich wollte warten bis. NET 5 ist endgültig und führen Sie vor der Veröffentlichung ein paar letzte Tests damit durch. Ich bezweifle, dass es irgendwelche Probleme geben wird, aber es ist so nah, dass ich dachte, es wäre besser zu warten.

@rprouse Irgendwelche Schätzungen, wann Sie die neue Version veröffentlichen werden? Ich möchte diese Funktion gerne nutzen.

@janissimsons sehr bald. Wir arbeiten an einigen Problemen mit unseren .NET 5.0-Builds und an einigen anderen Problemen, dann werde ich es veröffentlichen. Sie können den Status auf diesem Projektboard verfolgen, https://github.com/nunit/nunit/projects/4

Das sind wunderbare Neuigkeiten!

Verstehe ich richtig, dass dies in NUnit folgendes ermöglicht?

Kombinieren Sie all dies:

  • Tests sind parametrisiert
  • Führen Sie Tests parallel durch.
  • In der Lage sein, parametrisierte Variationen desselben Tests parallel auszuführen.
  • Tests können datengesteuert sein

Habe in der Vergangenheit hier einiges ausprobiert https://github.com/jawn/dotnet-parallel-parameterized-tests

Wird die neue Version weiterhin mit .NET Full Framework 4.7.2 oder mindestens 4.8 kompatibel sein?

Mit freundlichen Grüßen,
DR

@jawn Ich glaube, das wird alles funktionieren, ja. Ich habe all diese Dinge getestet, aber NUnit hat viele Funktionen, daher kann es bei einigen Kombinationen von Funktionen zu Grenzfällen kommen. Zögern Sie nicht, unser nächtliches NuGet-Paket zu ziehen und es zu testen! Wir würden uns über weitere Tests und Feedback freuen, bevor wir es veröffentlichen.

@drauch ja, die Version 3.13 wird weiterhin .NET 3.5 unterstützen.

@avilv Tolle Arbeit!!
Übrigens mit welchem ​​Tool hast du das schöne Flussdiagramm erstellt?
Vielen Dank

@rprouse Könnten Sie bitte auch die Dokumentation zur Verwendung dieser neuen Funktion aktualisieren? Vielen Dank!

@janissimsons Ich plane, die Dokumente für die Veröffentlichung zu aktualisieren. Die Kurzversion besteht jedoch darin, [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] zu Ihrer Testklasse hinzuzufügen, wahrscheinlich zusammen mit [Parallelizable(ParallelScope.All)] und eine Instanz Ihrer Testklasse wird für jeden Test instanziiert, der innerhalb der Klasse ausgeführt wird. Auf diese Weise können Sie Mitgliedsvariablen in der Klasse in parallelen Testläufen verwenden, ohne sich Sorgen machen zu müssen, dass andere Tests die Variablen ändern.

@LirazShay Das Diagramm im Screenshot wurde in Visual Studio Enterprise erstellt. Sie können im Projektmappen-Explorer mit der rechten Maustaste auf eine Auswahl klicken und beispielsweise "Auf Codezuordnung anzeigen" auswählen. https://docs.microsoft.com/en-us/visualstudio/modeling/map-dependencies-across-your-solutions ReSharper verfügt über eine ähnliche Funktion.

@jnm2 Vielen Dank!!!
Ich habe VS Enterprise und wusste nichts von dieser Funktion, die sehr hilfreich sein kann
vielen dank für den tipp

Liege ich richtig, dass dies veröffentlicht wurde und Teil der Version 3.13 ist?

Liege ich richtig, dass dies veröffentlicht wurde und Teil der Version 3.13 ist?

Ich glaube, es wurden einige Probleme beobachtet und behoben, insbesondere in Bezug auf die Attributanwendung auf Assemblyebene in #3720, und wir warten jetzt auf 3.13.1.

NUnit 3.13.1 wurde veröffentlicht.
Bleiben noch Probleme übrig?

Nein deshalb wurde es geschlossen @andrewlaser

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

DavidKlempfner picture DavidKlempfner  ·  3Kommentare

DustinKingen picture DustinKingen  ·  4Kommentare

Thaina picture Thaina  ·  5Kommentare

TobiasSekan picture TobiasSekan  ·  4Kommentare

ChrisMaddock picture ChrisMaddock  ·  3Kommentare