Effektiv Autotests schreiben - Subkutane Tests

Stellen wir uns eine hypothetische Situation vor (in die wir regelmäßig eintauchen). Sie wurden einem Projekt zur "Gash" -Automatisierung zugewiesen. Sie erhalten einen riesigen Testplan mit einer großen Anzahl (Tausende von ihnen!) Von „manuellen“ Tests, und sie sagen, dass Sie etwas tun müssen, und zwar genau dort. Und auch zu schnell und stabil.

Es ist zu spät , Unit-Tests zu schreiben oder an TDD zu denken . Der Produktcode wurde schon lange geschrieben. Dein Wort, Genosse Autotester!

Bild

Glücklicherweise gibt es einen kleinen Trick, der die Abdeckung erhöht und die Tests stabil und schnell macht - subkutane Tests („subkutane Tests“), aber das Wichtigste zuerst.


Das Wesentliche des Problems


Der erste bedingte Reflex des Automators ist die Einnahme von Selen (entweder dort, Selenid oder ein anderes Wunderkind für UI-Tests). Dies ist ein solcher Industriestandard, aber es gibt viele Gründe, warum er nicht startet:

  • UI-Tests sind langsam. Es gibt kein Entrinnen davon. Sie können parallel ausgeführt, abgelegt und etwas schneller ausgeführt werden, bleiben jedoch langsam.
  • UI-Tests sind instabil. Zum Teil, weil sie langsam sind. Und auch, weil der Webbrowser und die Benutzeroberfläche nicht für die Steuerung durch einen Computer erstellt wurden (dieser Trend ändert sich derzeit, aber nicht die Tatsache, dass dies gut ist).
  • UI- — . . ( , , , «» - , ).
  • , , , UI- . . ID XPath . , «» - - . , , — - , .
  • Jemand wird sagen, dass einige Funktionen einfach nicht anders getestet werden können. Ich werde sagen, dass wenn es Funktionen gibt, die nur durch UI-Tests getestet werden können (mit Ausnahme der UI-Logik selbst), dies ein gutes Zeichen für Architekturprobleme im Produkt sein kann.

Das einzige wirkliche Plus von UI-Tests ist, dass Sie damit mehr oder weniger nützliche Schecks „werfen“ können, ohne den Code des Produkts selbst tauchen und studieren zu müssen. Was auf lange Sicht kaum ein Plus ist. Eine ausführlichere Erklärung, warum dies zu hören ist, finden Sie in dieser Präsentation .

Alternative Lösung


Als sehr einfachen Fall betrachten wir eine Anwendung, die aus einem Formular besteht, in das Sie einen gültigen Benutzernamen eingeben können. Wenn Sie einen Benutzernamen eingeben, der den Regeln entspricht, wird der Benutzer im System erstellt und in der Datenbank aufgezeichnet.



Den Quellcode der Anwendung finden Sie hier: github.com/senpay/login-form . Sie wurden gewarnt - in der Anwendung gibt es viele Fehler und es gibt keine modischen Tools und Frameworks.

Wenn Sie versuchen, ein Scheckblatt für diese Anwendung zu "werfen", können Sie Folgendes erhalten:
NummerSchritteErwartete Ergebnisse
11. Geben Sie einen gültigen Benutzernamen ein.
2. Klicken Sie auf die Schaltfläche "Anmelden"
1.
2. Ein neuer Benutzer wird erstellt.
21. Geben Sie einen leeren Benutzernamen ein.
2. Klicken Sie auf die Schaltfläche "Anmelden"
1.
2. Die Fehlermeldung wird gegeben.

Sieht es einfach aus Einfach! Kann ich UI-Tests schreiben? Kann. Ein Beispiel für die schriftlichen Tests (zusammen mit einem vollständigen dreistufigen Framework ) finden Sie in LoginFormTest.java, wenn Sie zum Label uitests in git ( git checkout uitests ) gehen:

public class LoginFormTest {

    SelenideMainPage sut = SelenideMainPage.INSTANCE;
    private static final String APPLICATION_URL = "http://localhost:4567/index";

    @BeforeClass
    public static void setUpClass() {
        final String[] args = {};
        Main.main(args);
        Configuration.browser = "firefox";
    }

    @Before
    public void setUp() {
        open(APPLICATION_URL);
    }

    @After
    public void tearDown() {
        close();
    }

    @Test
    public void shouldBeAbleToAddNewUser() {
        sut.setUserName("MyCoolNewUser");
        sut.clickSubmit();
        Assert.assertEquals("Status: user MyCoolNewUser was created", sut.getStatus());
        Assert.assertTrue(sut.getUsers().contains("Name: MyCoolNewUser"));
    }

    @Test
    public void shouldNotBeAbleToAddEmptyUseName() {
        final int numberOfUsersBeforeTheTest = sut.getUsers().size();
        sut.clickSubmit();
        Assert.assertEquals("Status: Login cannot be empty", sut.getStatus());
        Assert.assertEquals(numberOfUsersBeforeTheTest, sut.getUsers().size());
    }
}


Einige Metriken für diesen Code:
Ausführungszeit: ~ 12 Sekunden (12 Sekunden 956 Millisekunden, als ich diese Tests das letzte Mal ausgeführt habe)
Codeabdeckung
Klasse: 100%
Methode: 93,8% (30/32)
Zeile: 97,4% (75/77) )

Nehmen wir nun an, dass funktionale AutoTests auf der Ebene „unmittelbar unter“ der Benutzeroberfläche geschrieben werden können. Diese Technik wird als subkutane Tests bezeichnet („subkutane Tests“ - Tests, die unmittelbar unter dem Niveau der Anzeigelogik liegen) und wurde vor langer Zeit von Martin Fowler vorgeschlagen [ 1 ].

Wenn Leute an Autotests ohne Benutzeroberfläche denken, denken sie oft sofort an REST / SOAP oder seine API. Die API (Application Programming Interface) ist jedoch ein viel umfassenderes Konzept, das sich nicht unbedingt auf HTTP und andere Schwergewichtsprotokolle auswirkt.

Wenn wir einen Produktcode auswählen , können wir etwas Interessantes finden:
public class UserApplication {

    private static IUserRepository repository = new InMemoryUserRepository();
    private static UserService service = new UserService(); {
        service.setUserRepository(repository);
    }

    public Map<String, Object> getUsersList() {
        return getUsersList("N/A");
    }

    public Map<String, Object> addUser(final String username) {
        final String status = service.addUser(username);
        final Map<String, Object> model = getUsersList(status);
        return model;
    }

    private Map<String, Object> getUsersList(String status) {
        final Map<String, Object> model = new HashMap<>();
        model.put("status", status);
        model.put("users", service.getUserInfoList());
        return model;
    }
}


Wenn wir auf eine Benutzeroberfläche klicken, wird eine dieser Methoden aufgerufen oder ein neues Benutzerobjekt hinzugefügt oder eine Liste bereits erstellter Benutzerobjekte zurückgegeben. Was ist, wenn wir diese Methoden direkt anwenden? Immerhin ist dies eine echte API! Und am wichtigsten ist, dass REST und andere APIs nach dem gleichen Prinzip arbeiten - sie nennen eine bestimmte Methode der "Controller-Ebene".

Mit diesen Methoden können wir einen einfacheren und besseren Test schreiben:
public class UserApplicationTest {

    private UserApplication sut;

    @Before
    public void setUp() {
       sut = new UserApplication();
    }

    @Test
    public void shouldBeAbleToAddNewUser() {
        final Map<String, Object> myCoolNewUser = sut.addUser("MyCoolNewUser");
        Assert.assertEquals("user MyCoolNewUser was created", myCoolNewUser.get("status"));
        Assert.assertTrue(((List) myCoolNewUser.get("users")).contains("Name: MyCoolNewUser"));
    }

    @Test
    public void shouldNotBeAbleToAddEmptyUseName() {
        final Map<String, Object> usersBeforeTest = sut.getUsersList();
        final int numberOfUsersBeforeTheTest = ((List) usersBeforeTest.get("users")).size();
        final Map<String, Object> myCoolNewUser = sut.addUser("");
        Assert.assertEquals("Login cannot be empty", myCoolNewUser.get("status"));
        Assert.assertEquals(numberOfUsersBeforeTheTest, ((List) myCoolNewUser.get("users")).size());
    }
}


Dieser Code ist unter den Label- Untertests verfügbar :

git checkout subctests


Versuchen wir, Metriken zu sammeln.
Ausführungszeit: ~ 21 Millisekunden
Codeabdeckung :
Klasse: 77,8%
Methode: 78,1 (30/32)
Zeile: 78,7 (75/77)

Wir haben ein wenig Deckung verloren, aber die Geschwindigkeit der Tests hat sich 600-mal erhöht !!!

Wie wichtig / signifikant ist der Deckungsverlust in diesem Fall? Es kommt auf die Situation an. Wir haben einen kleinen Klebercode verloren, der wichtig sein kann (oder auch nicht) (ich empfehle zu bestimmen, welcher Code als Übung verloren geht).

Rechtfertigt dieser Verlust der Abdeckung die Einführung von Schwergewichtstests auf UI-Ebene? Es kommt auch auf die Situation an. Wir können zum Beispiel:
  • Fügen Sie einen UI-Test hinzu, um den Klebercode zu überprüfen, oder
  • Wenn wir keine häufigen Änderungen am Klebercode erwarten, lassen Sie ihn ohne Autotests oder
  • Wenn wir eine Art „manuelles“ Testen haben, besteht eine große Wahrscheinlichkeit, dass der Tester Probleme mit dem Klebercode bemerkt, oder
  • Lassen Sie sich etwas anderes einfallen (gleicher Einsatz auf den Kanaren)


Zusammenfassend


  • Funktionale Autotests müssen nicht auf UI- oder REST / SOAP-API-Ebene geschrieben werden. Die Verwendung von „subkutanen Tests“ in vielen Situationen testet dieselbe Funktionalität mit höherer Geschwindigkeit und Stabilität.
  • Einer der Nachteile des Ansatzes ist ein gewisser Deckungsverlust.
  • Ein Weg, um einen Verlust der Abdeckung zu vermeiden, ist das „ Feature-Test-Modell “.
  • Aber selbst mit dem Verlust der Abdeckung ist die Erhöhung der Geschwindigkeit und Stabilität signifikant.


Eine englische Version des Artikels finden Sie hier .

All Articles