Schreiben von Tests für Allure-Go / Sudo Null IT News

Hey Habr! Sie erinnern sich vielleicht an mich aus einem früheren Artikel über Verlockung gehen, in der wir die Spitze unserer bescheidenen Errungenschaften erreicht haben. Heute werden wir ein paar Tests von Grund auf neu einwerfen, die Beispiele im Detail analysieren und sehen, was wir am Ende erreicht haben.

Seit unserem letzten Gespräch sind viele Commits durchgesickert. Update 0.5 wurde veröffentlicht, das viele Änderungen brachte, auch in den Schnittstellen, sowie Update 0.6, das Testplan-Unterstützung von TestOps hinzufügte. Weitere Informationen zu Aktualisierungen finden Sie unter Versionshinweise.

Wo anfangen?

Der erste Schritt besteht darin, die Abhängigkeiten zu installieren.

Rufen Sie github.com/ozontech/allure-go/pkg/allure auf. Rufen Sie github.com/ozontech/allure-go/pkg/framework auf

Jetzt müssen wir herausfinden, in welcher konkreten Form wir Tests brauchen. Allure-Go bietet mehrere Optionen zum Speichern und Ausführen von Tests:

  • Runner aus dem Framework/Runner-Paket, mit dem Sie Unit-Tests ausführen können. Sie können mehr in lesen Liesmich,

  • Suite – eine Reihe von Tests, mit denen Sie Tests nach Geschäftslogik oder nach einem gemeinsamen Faktor kombinieren können.

Heute werden wir über die Suite sprechen (sonst, bei Gott, wird uns kein Artikel ausreichen), aber die in diesem Artikel skizzierten Prinzipien gelten gleichermaßen für Tests im Läufer und für unabhängige Tests.

Das Konzept von Strukturen in Form von Testsuiten ist dem Framework entlehnt bezeugen.

Die Testsuite ist ein Analogon der Testklasse von JUnit/TestNG. Die Idee ist einfach: Wir haben ein Objekt, dessen Methoden Tests sind. Beim Ausführen von Tests übergeben wir eine Instanz der Struktur an die ausführende Methode – und diese wiederum führt die Methoden der Struktur als normale Tests aus.

Lassen Sie uns den einfachsten Test schreiben

Wow, das klingt sicherlich nicht so einfach wie die Idee, aber in der Praxis ist alles viel einfacher.

Hier ist eine einfache Beispiel:

Pakettestimport ( “testing” “github.com/ozontech/allure-go/pkg/framework/provider” “github.com/ozontech/allure-go/pkg/framework/suite” ) type MyFirstSuite struct { suite.Suite } func (s *MyFirstSuite) TestMyFirstTest(t provider.T) { } func TestSuiteRunner(t *testing.T) { suite.RunSuite(t, new(MyFirstSuite)) }

Mal sehen, was Salz ist.

type MyFirstSuite struct ist die Struktur, die dazu bestimmt ist, unsere Tests zu speichern. Damit die Struktur als Testsuite verwendet werden kann, muss sie um die Struktur suite.Suite erweitert werden.

func (s *MyFirstSuite) TestMyFirstTest(t provider.T) ist unser Testleerzeichen.

Wichtig! Beachten Sie, dass der Test die provider.T-Schnittstelle als Argument verwendet. Dies ist unser Hauptwerkzeug für die Arbeit mit Tests, wir werden später darauf zurückkommen.

func TestSuiteRunner(t *testing.T) – Testlauffunktion. Da Allure-Go ein Wrapper um die Testbibliothek ist, müssen wir den Testkontext *testing.T abrufen. Für uns ist diese Funktion der Ausgangspunkt für laufende Tests.

suite.RunSuite(t, new(MyFirstSuite)) ist eine Methode, die Suiten ausführt.

Hinweis: Standardmäßig verwendet der Allure-Bericht den Namen der Struktur als Namen der Suite. Sie können die Testsuite jedoch mit der Methode suite.RunNamedSuite ausführen und einen beliebigen Namen der Suite übergeben, der Ihnen am besten gefällt.

Sie können zu Recht sagen: „Freund, Sie sagten, dass dieses Ding ähnlich wie JUnit/TestNG ist. Aber was ist mit Vorher/Nachher-Hooks?“ Darauf werde ich nicht weniger fair antworten: “Bei ihnen ist alles ausgezeichnet.” Erweitern wir unser Beispiel:

Pakettestimport ( “testing” “github.com/ozontech/allure-go/pkg/framework/provider” “github.com/ozontech/allure-go/pkg/framework/suite” ) // Suite-Strukturtyp MyFirstSuite struct { suite.Suite } // Wird einmal ausgelöst, bevor die Suite gestartet wird func (s *MyFirstSuite) BeforeAll(t provider.T) { } // Wird einmal ausgelöst, nachdem alle Tests abgeschlossen sind func (s *MyFirstSuite) AfterAll(t provider. T) { } // Wird jedes Mal ausgelöst, bevor der Test beginnt func (s *MyFirstSuite) BeforeEach(t provider.T) { } // Wird jedes Mal ausgelöst, nachdem der Test beendet ist func (s *MyFirstSuite) AfterEach(t provider.T) { } func (s *MyFirstSuite) TestMyFirstTest(t provider.T) { } func TestSuiteRunner(t *testing.T) { suite.RunSuite(t, new(MyFirstSuite)) }

Sieht bekannt aus, oder?

Es gibt jedoch einige nicht offensichtliche Merkmale unserer BeforeEach-Implementierung:

  1. Wenn Sie einige Daten in der Suite-Struktur in der BeforeEach-Methode initialisieren und Ihre Tests parallel ausgeführt werden, werden Sie höchstwahrscheinlich darauf stoßen Rennzustand. Dies kann beispielsweise durch die Verwendung von Synchronisationstools oder Mutexes oder durch die Initialisierung aller notwendigen Daten in BeforeAll vermieden werden.

  2. In BeforeEach können Sie Tags und Etiketten setzen, die allen Tests für Allure gemeinsam sind. Zum Beispiel:

func (s *MyFirstSuite) BeforeEach(t provider.T) { t.Epic(“My Epic”) t.Feature(“My Feature”) // und so weiter }

Testkontextanbieter.T

Also haben wir den Start der Suiten herausgefunden. Lassen Sie uns nun in die Tests selbst eintauchen und herausfinden, welche Möglichkeiten provider.T uns bietet. Lassen Sie uns zunächst ein paar Behauptungen einwerfen:

func (s *MyFirstSuite) TestMyFirstTest(t provider.T) { test := “test” t.Require().NotNil(test) t.Require().Equal(test, “test”) }

und schau dir den Bericht an:

Wie Sie dem Screenshot entnehmen können, werden beide Behauptungen in unserem Bericht hervorgehoben.

Hinweis: Allure-Go unterstützt das Soft Assert-Muster in Form von t.Assert(). Der Unterschied besteht darin, dass t.Require() den Test sofort verwirft, während t.Assert() ihn bis zum Ende laufen lässt.

Exzellent! Aber ich möchte die Überprüfungen in einem gemeinsamen Schritt (allure.Step) gruppieren, richtig? Die Methode t.WithNewStep(string, provider.StepCtx, …allure.Parameter) hilft uns dabei:

func (s *MyFirstSuite) TestMyFirstTest(t provider.T) { test := “test” t.WithNewStep(“Mein erster Schritt”, func(stepCtx provider.StepCtx) { stepCtx.Require().NotNil(test) stepCtx. Require().Equal(test, “test”) }, allure.NewParameter(“time”, time.Now())) }

Hinweis: provider.StepCtx ist eine Schnittstelle, die provider.T in fast jeder Hinsicht ähnelt. Eine solche Mühe mit der Trennung von Schnittstellen ist erforderlich, um die Verschachtelung von Schritten während der Ausführung zu verfolgen.

Nun, es wäre großartig, wenn wir einige Parameter speichern würden, wie z. B. die Zeit (und warum nicht?).

Mal sehen, was wir am Ende haben:

Ganz andere Sache 🙂

Insgesamt ermöglicht Ihnen provider.T:

  • Allure-Etiketten anbringen (Feature, Epic, Severity, Tag usw.),

  • Markup-Tests in Schritte (Step, WithNewStep, WithNewAsyncStep),

  • Zugriff auf Step-Wrapping-Asserts (Require, Assert),

  • Verwalten Sie das Testverhalten (XSkip, Parallel, Fail usw.).

Hinweis: Die vollständige Liste der Funktionen ist in dem unglaublich stickigen Dokument beschrieben: Anbieter.T und Anbieter.StepCtx.

Was ist mit der Parametrisierung?

Hier gibt es im Moment auch für uns keine schöne Lösung. Lassen Sie uns ein Beispiel basierend auf unserem ersten Test aufschlüsseln:

func (s *MyFirstSuite) TestMySecondTest(t provider.T) { test := “test” testData := []string{“test0”, “test1”, “test2”} für idx, text := range testData { t.Run(text, func(t provider.T) { data := fmt.Sprintf(“%s%d” , test, idx) t.WithNewStep(“Mein erster Schritt”, func(sCtx provider.StepCtx) { sCtx.Require().NotNil(text) sCtx.Require().Equal(data, text) }, allure.NewParameter (“Zeit”, Zeit.Jetzt())) }) } }

Ja, Sie müssen den Kreislauf drehen – es geht noch nicht anders. Aber wir sind es jetzt an einer Lösung arbeiten dieses Problem 🙂

Wie wird es im Bericht aussehen?

Ja, es sieht toll aus! Beachten Sie, dass der Name des übergeordneten Tests als Bezeichnung der Suite für parametrisierte Tests und die Suite als Bezeichnung der ParentSuite dient.

Wichtig: Der Screenshot zeigt TestMySecondTest im Bericht. Um die Berichterstellungsoption für einen bestimmten Test zu deaktivieren, müssen Sie die SkipOnPrint-Methode aus der provider.T-Struktur aufrufen.

Für einen solchen Test wird kein Bericht erstellt. Dies geschieht aus einem wichtigen Grund nicht automatisch: Go unterstützt verschachtelte Tests, und wir möchten diese Funktionalität nicht verlieren.

Vergessen wir nicht XSkip

Fast alle!

Lassen Sie uns nun so etwas wie XSkip analysieren. Stellen Sie sich also die Situation vor: An einem warmen Abend schlürfen Sie einen guten alten Port 777-Bananen-Smoothie und sehen sich zum zwanzigsten Mal den My Little Pony-Videovortrag über C ++ an. Hier erhalten Sie eine Benachrichtigung im Arbeitschat:

%QAName%! Ihr Test ist erneut fehlgeschlagen, wir können ihn nicht einführen.

Und Sie erinnern sich, dass Sie im letzten Sprint einen Fehler für diesen Test eingeführt haben, gestern hat der Entwickler ihn zum Laufen gebracht und versprochen, ihn erst morgen auszurollen. Was tun, um den Jungs zu helfen und diesen Test nicht zu vergessen?

Unser t.XSkip() kommt zur Rettung! Schauen wir uns ein Beispiel an:

func (s *MyFirstSuite) TestMySecondTest(t provider.T) { var test string t.Require().Equal(“test”, test) }

Der Test fiel wie erwartet aus. Was zu tun ist?

func (s *MyFirstSuite) TestXSkip(t provider.T) { var test string t.XSkip() t.Require().Equal(“test”, test) }

Mit etwas Magie wird Ihr Unstable-Test im Falle eines Absturzes automatisch übersprungen und dem Testnamen ein entsprechendes Präfix hinzugefügt.

Hinweis: Dem aufmerksamen Leser ist aufgefallen, dass XSkip dem xfail-Decorator von pytest sehr ähnlich ist. Im Gegensatz zu xfail überspringt XSkip jedoch den Test, anstatt ihn zu begrünen.

Parallelität in Tests

Und wieder kannst du fragen: „Freund! Aber was ist mit Asynchronität? Immerhin hat bezeugen dieses Problem nicht überwunden. Und als Sie das letzte Mal einen faszinierenden Artikel über Allure-Go geschrieben haben, stellte Ihnen die liebenswerte @Tan_tan eine Frage zur Parallelität in Tests, und Sie sagten, dass das Problem nur teilweise gelöst sei. Worauf ich antworte: „Wir haben gewonnen. Dann mit Blut, emotionalem Burnout, Tränen … aber der Sieg lag in unseren Händen. Wir können mit Stolz sagen: Allure-Go unterstützt voll und ganz asynchrone Starts ohne jegliches Aber, Konventionen und ähnliche Hüllen.

Hinweis: dann wie wir das Problem der Parallelität in Tests umgangen haben, verdient einen separaten und sehr ausführlichen Beitrag. Irgendwann erzähle ich es euch auf jeden Fall.

Werfen wir einen Blick auf unsere wunderbaren Suite-Tests und machen sie parallel:

func (s *MyFirstSuite) TestMyFirstTest(t provider.T) { test := “test” t.Parallel() // diese Methode ruft Parallelität auf t.WithNewStep(“My First Step”, func(sCtx provider.StepCtx) { sCtx .Require().NotNil(test) sCtx.Require().Equal(test, “test”) }, allure.NewParameter(“time”, time.Now())) } func (s *MyFirstSuite) TestMySecondTest( t provider.T) { test := “test” t.Parallel() für idx, text := range []string{“test0”, “test1”, “test2”} { t.Run(text, func(t provider.T) { testText = text // Stellen Sie sicher, dass Sie eine lokale Variable für parametrisierte Tests speichern, wenn Sie parallel ausgeführt werden, um dies zu vermeiden Rennbedingungsdaten : = fmt.Sprintf(“%s%d”, test, idx) t.Parallel() t.WithNewStep(“Mein erster Schritt”, func(sCtx provider.StepCtx) { sCtx.Require().NotNil (testText) sCtx .Require().Equal(data, testText) }, allure.NewParameter(“time”, time.Now())) }) } }

Aber das ist nicht alles. Sie können auch t.WithNewAsyncStep verwenden, um asynchrone Schritte auszuführen. Sie werden parallel zum Hauptthread Ihres Tests ausgeführt. Für eine genauere Kontrolle über ihre Ausführung wird empfohlen, sync.WaitGroup oder channel zu verwenden. Wenn Sie sie jedoch vergessen, gilt der Test immer noch nicht als abgeschlossen, bis alle asynchronen Schritte beendet sind, und wartet auf alle asynchronen Prozesse, die schrittweise begonnen werden, um zu beenden.

Schauen wir uns ein Beispiel an:

func (s *MyFirstSuite) TestMyFirstTest(t provider.T) { test := “test” wg := sync.WaitGroup{} // eine WaitGroup initialisieren, um den asynchronen Testlauf zu beobachten t.Parallel() // das ist die Methode das verursacht Parallelität wg .Add(1) // füge eins hinzu, um zu warten delta (wenn delta 0 ist, dann hören wir auf zu warten) t.WithNewAsyncStep(“My First Step”, func(sCtx provider.StepCtx) { defer wg.Done( ) // Vergessen wir nicht, unsere nach Abschluss der Funktion sCtx.Assert().NotNil(test) sCtx.Assert().Equal(test, “test”) freizugeben }, allure.NewParameter(“time “, time.Now())) wg. Wait() // und schließlich warten wir }

Wichtig! Es wird dringend davon abgeraten, t.Require() mit WithNewAsyncStep zu verwenden, da testing.T.FailNow() die Test-Goroutine grob und ohne Umschweife über runtime.goexit schließt, was wiederum alle übergeordneten Goroutinen beendet. Ihre Schrittdaten können verloren gehen. Verwenden Sie t.Assert(), um Verlegenheit zu vermeiden.

Schlussfolgerungen

Für heute habe ich vielleicht alles. Codebeispiele aus dem Artikel können Sie finden Sie hier.

Darüber hinaus möchte ich auf die kürzlich erfolgte Veröffentlichung hinweisen CUTE Bibliothekendas dem Testen von HTTP gewidmet ist, das auf Allure-Go basiert, und ein Artikel darüber von @siller174.

Besser als bei uns neue und schöne Readmeniemand wird Ihnen etwas über die Feinheiten von Allure-Go erzählen.

Beim nächsten Mal werden wir die Durchführung der resultierenden Tests in der Pipeline besprechen, darüber nachdenken, wie wir die Testinfrastruktur verbessern können, und herausfinden, was dafür erforderlich ist.

Vielen Dank für Ihre Aufmerksamkeit, passen Sie auf sich auf, kaufen Sie Gold und bleiben Sie in Kontakt!

Similar Posts

Leave a Reply

Your email address will not be published.