Anmerkung: In diesem Blog nutze ich die informelle Anrede „du“ und gleichzeitig das generische Maskulinum. Dies dient dazu, eine persönliche Atmosphäre zu schaffen, die den Austausch und das Lernen ansprechender macht. Gleichzeitig erleichtert es den Lesefluss. Dennoch gilt natürlich allen Lesern und Leserinnen mein höchster Respekt.

Einführung in Angular

Bei diesem Artikel handel es sich um Teil 2 der Einführung in Angular-Komponenten. Falls du dir vorher die Grundlagen erarbeiten möchtest, sieh dir gerne Einführung in Angular-Komponenten (Part 1) an.


Testen der Komponenten

Nun, da du die grundlegende Mini-Anwendung implementiert hast, ist es an der Zeit, sie zu testen. Bevor wir uns den eigentlichen Integrationstests der Komponenten widmen, zeige ich dir anhand eines kurzen Unittests, wie du diese in Angular grundsätzlich aufsetzen kannst. Dafür werfen wir einen Blick auf die Datei app.component.ts aus dem Repository:
import { Component, OnInit } from "@angular/core";

@Component ({
        selector: "app-root",
        templateUrl: "./app.component.html",
        styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
        readonly title: string;
        postImg: string[];
        postText: string[];
        constructor() {
                this.title = "angular-demo-application";
                this.postImg = [];
                this.postText = [];
        }

        ngOnInit() {
                this.postImg.push("0", "1", "2", "3", "4", "5");
                this.postText.push(
                        "This is a card.",
                        "That is also a card.",
                        "This is anonther card.",
                        "That is the fourth card. ",
                        "This is the fifth card.",
                        "That is the last card."
                );
        }
}

Als einfachen Unittest möchten wir hier den Wert der Eigenschaft title überprüfen. Öffne dafür bitte die Datei app.component.spec.ts, die bisher etwa so aussehen sollte:

import { AppComponent } from "./app.component";
import { CardComponent } from "./card/card.component";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { FriendBoxComponent } from "./friend-box/friend-box.component";
import { HeaderComponent } from "./header/header.component";
import { MatIcon } from "@angular/material/icon";
import { ProfileRowComponent } from "./profile-row/profile-row.component";
import { ProposalsComponent } from "./proposals/proposals.component";

describe("AppComponent", () => {
        let component: AppComponent;
        let fixture: ComponentFixture<AppComponent>;

        beforeEach(() => {
                TestBed.configureTestingModule({
			declarations: [
				AppComponent,
				CardComponent,
				FriendBoxComponent,
				HeaderComponent,
				MatIcon,
				ProfileRowComponent,
				ProposalsComponent
			],
                        imports: [],
                        providers: []
                }).compileComponents();
                fixture = TestBed.createComponent(AppComponent);
                component = fixture.componentInstance;
                fixture.detectChanges();
        });

        it("should be created", () => {
                expect(component).toBeTruthy();
        });
});
Hier passiert zunächst nicht allzu viel, außer dass überprüft wird, ob die Komponente überhaupt erstellt werden kann. Falls du bereits Erfahrung mit Tests in JavaScript oder TypeScript hast, sollten dir die Bezeichner describe, beforeEach und it bekannt vorkommen. Diese Konvention wird in verschiedenen Frameworks genutzt, um eine konsistente Struktur für Tests zu gewährleisten. Der beforeEach-Block selbst ist dazu da, mithilfe des Angular-Kernmoduls TestBed die Konfiguration vorzubereiten. Hierbei kannst du drei verschiedene Eigenschaften festlegen:
  • declarations: In diesem Array sind die Komponentenklassen aufgelistet, die du in deinem Unittest verwenden willst.
  • imports: In diesem Array kannst weitere Modulklassen aufführen, die du in deinen Unittest importieren willst.
  • providers: In diesem Array sind alle Dienste aufgelistet, auf die du in deinem Unittest zugreifen willst.

Die Methode compileComponents() sorgt dafür, dass alle mit configureTestingModule geladenen Klassen und Module zusammengeführt werden. Anschließend wird mithilfe der Methode createComponent() eine Instanz der Komponente AppComponent erstellt und der Variablen fixture zugewiesen. Durch die Eigenschaft componentInstance kannst du dann auf das tatsächliche Objekt zugreifen. Dann sorgt die Zeile fixture.detectChanges() dafür, dass alle Änderungen erkannt und angewandt werden. Es ist wichtig, dass du Methode nach jeder Änderung aufrufst, wenn du mit Komponenten arbeitest. Obwohl es auch eine Möglichkeit gibt, dass Angular Änderungen automatisch erkennt, habe ich persönlich festgestellt, dass das nicht immer zuverlässig funktioniert.


Asynchrone Testkonfiguration

Jetzt gibt es noch eine Kleinigkeit, die du wissen solltest. Das Zusammenführen mit compileComponents läuft asynchron ab, und somit im Hintergrund. Das bedeutet, wenn du dir eine Komponente erstellst, kann es sein, dass Angular mit der Konfiguration noch gar nicht fertig ist. Deswegen ist es gängige Praxis, also Best Practise, die Konfiguration und die Zuweisung in zwei separate beforeEach-Blöcke zu packen:
import { AppComponent } from "./app.component";
import { CardComponent } from "./card/card.component";
import { ComponentFixture, TestBed } from "@angular/core/testing";
import { FriendBoxComponent } from "./friend-box/friend-box.component";
import { HeaderComponent } from "./header/header.component";
import { MatIcon } from "@angular/material/icon";
import { ProfileRowComponent } from "./profile-row/profile-row.component";
import { ProposalsComponent } from "./proposals/proposals.component";

describe("AppComponent", () => {
        let component: AppComponent;
        let fixture: ComponentFixture<AppComponent>;

        beforeEach(async() => {
                TestBed.configureTestingModule({
                        declarations: [
				AppComponent,
				CardComponent,
				FriendBoxComponent,
				HeaderComponent,
				MatIcon,
				ProfileRowComponent,
				ProposalsComponent
			],
                        imports: [],
                        providers: []
                }).compileComponents()
        })

        beforeEach(() => {
                fixture = TestBed.createComponent(AppComponent);
                component = fixture.componentInstance;
                fixture.detectChanges();
        });

        it("should be created", () => {
                expect(component).toBeTruthy();
        });
});

Wie du siehst, steht die Konfiguration in einem asynchronen beforeEach-Block und die Zuweisung an die Variable in einem „normalen“ (synchronen) beforeEach-Block. Dadurch ist sichergestellt, dass die Konfiguration erst abgearbeitet wird, und dann finden die Zuweisungen statt.


Ein einfacher Unittest

Nachdem du jetzt einen ersten Einblick bekommen hast, wie ein Unittest in Angular aufgebaut ist, kannst du nun relativ leicht einen eigenen Unittest implementieren. Die Assertion kommt aus dem Testframework Jasmine und wird immer mit expect und einem Ausdruck oder einer Variable in Klammern eingeleitet. Danach folgt nach einem Punkt der entsprechende, sogenannte Matcher. Dieser prüft auf bestimmte Art und und Weise, ob eine bestimmte Bedingung gegeben ist. Hier ein paar Beispiele:

  • toBe(), toEqual(): Damit kannst du prüfen, ob eine Variable einen bestimmten Wert hat.
  • toBeTrue(), toBeFalse(): Damit kannst du prüfen, ob eine Bedingung erfüllt (true) oder nicht erfüllt (false) ist.
  • toBeTruthy(), toBeFalsy(): Damit kannst du Bedingungen prüfen, aber auch z.B. ob Objekte (nicht) null sind.
  • toHaveBeenCalled(), toHaveBeenCalledTimes(): Damit kannst du prüfen, ob oder auch wie oft eine Funktion aufgerufen wurde. Dies ist eine Spezialität in Jasmine, die man Spy nennt. Darauf gehe ich nachher noch ein.

Jetzt prüfen wir, welchen Wert der Titel hat. Im TypeScript der Komponente AppComponent wird ja der Titel standardmäßig auf den Wert "angular-demo-application" gesetzt. Also ist genau das auch unsere Bedingung. Die Assertion lautet also:

expect(component.title).toBe("angular-demo-application");
Der Matcher ist in diesem Fall toBe(), da der Titel mit dem angegebenen String übereinstimmen muss. Du könntest auch toEqual verwenden, aber den nimmt man normalerweise bei komplexen Datentypen. Und somit fügst du einfach unterhalb des bereits vorhandenen it-Blocks den folgenden Test ein:
it("should have the title 'angular-demo-application'", () => {
        expect(component.title).toBe("angular-demo-application");
});

Die Unittests rufst du nun im Projektordner mit dem Befehl ng test auf:

C:\Users\Robert\angular-demo-application>ng test
⠸ Generating browser application bundles (phase: building)...
✔ Browser application bundle generation complete.
24 08 2023 13:41:32.099:WARN [karma]: No captured browser, open http://localhost:9876/
24 08 2023 13:41:32.134:INFO [karma-server]: Karma v6.4.2 server started at http://localhost:9876/
24 08 2023 13:41:32.136:INFO [launcher]: Launching browsers Chrome with concurrency unlimited
24 08 2023 13:41:32.145:INFO [launcher]: Starting browser Chrome
24 08 2023 13:41:34.081:INFO [Chrome 116.0.0.0 (Windows 10)]: Connected on socket 5qgCI-_c462ygcS9AAAB with id 87533278
Chrome 116.0.0.0 (Windows 10): Executed 5 of 8 SUCCESS (0 secs / 0.155 secs)
Chrome 116.0.0.0 (Windows 10): Executed 8 of 8 SUCCESS (0.242 secs / 0.196 secs)
TOTAL: 8 SUCCESS

Und wenn der Test erfolgreich war, sollte sich eine Browserinstanz mit dem Karma-Testrunner öffnen, und du solltest in etwa folgendes sehen (Klicken zum Vergrößern):

Den eben implementierten Test habe ich rot markiert. Damit du siehst, wie ein fehlgeschlagener Test aussieht, habe ich einfach mal die Bedingung negiert. Dies geht durch Einfügen des Wortes not:

expect(component.title).not.toBe("angular-demo-application");

Und so sieht das im Testrunner aus (Klicken zum Vergrößern):

Du siehst also sofort, welcher Test fehlgeschlagen ist, was die Ursache war, und du bekommst einen Stacktrace. Wenn mehrere Tests fehlschlagen, werden diese selbstverständlich auch angezeigt.


Spionage – Der Jasmine-Spy

Eine tolle Möglichkeit in Jasmine ist, dass du – wie bereits erwähnt – prüfen kannst, ob und wie oft eine bestimmte Funktion aufgerufen wird. Hierzu richtest du vorher einen sogenannten Spy ein. Dieser kann jedoch noch viel mehr. Du kannst damit auch Funktionen manipulieren, wenn du ihnen bestimmte Werte übergibst. Ich will dir hier zwei allgemeine Beispiele zeigen. Das erste davon benötigen wir dann später im Komponententest noch.


Wurde ich aufgerufen?

Zuerst zeige ich dir, wie du einen Spy so einrichtest, dass du einen Funktionsaufruf prüfen kannst. Dies sieht im Allgemeinen so aus:

class SomeClass {
        someMethod() {
                //  do something ...
        }
}
[...]
let someObject = new SomeClass();
spyOn(someObject, "someMethod");
[...]
someObject.someMethod();
[...]
expect(someObject.someMethod).toHaveBeenCalled();
expect(someObject.someMethod).toHaveBeenCalledTimes(<number>);

Hierbei wird mit spyOn der Spion eingerichtet. Man übergibt ihm das zu überwachende Objekt, und den zu überwachenden Methodennamen als String. Später kannst du die Methode wie gewohnt aufrufen, und mit expect(someObject.someMethod).toHaveBeenCalled() prüfen, ob die Methode aufgerufen wurde, und mit expect(someObject.someMethod).toHaveBeenCalledTimes(<number>) kannst du prüfen, wie oft die Methode aufgerufen wurde. Wenn du prüfen willst, ob die Methode nicht aufgerufen wurde, dann schreibst du einfach expect(someObject.someMethod).not.toHaveBeenCalled() oder alternativ expect(someObject.someMethod).toHaveBeenCalledTimes(0).

WICHTIG: Achte bitte darauf, dass du beim Einrichten des Spions den Methodennamen als Stringliteral angibst, da es sich technisch um einen keyof-Operator aus TypeScript handelt. Beim expect kannst du dann den Methodennamen wie im Beispiel angegeben verwenden. Falls du aber Gründe hast, den Namen in einer Variablen zu speichern, kannst du auch folgenden Workaround nutzen:

let variable = someObject.someMethod.name as keyof SomeClass;
spyOn(someObject, variable);
Bei den Integrationstests werde ich die Variante mit dem Stringliteral verwenden, da es für unser Mini-Projekt völlig ausreicht, und einfach und unkompliziert ist.

Der Manipulator

Ein weiteres bemerkenswertes Merkmal von Jasmine-Spionen ist ihre Verwendung als Mocks. Du kannst einen Spion einrichten und ihm Anweisungen geben, welche Ergebnisse er zurückliefern soll. Ein praktisches Beispiel dafür ist, wenn du in einem bestimmten Test Szenarien simulieren möchtest, die in der Realität zu zeitaufwendig wären, wie beispielsweise eine Datenbankabfrage. Du könntest dann stattdessen einfach dem Spy sagen, welche Werte er liefern soll. Das ermöglicht dir, verschiedene Szenarien zu testen, ohne reale Ressourcen zu beanspruchen. Die Verwendung von Mocks, wie sie durch Spione ermöglicht wird, ist besonders nützlich, wenn du Tests unabhängig von externen Ressourcen durchführen oder komplexe Szenarien simulieren möchtest, die in einer Testumgebung besser kontrolliert werden können. Die Funktionalität deiner Methode über den Spy kannst du wie im folgenden Beispiel ändern:
class SomeClass {
        someMethod(): number {
                return 1;
        }
}
[...]
let someObject = new SomeClass();
spyOn(someObject, "someMethod").and.returnValue(2);
[...]
let value = someObject.someMethod();
expect(value).toBe(2);

Hier sagst du nach dem Aufruf von spyOn() dem Spion, was er als Rückgabewert zurückgeben soll. Das and in der Mitte ist übrigens optional. Es handelt sich um eine Art syntaktischer Zucker und dient somit nur der besseren Lesbarkeit. Wichtig ist hier die Methode returnValue(). Diese gehört zur Klasse SpyStrategy von Jasmine. Mit dieser teilst du deinem Spion mit, welchen Wert er durch den Methodenaufruf zurückgeben soll. Du brauchst aber nicht explizit eine neue Instanz von SpyStrategy erzeugen, da du diese implizit durch spyOn() aufrufst.

Du kannst auch festlegen, dass eine Funktion nur mit bestimmten Werten aufgerufen werden darf, also beispielsweise:

class SomeClass {
        double(n: number): number {
                return 2 * n;
        }
}
[...]
let someObject = new SomeClass();
spyOn(someObject, "double").withArgs(3).and.returnValue(5);
[...]
let value = someObject.double(3);
expect(value).toBe(5);

Hier ist es erforderlich, dass du jeden zulässigen Wert gesondert einrichtest. In diesem Fall darf someObject.double() nur mit der Zahl 3 als Argument aufgerufen werden. Würdest du die Funktion mit anderen Argumenten aufrufen, würde dein Test fehlschlagen. Du könntest aber auch festlegen, dass er eine ganz andere Funktion aufrufen soll:

class SomeClass {
        double(n: number): number {
                return 2 * n;
        }
}
[...]
let someObject = new SomeClass();
spyOn(someObject, "double").and.callFake((n: number) => 3 * n);
[...]
let value = someObject.double(3);
expect(value).toBe(9);

In diesem Fall überschreibst du die Originalfunktion so, dass sie die angegebene Zahl nicht verdoppelt, sondern verdreifacht.


Plus de ça

Es gibt natürlich noch viel mehr Möglichkeiten in Jasmine. Da ich dir hier nur die Grundlagen zeigen möchte, lade ich dich dazu ein, im Anschluss gerne meine Quellenangaben durchzugehen und gerne auch durch Ausprobieren herauszufinden, welche weiteren Möglichkeiten Angular und Jasmine bieten.

Komponenten-Integration

Lassen wir uns nun anschauen, wie zwei Komponenten miteinander interagieren können. In unserem kleinen Projekt sind es die FriendBoxComponent und die ProfileRowComponent, die miteinander agieren. Der AddFriendService dient dabei als Vermittler. Wenn du beispielsweise bei einem Profil auf „Folgen“ klickst, übermittelt das Profil über den Service seine Informationen an die FriendBoxComponent. Dadurch weiß diese, welches Bild angezeigt werden soll. Vor der Anzeige muss jedoch geprüft werden, ob nicht bereits drei Profile vorhanden sind. Anschließend wird der „Folgen“-Link ausgeblendet.

Wir werden hierfür folgende Schritte durchgehen:

  • Ordner tests für die Integrationstests anlegen.
  • Weitere Unterordner für benötigte Klassen, Komponenten und Dienste nach dem Page Object Model anlegen.
  • Die eben erwähnten Klassen, Komponenten und Dienste implementieren.
  • Die entsprechenden Integrationstests implementieren.

Neue Ordnerstruktur

Lass uns die ersten beiden Schritte angehen. Du weißt bereits, dass sich unterhalb des Ordners src die Unterordner app und assets befinden. Erstelle nun einen weiteren Ordner direkt unter src mit dem Namen tests. Erstelle innerhalb dieses Ordners die Unterordner classes, components und services. Nach diesen Schritten sollte die Ordnerstruktur in etwa folgendermaßen aussehen:

angular-demo-application
├── src
│   ├── app
│   ├── assets
│   ├── tests
│   │   ├── classes
│   │   ├── components
│   │   ├── services
[...]

Der Unterordner classes wird die Mutterklasse Components enthalten, die gemeinsame Funktionen für die Freundschaftsbox und die Profile bereitstellt. In components werden die Klassen für die Freundschaftsbox und die Profile nach dem Page-Object-Modell abgelegt. Der AddFriendService wird im Ordner services zu finden sein.


Klassen-Implementierung

Als Nächstes legen wir die entsprechenden Klassen, Komponenten und Dienste an. Die erste Klasse ist die Mutterklasse der Komponenten. Der Dateiname lautet Component.ts und kommt direkt unterhalb von tests/classes. Der Inhalt sieht wie folgt aus:

import { ComponentFixture, TestBed } from "@angular/core/testing";
import { Type } from "@angular/core";

export class Component<T> {
        protected readonly fixture: ComponentFixture<T>;

        constructor(component: Type<T>) {
                this.fixture = TestBed.createComponent(component);
                this.update();
        }

        get(): ComponentFixture<T> {
                return this.fixture;
        }

	getElement(): any {
		return this.fixture?.nativeElement;
	}

        getInstance(): T {
                return this.fixture?.componentInstance;
        }

        public update() {
                this.fixture?.detectChanges();
        }

        public static update(...components: Component<any>[]) {
                for(let component of components) {
                        component?.get().detectChanges();
                }
        }
}

Diese generische Klasse ermöglicht es uns, mithilfe von TestBed eine neue Komponente zu erstellen und Änderungen zu aktualisieren. Die Methode get() gibt dir die Komponente selbst zurück. getElement() ermöglicht den Zugriff auf den DOM-Baum (das HTML-Element). getInstance() gibt dir Zugriff auf deine eigene Implementierung der Klasse, also auf Eigenschaften und Methoden. Die Methode update() aktualisiert Änderungen in der Komponente, ruft also um Prinzip detectChanges() der Komponente auf. Ich finde, das klingt einfach direkter. Weil genau das macht detectChanges() auch. Die statische Methode update() aktualisiert ebenfalls Änderungen, jedoch kannst du hier mehrere Komponenten angeben, damit du nicht jede Komponente einzeln updaten musst.

Die Spy-Klasse wird als Datei Spy.ts ins gleiche Verzeichnis abgelegt. Sie ist relativ minimalistisch und sieht wie folgt aus:

export class Spy {
        static on(object: any, functionName: string) {
                let spyObject = object;
                if("get" in object) spyObject = object.get();
                spyOn(spyObject, functionName);
        }
}

Die Zeile if("get" in object) spyObject = object.get(); ermöglicht es, die Methode get(), falls sie existiert, aufzurufen, und die Komponente oder den Dienst selbst als Objekt zu verwenden. Das benötigen wir, da der AddFriendService, ähnlich wie die Klasse Component, auch eine get()-Methode haben wird. Weil das der einzige Dienst ist, habe ich die Funktionen nicht in eine separate Klasse ausgelagert.


Dienst-Implementierung

Bevor du die Komponenten und die zugehörigen Tests implementierst, kümmern wir uns vorher noch kurz um den Freundschaftsdienst. Leg hierzu bitte die Datei AddFriendService im Verzeichnis services mit folgendem Inhalt an:

import { AddFriendService } from "src/app/add-friend.service";
import { TestBed } from "@angular/core/testing";

export class AddFriends {
        private readonly service: AddFriendService;

        constructor() {
                this.service = TestBed.inject(AddFriendService);
        }

        static getService(): typeof AddFriendService {
                return AddFriendService;
        }

        get(): AddFriendService {
                return this.service;
        }
}

Ähnlich wie bei der Komponenten-Klasse legt AddFriendService mithilfe der Methode TestBed.inject() einen neuen Dienst an. Auf die Dienstinstanz kannst du dann später über get() bzw. getService() zugreifen.


Implementierung der Komponenten

Als erstes implementieren wir die Freundschaftsbox als Klasse. Leg dazu bitte im Unterordner components die Datei Friendbox.ts mit folgendem Inhalt an:

import { Component } from "../classes/Component";
import { FriendBoxComponent } from "src/app/friend-box/friend-box.component";

export class FriendBox extends Component<FriendBoxComponent> {
        constructor() {
                super(FriendBoxComponent);
        }

        static getComponent(): typeof FriendBoxComponent {
                return FriendBoxComponent;
        }

        getFriendList(): HTMLDivElement {
                return this.getElement().querySelector("div.friend") as HTMLDivElement;
        }

        getFriendNodes(): NodeList {
		return this.getElement().querySelectorAll("div.friend") as NodeList;
	}

        beingEmtpy(): boolean {
                return this.lengthBeing(0);
        }

        lengthBeing(length: number): boolean {
                this.update();
                let instance = this.fixture?.componentInstance;
                let namesLength = instance.friendService.names.length == length;
                let textsLength = instance.friendService.texts.length == length;
                let imagesLength = instance.friendService.images.length == length;
                return namesLength && textsLength && imagesLength;
        }
}

Der Konstruktor ruft einfach die Mutterklasse auf. Eine wichtige Methode hier ist getComponent(), die auch in der Klasse Profile.ts implementiert ist. Diese Methode wird später bei der Implementierung der Integrationstests benötigt. Beim Verwenden von TestBed gibst du mithilfe von declarations und providers die Komponenten und Dienste an, die du importieren willst. Die beiden Klassen und der Dienst im Page-Object-Model können jedoch leider nicht direkt dort angegeben werden. Daher habe ich die Methoden getComponent() und getService() implementiert. Diese erlauben es, die entsprechenden Komponenten- und Dienst-Instanzen zu erhalten.

Anmerkung: Ich wollte die Methode getComponent() eigentlich ebenfalls in der Mutterklasse Component<T> implementieren. Jedoch erlaubt es TypeScript nicht, generische Datentypen als Return-Value zurückzugeben. Und da es sich hier nur um eine Minimal-Anwendung handelt, ging es schneller, die Methode zweimal gesondert zu implementieren, als eine generelle Lösung zu finden. In größeren Projekten kann dies aber notwendig sein, eine entsprechende Lösung zu erarbeiten.

In den Methoden getFriendList() und getFriendNodes() gibt es etwas Neues. Und zwar eine Abfrage mit einem CSS-Selektor. Da wir nämlich nach dem Page-Object-Model arbeiten, und wir eine Web-Anwendung haben, suchen wir auch Elemente aus dem DOM-Baum. In anderen Worten, wir suchen konkrete HTML-Elemente heraus, also bspw. über Tag-Name oder CSS-Klasse. Um solche Elemente zu finden, stehen dir in Angular die Methoden querySelector() und querySelectorAll() zur Verfügung. Der erste davon findet immer nur das erste Element und der zweite findet alle Elemente. Dort kannst du einen beliebigen String angeben, der das entsprechende HTML-Element eindeutig identifiziert. Es gibt auch noch andere Möglichkeiten HTML-Elemente zu suchen, bspw. über die Komponenten-Eigenschaft debugElement, welche mehrere query...-Methoden hat. Du könntest alternativ auch klassisch document.getElemementByXXX() verwenden. Doch kann es bei letzterem sein, dass er das entsprechende Element nicht immer  findet, insbesondere wenn du spezielle Angular-Komponenten suchst.

Das Element, dass wir suchen, ist übrigens ein div-Container mit der CSS-Klasse friend. Damit du weißt, worum es sich genau handelt, zeige ich dir hier den Quellcode der Komponente:

<div class="default-card padding-15 friend-row">
        <div *ngIf="friendService.names.length == 0" class="empty">
                Du hast leider noch keine Freunde. 😢
        </div>
      <div class="friend" *ngFor="let name of friendService.names; let i = index">
                <img src="{{friendService.getImage(i)}}"/> {{name}}
        </div>
</div>

Mit den beiden Methoden beingEmpty() und lengthBeing() kannst du ermitteln, ob die Freundschaftsliste noch leer ist, bzw. wie viele bereits vorhanden sind. Dazu prüft die Methode einfach über die Eigenschaft length, ob die drei Arrays names, texts und images die gleiche Anzahl an Elementen haben. Vor der eigentlichen Bedingung wird übrigens die entsprechende Komponente noch mal geupdatet, falls irgendwelche Änderungen stattgefunden haben.

Nachdem ich dir die Methoden querySelector() und querySelectorAll() gezeigt habe, kann ich dir jetzt die Implementierung der Profil-Klasse zeigen. Leg dazu unter components die Datei Profile.ts mit folgendem Inhalt an:

import { Component } from "../classes/Component";
import { ProfileRowComponent } from "src/app/profile-row/profile-row.component";

export class Profile extends Component<ProfileRowComponent>  {
        private id: string;
        private name: string;
        private description: string;
        private canFollow: boolean;

        constructor(object: any) {
                super(ProfileRowComponent);
                let instance = this.getInstance();
                let profile = object as Profile;
                this.id = profile.id;
                this.name = profile.name;
                this.description = profile.description;
                this.canFollow = profile.canFollow;

                instance = instance?.with({
                        id: this.id,
                        name: this.name,
                        description: this.description,
                        canFollow: this.canFollow
                });
                this.update();
        }

        static getComponent(): typeof ProfileRowComponent {
                return ProfileRowComponent;
        }

        getImage(): HTMLImageElement {
                return this.getElement().querySelector("img");
        }

        getName(): HTMLDivElement {
                return this.getElement().querySelector("div.name");
        }

        getDescription(): HTMLSpanElement {
                return this.getElement().querySelector("span.description");
        }

        getFollowLink(): HTMLAnchorElement {
                return this.getElement().querySelector("a#follow");
        }

        clickOnFollow() {
                this.getFollowLink().click();
        }
}

Diese Klasse ruft im Konstruktor auch wieder ihre Mutterklasse auf. Da der Konstruktor der Klasse Profile jedoch als Parameter ein Objekt vom Typ Profile mit Profilname, Beschreibung und Bildnummer erwartet, muss der Konstruktor diese Eigenschaften auch setzen und die Komponente updaten. Bei den darauffolgenden Methoden handelt es sich, wie bei der Klasse FriendBox, um Methoden, die aus dem entsprechenden Profil bestimmte HTML-Tags heraussuchen. Hier siehst du den Quellcode der Komponente profile-row-component.ts:

<div class="profile-row margin-bottom-15">
        <img src="/assets/images/cards/{{id}}.webp"/>
        <div class="name">
                <b>{{name}}</b><br/>
                <span class="description">{{description}}</span>
        </div>
        <a id="follow" *ngIf="canFollow" (click)="addFriend(name, description, id)">Folgen</a>
</div>

Des Weiteren klickt die Methode click() auf den „Folgen“-Link. Auch wenn als Event ein spezielles Angular-Event angegeben ist, kannst du hier ganz gewöhnlich die click()-Methode aufrufen, die in allen HTML-Elementen zur Verfügung steht.


Testimplementierung

Nun ist alles vorbereitet, was du für die Durchführung der Komponententests benötigst. Lass uns jetzt zum eigentlichen Test übergehen. Erstelle dafür eine Datei namens custom.spec.ts direkt im Ordner tests. In diesem Fall ist es das einzige Testskript, daher verzichten wir auf weitere Unterverzeichnisse. Bei größeren Projekten könnte es jedoch sinnvoll sein, die Tests entsprechend der fachlichen Anwendung weiter zu strukturieren. Im Repository findest du eine ausführlichere Version dieser Datei, die zusätzliche Komponententests enthält. Ich habe Stellen, an denen ich Inhalte gekürzt habe, mit [...] markiert. Nachfolgend findest du den für dich relevanten Codeausschnitt:

import { AddFriends } from "./services/AddFriends";
import { Component } from "./classes/Component";
import { FriendBox } from "./components/FriendBox";
import { Profile } from "./components/Profile";
import { Spy } from "./classes/Spy";
import { TestBed } from "@angular/core/testing";

describe("Custom component tests:", () => {
        const TEMPLATE = {
                id: "1",
                name: "Robert",
                description: "Das ist mein Testprofil.",
                canFollow: true
        };

        let service: AddFriends;
        let friendBox: FriendBox;
        let profile: Profile;

        beforeEach(async() =>{
                TestBed.configureTestingModule({
                        declarations: [FriendBox.getComponent(), Profile.getComponent()],
                        providers: [AddFriends.getService()]
                }).compileComponents();
        });

        beforeEach(() => {              
                service = new AddFriends();
                friendBox = new FriendBox();
                profile = new Profile(TEMPLATE);
        });

        [...]

        it("should not be able to add more than 3 profiles (Test #4)", () => {
                let profile1: Profile = new Profile(TEMPLATE);
                let profile2: Profile = new Profile(TEMPLATE);
                let profile3: Profile = new Profile(TEMPLATE);
                let profile4: Profile = new Profile(TEMPLATE);

                expect(friendBox.beingEmtpy()).toBeTrue();

                profile1.clickOnFollow();
                profile2.clickOnFollow();
                profile3.clickOnFollow();
                Component.update(friendBox, profile1, profile2, profile3);

                let friends = friendBox.getFriendNodes();
                expect(friends.length).toBe(3);
                expect(profile1.getFollowLink()).toBeFalsy();
                expect(profile2.getFollowLink()).toBeFalsy();
                expect(profile3.getFollowLink()).toBeFalsy();

                profile4.clickOnFollow();
                Component.update(friendBox, profile4);

                friends = friendBox.getFriendNodes();
                expect(friends.length).toBe(3);
                expect(profile4.getFollowLink()).toBeTruthy();
        })
});

Der Einfachheit halber habe ich als Vorlage ein Profil mit dem Namen TEMPLATE erstellt. In meinem Test erzeuge ich dieses Profil später viermal. Du kannst gerne auch vier verschiedene Profile erstellen, jederzeit mit unterschiedlichem Namen, Beschreibung und Bildnummer. Wie du vielleicht bemerkt hast, habe ich mich auch hier an die Best Practice gehalten, die Komponenten, die ich im TestBed erzeuge, in einen asynchronen beforeEach-Block zu platzieren, während die benötigten Variablen in einem synchronen beforeEach-Block definiert werden.

Der Test selbst verläuft insgesamt folgendermaßen: Zuerst erstelle ich die bereits vier erwähnten Profile. Anschließend prüfe ich, ob die Freundesliste am Anfang leer ist. Um Freunde hinzuzufügen, klicke ich bei den ersten drei Profilen jeweils auf den „Folgen“-Link. Anschließend aktualisiere ich die Komponenten, um sicherzustellen, dass die Änderungen erkannt werden. An dieser Stelle hole ich auch die aktualisierte Freundesliste ab. Im nächsten Schritt überprüfe ich, ob tatsächlich drei Freunde zur Liste hinzugefügt wurden, wie erwartet. Zusätzlich prüfe ich, ob der „Folgen“-Link bei den ersten drei Profilen deaktiviert ist. Jetzt teste ich, wie die Anwendung reagiert, wenn ich beim vierten Profil auf den „Folgen“-Link klicke. Auch hier aktualisiere ich die Komponenten, um die Änderungen zu reflektieren. Zum Abschluss überprüfe ich, ob immer noch drei Freunde in der Liste vorhanden sind und ob der „Folgen“-Link noch aktiv ist. Dies stellt sicher, dass die Anwendung korrekt auf die Interaktionen reagiert und die Anzeige der Freunde richtig aktualisiert wird.

Zusätzlich findest du in der Konsolenausgabe auch folgende Meldung:

ALERT: 'Du kannst nicht mehr als 3 Freunde haben.'

Diese Ausgabe wird direkt von der Methode addFriend in der Komponente ProfileRowComponent generiert, die den AddFriendService verwendet. Um das genauer zu verstehen, kannst du dir den folgenden Code-Ausschnitt ansehen:

        [...]
        addFriend(name: string, description: string, id: number) {
                let result = this.friendService.addFriend(name, description, id);
                if(result == 200) {
                        this.canFollow = false;
                }
                else if(result == 400) {
                        alert("Du kannst nicht mehr als 3 Freunde haben.");
                }
                else {
                        alert("Unerwarteter Fehler");
                }
        }
        [...]

Herzlichen Glückwunsch!

Wenn nun alles gut gelaufen ist, sollten alle Tests erfolgreich sein und der Inhalt deines Testrunners in etwa so aussehen (Klicken zum Vergrößern):

Falls deine Tests fehlgeschlagen sein sollten, empfehle ich dir, gründlich nach möglichen Fehlern zu suchen. Sollte der Fehler dennoch wider Erwarten in meinem bereitgestellten Code liegen, dann kontaktiere mich gerne per Kommentar. Ich werde den Fehler baldmöglichst im Repository korrigieren.

Selbstverständlich steht es dir frei, lokal am Repository weiter zu experimentieren und zusätzliche Tests zu implementieren – sei es Unittests oder Integrationstests. Erkunde gerne auch die von mir bereitgestellten Tests, um weitere Einblicke zu erhalten. Probier gerne auch End-to-End-Tests aus, falls dich das interessiert. Das wird von Karma genauso unterstützt. Mit diesem Wissen sind wir nun im Bereich der Testimplementierung gut aufgestellt.


Projekt-Konfiguration

Auf die Konfiguration von Angular, Karma und Jasmine bin ich bewusst nicht eingegangen – der Einfachheit halber. Du kannst in Angular noch alles Mögliche in der Datei angular.json einstellen, z.B. welches Testframework du verwenden möchtest, auch für End-to-End-Tests. Du kannst dort beispielsweise Cypress, Playwright oder auch Selenium einsetzen. Die Einstellungen von Karma und Jasmine kannst du in einer Karma-Konfigurations-Datei vornehmen, z.B. in welchem Browser deine Tests starten sollen. Für die Konfiguration sind mehrere Dateinamen zulässig. Eine Möglichkeit ist karma.conf.js. Falls dich das alles interessiert, bitte ich dich, unten die Quellenangaben durchzustöbern.


Wie geht es weiter?

Ich habe dir nun die grundlegenden Schritte zur Erstellung einer Angular-Anwendung und zum Testen der einzelnen Komponenten vermittelt. Die nächste Phase liegt bei dir – du kannst dein neu gewonnenes Wissen vertiefen oder in deinem eigenen Projekt anwenden. Selbst wenn Angular nicht in deinem Projekt verwendet wird, kannst du die Prinzipien, die ich dir gezeigt habe, sicherlich adaptieren. Wenn du bis hierhin gelesen und aktiv mitgemacht hast, danke ich dir herzlich für deine Aufmerksamkeit und wünsche dir viel Erfolg bei all deinen kommenden Projekten. Bei Fragen und Anregungen freue ich mich auf einen Kommentar von dir, und bin selbstverständlich auch offen für konstruktive Kritik.

Quellenangaben


 

Anmerkung: In diesem Blog nutze ich die informelle Anrede „du“ und gleichzeitig das generische Maskulinum. Dies dient dazu, eine persönliche Atmosphäre zu schaffen, die den Austausch und das Lernen ansprechender macht. Gleichzeitig erleichtert es den Lesefluss. Dennoch gilt natürlich allen Lesern und Leserinnen mein höchster Respekt.

Ein kleines Tutorial

Im Rahmen seiner Tätigkeit in der IT kommt man um bestimmte Frameworks nicht herum. Oft werden sogar mehrere Frameworks in einem einzigen Projekt verwendet, da jedes einen anderen Zweck erfüllt. Persönlich bin ich nun schon zum zweiten Mal auf Angular gestoßen. Von daher lade ich dich dazu ein, dass wir hier gemeinsam eine kleine, unkomplizierte Anwendung in diesem Framework erstellen und die verschiedenen Komponenten daraufhin testen. Wir halten uns hierbei an das K.I.S.S.-Prinzip (Keep It Simple and Stupid), was bedeutet, dass ich in diesem Blog nur die grundlegenden Konzepte erläutere, um das Verständnis zu erleichtern. In diesem Beitrag arbeite ich unter Windows, verwende die Eingabeaufforderung (cmd.exe) als Terminal innerhalb von Visual Studio Code als IDE. Selbstverständlich steht es dir frei, unter einem anderen Betriebssystem, mit einem anderen Terminal oder auch mit einer anderen IDE zu arbeiten. Ich bitte dich jedoch, dass du dich selbstständig mit diesen vertraut machst, insbesondere wenn es darum geht, etwas zu installieren.


Für wen ist dieser Artikel?

Dieser Blog-Beitrag richtet sich an dich, wenn du dich zum ersten Mal mit Angular beschäftigst. Hier werden die Grundlagen vermittelt, um eine Angular-Anwendung zu erstellen und sie mit Karma und Jasmine zu testen. Es ist wichtig, dass du über folgende Grundkenntnisse verfügst:

  • Terminologie:
    • Page Object Modell
    • Komponenten
    • Services (Dienste)
  • Technologien:
    • HTML
    • (S)CSS
    • JavaScript / TypeScript
    • Node.js
    • Testing-Frameworks (allgemein)
    • XPath (o.ä.)

Was ist das Ziel?

Das Hauptziel dieses Artikels ist es, am Ende eine kleine, unkomplizierte Angular-Anwendung zu haben, die ein soziales Netzwerk simuliert. In dieser Anwendung können bis zu drei Freunde hinzugefügt werden. Wir werden diesen Vorgang auch durch Integrationstests verifizieren. Da das Thema dennoch sehr umfangreich ist, habe ich es auf zwei Teile aufgeteilt. In diesem hier werden die Grundlagen von Angular und die Implementierung der Anwendung behandelt und im zweiten Teil wollen wir unsere Anwendung dann durch Unit- und Integrationstests testen.


Repository zum Mitarbeiten

Zum Nachverfolgen der Schritte und für den Zugriff auf die im Projekt genutzten Bilddateien benötigst du das zugehörige Repository. Du kannst es mit Git (oder GitHub Desktop) über den Link in den Quellenangaben klonen. Zum selbstständigen Erarbeiten der Schritte empfehle ich dir, zunächst nur die Bilddateien herunterzuladen und sie in den Assets-Ordner (siehe Ordnerstruktur in Angular) zu kopieren.


Einführung in Angular

Angular installieren

Starte nun bitte das Terminal. Bevor du Angular installierst, prüfe bitte sicherheitshalber noch mal die Versionen von Node.js und npm:

C:\Users\Robert>node -v
v18.17.0

C:\Users\Robert>npm -v
9.6.7

Sollte eine Fehlermeldung auftreten, zum Beispiel:

C:\User\Robert>node -v
'node' is not recognized as an internal or external command, operable program or batch file.

C:\User\Robert>npm -v
'npm' is not recognized as an internal or external command, operable program or batch file.

Dann prüfe bitte den Installationspfad von Node.js und die Umgebungsvariable PATH. Falls erforderlich, lade Node.js erneut herunter und installiere es erneut. Der Paketmanager npm wird dabei automatisch mitinstalliert.

Wenn Node.js und npm installiert sind, kannst du nun auch Angular installieren:

C:\Users\Robert>npm install -g @angular/cli

Neben Angular werden automatisch auch noch der Testrunner Karma und das Testframework Jasmine installiert. Dazu kommen wir später. Nach der Installation von Angular steht dir nun der Befehl ng zur Verfügung und du kannst ein neues Projekt anlegen:

C:\User\Robert>ng new angular-demo-application
? Would you like to add Angular routing? (y/N) y
? Which stylesheet format would you like to use?
  CSS
> SCSS [ https://sass-lang.com/documentation/syntax#scss ]
  Sass [ https://sass-lang.com/documentation/syntax#the-indented-syntax ]
  Less [ http://lesscss.org ]
...
CREATE angular-demo-application/... (... bytes)
...
| Installing packages (npm)...

  • Die Option für Angular-Routing ermöglicht dir das Hinzufügen von Unterseiten zur Anwendung. Dies hat jedoch nichts mit den eigentlichen Komponenten zu tun.
  • Die Stylesheet-Sprache kannst du nach Belieben wählen. Meine persönliche Empfehlung ist CSS oder (wie in diesem Projekt) SCSS. SCSS ist eine Erweiterung von CSS, die zusätzliche Funktionen wie Variablen, verschachtelte Selektoren und Mixins bietet. Dadurch wird das Styling deiner Anwendung effizienter und organisierter.

Ordnerstruktur in Angular

Bevor du überhaupt eine Zeile Code schreibst, ist es wichtig, dass du dich mit der Projektstruktur vertraut machst. Im Folgenden skizziere ich dir die grundlegende Ordnerstruktur, wobei ich mich auf diejenigen Ordner und Dateien konzentriere, die für diesen Blogbeitrag relevant sind.

angular-demo-application
├── dist
│   └── angular-demo-application
├── node_modules
├── src
│   ├── app
│   │   ├── example
│   │   │   ├── example.component.html
│   │   │   ├── example.component.scss
│   │   │   ├── example.component.spec.ts
│   │   │   └── example.component.ts
│   │   ├── app-routing.module.ts
│   │   ├── app.component.html
│   │   ├── app.component.scss
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts
│   ├── assets
│   ├── favicon.ico
│   ├── index.html
│   ├── main.ts
│   └── styles.scss
├── angular.json
├── package.json
└── tsconfig.json

Kurze Erklärung:

  • angular-demo-application: Jedes Angular-Projekt befindet sich in einem eigenen Ordner, wie du es sicher von anderen Entwicklungsumgebungen kennst.
  • dist/angular-demo-application: Hierhin wird die Anwendung kompiliert. Alle benötigten Source-Files werden hineinkopiert, und die TypeScript-Dateien werden in gewöhnliches JavaScript transpiliert.
  • node_modules: Dieser Ordner enthält die Abhängigkeiten und Pakete, die die Angular-Anwendung benötigt. Diese werden normalerweise automatisch von Angular und npm verwaltet.
  • src: Der Hauptordner deiner Angular-Anwendung, in dem der Großteil des Quellcodes und der Ressourcen enthalten sind.
  • app: Dies ist der Kern der Anwendung. Hier findest du alle Angular-Komponenten, Module, Services und andere wichtige Teile.
  • example: Ein Beispielordner für eine Komponente. Eine Komponente besteht in der Regel aus vier Dateien:
    • example.component.html: Enthält die grobe Struktur der Komponente als HTML-Code.
    • example.component.scss: Enthält die Stylesheet-Informationen in CSS bzw. SCSS.
    • example.component.spec.ts: Enthält die Unittests in TypeScript für die Komponente.
    • example.component.ts: Enthält die Grundlogik der Komponente in TypeScript.
  • app-routing.module.ts: Hier werden die Unterseiten der Anwendung verwaltet. In diesem Blogbeitrag gehe ich nicht näher darauf ein, da wir nur eine Single-Page-Anwendung haben.
  • app.component.html: Die Hauptkomponente der Anwendung. Hier können andere Komponenten eingebettet werden.
  • app.component.scss: Die globale Stylesheet-Datei für die gesamte Anwendung.
  • app.component.spec.ts: Unittests in TypeScript für die Hauptkomponente.
  • app.component.ts: Die Grundlogik der Hauptkomponente.
  • app.module.ts: Das Hauptmodul der Anwendung, das alle Komponenten, Dienste und andere Module zusammenführt.
  • assets: Hier werden statische Dateien, z.B. Bilder, Schriftarten usw. abgelegt, die unsere Anwendung benötigt.
  • favicon.ico: Das Icon der Anwendung, das in der Browser-Registerkarte angezeigt wird.
  • index.html: Die Einstiegs-HTML-Datei für die Anwendung. Hier wird normalerweise das HTML-Tag <app-root> platziert, das die Hauptkomponente einfügt.
  • main.ts: Die Einstiegsdatei für deine Anwendung, in der die Anwendung gestartet wird.
  • styles.scss: Die globale Stylesheet-Datei für die gesamte Anwendung, die auf alle Komponenten angewendet wird.
  • angular.json: Die Konfigurationsdatei des Angular-Projekts, in der verschiedene Einstellungen wie Build-Optionen, Erweiterungen usw. festgelegt werden
  • package.json: Die Datei, die die Metadaten und Abhängigkeiten des Projekts enthält. Hier werden die benötigten Pakete und ihre Versionen aufgeführt.
  • tsconfig.json: Die TypeScript-Konfigurationsdatei, die Einstellungen für den TypeScript-Compiler enthält.

Angular-Anwendung starten

Ist der Start der Anwendung zum jetzigen Zeitpunkt überraschend für dich? Schließlich hast du ja noch gar keinen eigenen Code geschrieben, oder? Ja, das ist richtig. Aber Angular erstellt mit dem Befehl ng new automatisch eine Art Demo-Anwendung. Wenn wir uns die index.html ansehen, steht da lediglich Folgendes drin:

<!doctype html>
<html>
        <head>
                <meta charset="utf-8">
                <title>AngularDemoApplication</title>
                <base href="/">
                <meta name="viewport" content="width=device-width, initial-scale=1">
                <link rel="icon" type="image/x-icon" href="favicon.ico">
        </head>
        <body>
                <app-root></app-root>
        </body>
</html>

Die soll auch so bleiben, denn die eigentliche Anwendung wird durch die beiden Tags <app-root></app-root> eingebettet. Die eingebettete HTML-Datei dazu ist die app.component.html. In der zugehörigen TypeScript-Datei sieht man, dass sie das Tag <app-root> hat:

import { Component, OnInit } from "@angular/core";

@Component({
        selector: "app-root",
        templateUrl: "./app.component.html",
        styleUrls: ["./app.component.scss"]
})
export class AppComponent implements OnInit {
        title = "angular-demo-application";

        ngOnInit() {}
}

Der Dekorator @Component beschreibt die Komponente genauer. Dort siehst du folgende drei Eigenschaften:

  • selector: Hier steht String, mit dem du bestimmen kannst, wie das HTML-Tag heißen soll, mit dem du auf deine Komponenten zugreifst. In diesem Blog halten wir uns an die Default-Konvention, dass vor den Komponentennamen im HTML das Wort app mit Bindestrich steht.
  • templateUrl: Hier steht, zu welcher HTML-Datei das TypeScript, das die Logik implementiert, gehört.
  • styleUrls: Hierbei handelt es sich um ein Array, in welchem alle (S)CSS-Dateien angegeben sind, die das Design der HTML-Komponente beschreiben.
Um die Anwendung zu starten, verwende den Befehl ng serve. Wenn du Visual Studio Code verwendest, empfehle ich dir, das Terminal innerhalb der IDE zu verwenden. Dadurch erkennt die IDE, wenn du Code bearbeitest und speicherst, und kompiliert deine Anwendung automatisch nach jedem Speichervorgang. Das ermöglicht dir eine „Live“-Entwicklung deiner Anwendung. Falls du jedoch ein extra Terminal verwendest, kannst du deine Anwendung auch manuell mit ng build kompilieren lassen. Wichtig ist außerdem, dass du dich stets im Angular-Projektordner befindest. Sollte dies nicht der Fall sein, quittiert Angular dies mit der folgenden Fehlermeldung:
C:\Users\Robert\>ng serve
The serve command requires to be run in an Angular project, but a project definition could not be found.
Falls dies du also nicht in deinem Projektordner bist, wechsle dort hinein, beispielsweise mit:
C:\Users\Robert\>cd angular-demo-application
C:\Users\Robert\angular-demo-application\>
Und dann steht dem Start nichts mehr im Wege:
C:\Users\Robert\angular-demo-application>ng serve --open
√ Browser application bundle generation complete.

Initial Chunk Files | Names | Raw Size
vendor.js | vendor | 2.33 MB |
polyfills.js | polyfills | 333.21 kB |
styles.css, styles.js | styles | 230.94 kB |
main.js | main | 48.15 kB |
runtime.js | runtime | 6.54 kB |

| Initial Total | 2.94 MB

Build at: 2023-08-22T14:03:13.256Z - Hash: 56ce367093804d17 - Time: 5318ms

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **


√ Compiled successfully.
√ Browser application bundle generation complete.

Initial Chunk Files | Names | Raw Size
main.js | main | 48.15 kB |
runtime.js | runtime | 6.54 kB |

3 unchanged chunks

Build at: 2023-08-22T14:15:48.519Z - Hash: 637f1efa8a1a1564 - Time: 1340ms

√ Compiled successfully.

Die Befehlsoption --open signalisiert Angular, dass die Anwendung im Standardbrowser geöffnet werden soll. Wenn du die Option weglässt, kannst du die Anwendung nach dem Kompilieren auch manuell öffnen, indem du in der Adresszeile deines Browsers die URL localhost:4200 eingibst und mit der Eingabetaste bestätigst. Jedenfalls sollte das Ganze nun so aussehen (Klicken zum Vergrößern):


Komponenten in Angular erstellen

Angular-Anwendungen bestehen ja hauptsächlich aus Komponenten und Diensten. Zuerst wollen wir also eine neue Komponente erstellen. Wir nennen diese header. Um sie zu erstellen, gibt es den Befehl ng generate component. Also generiere bitte deine erste, eigene Komponente. Dazu kannst du einfach ein weiteres Terminal öffnen. Wichtig ist nur, dass du im Angular-Projektordner bist.

C:\Users\Robert\angular-demo-application>ng generate component header
CREATE src/app/header/header.component.html (21 bytes)
CREATE src/app/header/header.component.spec.ts (559 bytes)
CREATE src/app/header/header.component.ts (203 bytes)
CREATE src/app/header/header.component.scss (0 bytes)
UPDATE src/app/app.module.ts (475 bytes)

Jetzt haben wir einen neuen Ordner in unserer Projektstruktur:

angular-demo-application
├── src
│   ├── app
│   │   ├── header
│   │   │   ├── header.component.html
│   │   │   ├── header.component.scss
│   │   │   ├── header.component.spec.ts
│   │   │   └── header.component.ts
│   │   ├── app-routing.module.ts
│   │   ├── app.component.html
│   │   ├── app.component.scss
│   │   ├── app.component.spec.ts
│   │   ├── app.component.ts
│   │   └── app.module.ts
[...]

Wie erwartet hat Angular für uns einen Unterordner für die Header-Komponente mit den vier benötigten Dateien erstellt. Zuerst wollen wir uns um die HTML-Datei kümmern. Standardmäßig fügt er eine Art Default-Code ein, in diesem Fall:

<p>header works!</p>

Ersetz den kompletten Code in der header.component.html bitte durch folgenden:

<header>
        <img src="assets/images/simplytest.png"/>
</header>

Falls du das Beispiel selbst ausprobierst, ist es spätestens jetzt wichtig, dass du die Bilder in den Assets-Ordner geladen hast, damit die Angular-Anwendung richtig dargestellt wird. Wir wollen das ganze auch ein bisschen schön formatieren. Deswegen trag in die Datei header.component.scss bitte Folgendes ein:

header {
        background-color: lightgray;
        box-shadow: 8px 8px 8px rgba(0, 0, 0, 0.5);
}

Und damit wir den Header auch sehen, ersetzt einfach alles aus der app.component.html erst einmal durch folgenden Code:

<app-header/>
Das wird dann später noch erweitert. Das ganze wollen wir mit einer Hintergrundfarbe versehen, ich habe eine Art helles Gelb-Grau genommen. Du darfst gerne auch mit anderen Farben experimentieren. Trag bitte Folgendes in die app.component.scss ein:
html, body {
        height: 100%;
}

body {
        background-color: rgba(255, 255, 222, 0.75);
        font-family: Arial, Helvetica, sans-serif;
        margin: 0;
}

.default-card {
        background-color: #123456;
        border-radius: 10px;
        box-shadow: rgba(0, 0, 0, 0.5);
        box-sizing: border-box;
        margin: 15px;
        margin-bottom: 10px;
        width: 480px;
}

.margin-bottom-15 {
        margin-bottom: 15px;
}

.padding-15 {
        padding: 15px;
}

Die CSS-Klassen .default-card, .margin-bottom-15 und .padding-15 brauchen wir später noch. Es kommen ja noch weitere Komponenten dazu. Wenn du alles richtig gemacht hast, sollte deine Angular-Anwendung nun wie folgt aussehen (Klicken zum Vergrößern):

Du kannst in der HTML-Datei der Komponente wie gewohnt jeglichen HTML-Code einfügen, um die Struktur genauer zu beschreiben. Selbstverständlich kannst du auch andere Komponenten, die du bereits erstellt hast, dort einbetten. Das Gleiche haben wir ja hier in der Hauptkomponente app.component.html gemacht. Und genauso kannst du die Style-Informationen in der entsprechenden (S)CSS-Datei deiner Komponente anpassen, die dann hierarchisch nur für die Komponente und darunter liegende Elemente gelten.


Quellcode der Komponenten

Schauen wir uns kurz die beiden TypeScript-Dateien an. Dort sollte nur wenig Code drinstehen, der in etwa wie folgt aussehen sollte:

header.component.ts:

import { Component, OnInit } from "@angular/core";

@Component({
        selector: "app-header",
        templateUrl: "./header.component.html",
        styleUrls: ["./header.component.scss"]
})
export class HeaderComponent implements OnInit {
        ngOnInit() {}
}

header.component.spec.ts:

import { ComponentFixture, TestBed } from "@angular/core/testing";
import { HeaderComponent } from "./header.component";

describe("HeaderComponent", () => {
        let component: HeaderComponent;
        let fixture: ComponentFixture<HeaderComponent>;

        beforeEach(() => {
                TestBed.configureTestingModule ({
                        imports: [],
                        declarations: [HeaderComponent]
                }).compileComponents();
                fixture = TestBed.createComponent(HeaderComponent);
                component = fixture.componentInstance;
                fixture.detectChanges();
        });

        it("should be created", () => {
                expect(component).toBeTruthy();
        });
});

Falls sie komplett anders aussehen, bspw. könnte es sein, dass im Testscript vorgefertigte Tests drinstehen, dann ersetze sie bitte durch obigen Code. Ersetze auch bitte die beiden TypeScript-Dateien der App-Komponente durch folgenden Code:

app.component.ts:

import { Component, OnInit } from "@angular/core";

@Component({
        selector: "app-root",
        templateUrl: "./app.component.html",
        styleUrls: ["./app.component.scss"]
})
export class AppComponent  implements OnInit {
        title = "angular-demo-application";

        ngOnInit() {}
}

app.component.spec.ts:

import { AppComponent } from "./app.component";
import { ComponentFixture, TestBed } from "@angular/core/testing";

describe("AppComponent", () => {
        let component: AppComponent;
        let fixture: ComponentFixture<AppComponent>;
      
        beforeEach(() => {
                TestBed.configureTestingModule({
                        imports: [],
                        declarations: [AppComponent]
                }).compileComponents();

                fixture = TestBed.createComponent(AppComponent);
                component = fixture.componentInstance;
                fixture.detectChanges();
        });

        it("should be created", () => {
                expect(component).toBeTruthy();
        });
});

Für diese Beispiel-Anwendung bitte ich dich, nach dem Erstellen der Komponente jeweils die beiden TypeScript-Dateien wie oben anzupassen. Ich gehe dann später noch auf die entsprechenden Komponenten-Tests ein.

Als Nächstes benötigen wir zusätzliche Komponenten, die in die Anwendung eingebettet werden sollen. Verwende den Befehl ng generate component, um die unten angegebenen vier Komponenten zu generieren. Achte dabei bitte auf die Schreibweise. Hier kannst du noch mal nachsehen, wie das geht.

  • friend-box: Diese Komponente repräsentiert eine Box, in der die Profile angezeigt werden, denen du folgst.
  • card: Diese Komponente stellt eine Profilkarte dar. Sie enthält ein Bild, darunter den Benutzernamen und eine Beschreibung sowie zwei Schaltflächen: „Like“ und „Message“.
  • profile-row: Hierbei handelt es sich um einzelne Profile, die in den Profilvorschlägen angezeigt werden.
  • proposals: Diese Komponente zeigt die zuvor erwähnten Profilvorschläge an.

Dienste in Angular erstellen

Bevor du die Komponenten selbst anpassen kannst, ist erst noch ein weiterer Schritt durchzuführen. Das Ziel ist es, auf unserem kleinen, sozialen Netzwerk Freunde hinzuzufügen. Die Komponenten FriendBox und ProfileRow müssen dabei miteinander kommunizieren können. Wie du es sicherlich aus anderen sozialen Netzwerken kennst, wenn du auf „Folgen“ klickst, sollte der „Folgen“-Link verschwinden oder deaktiviert werden. Schließlich kannst du einem anderen Account nicht zweimal folgen. Außerdem müssen die Profile in der Freundschaftsbox angezeigt werden. Diese Funktionalität kannst du mit einem Dienst realisieren. In Angular erstellst du einen Dienst mit dem Befehl ng generate service:
C:\Users\Robert\angular-demo-application>ng generate service add-friend
CREATE src/app/add-friend.service.spec.ts (373 bytes)
CREATE src/app/add-friend.service.ts (138 bytes)

C:\Users\Robert\angular-demo-application>
Im Gegensatz zu den Komponenten erzeugt Angular hier jetzt nur zwei Dateien, und zwar direkt im app-Verzeichnis: app-friend-service.spec.ts und app-friend-service.spec.ts. Normalerweise ist es vielleicht angenehmer, Dienste in einem speziellen Unterordner zu haben, insbesondere wenn mehrere Dienste vorhanden sind. Doch für unsere kleine Anwendung reicht es vorerst aus. Die erste TypeScript-Datei enthält wieder die Logik, während die zweite die Unittests beinhaltet. Hier ist der Inhalt des Spec-Files, ähnlich wie bei den Komponenten, aber mit einigen Anpassungen:
import { AddFriendService } from "./add-friend.service";
import { TestBed } from "@angular/core/testing";

describe("AddFriendService", () => {
        let service: AddFriendService;

        beforeEach(() => {
                TestBed.configureTestingModule({});
                service = TestBed.inject(AddFriendService);
        });

        it("should be created", () => {
                expect(service).toBeTruthy();
        });
});

Der Hauptunterschied liegt darin, dass du keine Komponente hast, die mit der Methode createComponent() erzeugt wird. Stattdessen hast du einen Dienst, der mit inject() injiziert wird. Die Datei app-friend-service.ts sollte momentan in etwa so aussehen:

import { Injectable } from "@angular/core";

@Injectable({
        providedIn: "root"
})
export class AddFriendService {
        constructor() { }
}

Im Gegensatz zu den Komponenten steht hier der Dekorator @Injectable, da es sich, wie bereits erwähnt, um einen Dienst handelt. Die Eigenschaft providedIn gibt an, wo der Dienst zur Verfügung steht:

  • „any“: Der Dienst steht der gesamten Anwendung zur Verfügung, und es können auch mehrere Dienstinstanzen vorhanden sein.
  • „root“: Der Dienst steht der gesamten Anwendung zur Verfügung, wobei es nur eine Dienstinstanz gibt – ähnlich einer statischen Klasse.
  • <ModulName>: Wenn du einen Modulnamen als Bezeichner angibst, steht der Dienst nur diesem Modul zur Verfügung.
  • <ServiceName>: Wenn du einen Dienstnamen als Bezeichner angibst, steht der Dienst nur einem anderen Dienst zur Verfügung.

Implementierung des Freundschaftsdienstes

Jetzt wollen wir die Logik des Dienstes implementieren. Er ist dafür verantwortlich, dass du in der Anwendung Freunde hinzufügen kannst:

import { Injectable } from "@angular/core";

@Injectable ({
        providedIn: "root"
})
export class AddFriendService {
        names: string[] = [];
        texts: string[] = [];
        images: number[] = [];

        getImage(index: number) {
                return `/assets/images/cards/${this.images[index]}.webp`;
        }

        addFriend(name: string, text: string, id: number): number {
                if(this.names.length >= 3)
                        return 400;

                this.names.push(name);
                this.texts.push(text);
                this.images.push(id);
                return 200;
        }

        constructor() { }
}

Der Dienst enthält drei Arrays: names, texts und images, in denen die Informationen der Freunde gespeichert sind. Die Methode getImage() verwendet den Index, um den vollständigen Dateinamen mit relativem Pfad abzurufen. Wenn du die Beispiele selbst durchgehst, achte bitte darauf, die Bilddateien korrekt in den Assets-Ordner zu platzieren. Andernfalls könnten sie nicht angezeigt werden oder Fehlermeldungen auftreten. Die Bilder befinden sich im Unterordner assets/images, während die Profilbilder in assets/images/cards zu finden sind. Der Service wird durch die Methode addFriend() aufgerufen, um die entsprechenden Informationen zu den Arrays hinzuzufügen. Ich habe mir überlegt, dass der Service einen Code im Stil eines HTTP-Statuscodes zurückgibt. Vor dem Hinzufügen überprüft er die Array-Eigenschaft length, um festzustellen, ob die maximale Anzahl erreicht wurde. In diesem Fall wird der Fehlercode 400 zurückgegeben. Andernfalls werden die Profilinformationen hinzugefügt, und der Code 200 wird zurückgegeben.


Angular-Spezialitäten

In diesem Beitrag hast du bisher Komponenten als statischen HTML-Code erzeugt. Doch eine der Stärken von Angular liegt darin, Komponenten dynamisch zu erzeugen. Das bedeutet, dass du Komponenten bspw. basierend auf bestimmten Bedingungen anzeigen lassen kannst. Zwei besonders nützliche Funktionen, auf die ich eingehen werde, sind ngIf und ngFor.

Bedingte Anzeige mit „ngIf“

Das Kürzel „ngIf“ steht im Wesentlichen für „Angular-If“ und ermöglicht es, eine Komponente nur dann darzustellen, wenn eine bestimmte Bedingung erfüllt ist. Diese Bedingung kann eine einfache boolesche Variable sein, die entweder true oder false ist. Alternativ kannst du auch eine komplexere Bedingung verwenden. Hierbei können die Variablen auf Eigenschaften zugreifen, auf die die Komponente direkt zugreift. Lass uns jetzt ngIf in der ProfileRowComponent verwenden. Dafür benötigen wir eine kleine Erweiterung im Code. Bitte passe die Datei profile-row.component.ts wie folgt an:
import { AddFriendService } from "../add-friend.service";
import { Component, Input, OnInit } from "@angular/core";

@Component ({
        selector: "app-profile-row",
        templateUrl: "./profile-row.component.html",
        styleUrls: ["./profile-row.component.scss"]
})
export class ProfileRowComponent implements OnInit {
        @Input()
        id: number = 0;

        @Input()
        name: string = "";

        @Input()
        description: string = "";

        @Input()
        canFollow: boolean = true;

        addFriend(name: string, description: string, id: number) {
                let result = this.friendService.addFriend(name, description, id);
                if(result == 200)
                        this.canFollow = false;
                else if(result == 400)
                        alert("Du kannst nicht mehr als 3 Freunde haben.");
                else
                        alert("Unerwarteter Fehler");
        }

        constructor(public friendService: AddFriendService) {}

        ngOnInit(): void {}

        with(object: any): ProfileRowComponent {
                let profile = object as ProfileRowComponent;
                this.id = profile.id;
                this.name = profile.name;
                this.description = profile.description;
                this.canFollow = profile.canFollow;
                return this;
        }
}
Das Flag canFollow dient hierbei als Bedingung. Der Dekorator @Input() ermöglicht es, auf diese Variablen im HTML-Code deiner Angular-Anwendung ähnlich wie auf Attribute zuzugreifen. Die Methode addFriend() führt den Dienst AddFriendService aus und verarbeitet das Ergebnis. Und jetzt kannst du auch die zugehörige HTML-Datei profile-row.component.html anpassen:
<div class="profile-row margin-bottom-15">
        <img src="/assets/images/cards/{{id}}.webp"/>
        <div class="name">
                <b>{{name}}</b><br/>
                <span class="description">{{description}}</span>
        </div>
        <a id="follow" *ngIf="canFollow" (click)="addFriend(name, description, id)">Folgen</a>
</div>

Das angezeigte Bild in dieser Komponente hängt von der jeweiligen ID ab. Die Stringwerte der Attribute, in diesem Fall src, enthalten die Variable id in doppelten, geschweiften Klammern. Dasselbe Prinzip gilt auch für den Profilnamen und die Beschreibung weiter unten. Jetzt kommt die eigentliche Besonderheit: Mit *ngIf gibst du die Bedingung in einem String an, in unserem Fall "canFollow". Das sagt Angular: „Zeige diese Komponente nur an, wenn die Bedingung erfüllt ist“. Zusätzlich verfügt Angular über eigene Ereignisse, auf die es reagiert. In diesem Fall wird die Methode addFriend() aufgerufen, wenn du auf „Folgen“ klickst. Während man in normalem HTML onclick verwenden würde, verwendet Angular das Ereignis (click).


Dynamisches Generieren mit „ngFor“

Die zweite Besonderheit in Angular, die ich hier hervorheben möchte, ist die Fähigkeit, dynamisch mehrere Komponenten zu erzeugen, ohne die genaue Anzahl im Voraus zu kennen. Dies ähnelt einer Schleife und wird durch *ngFor ermöglicht, was für „Angular-For“ steht. Lass uns auch hier den Code entsprechend anpassen. Zuerst die Datei proposals.component.ts:

import { Component, OnInit } from "@angular/core";

@Component({
        selector: "app-proposals",
        templateUrl: "./proposals.component.html",
        styleUrls: ["./proposals.component.scss"]
})
export class ProposalsComponent implements OnInit {
        names: string[] = [
                "First User",
                "Second User",
                "Third User",
                "Fourth User",
                "Fifth User",
                "Sixth User"
        ];

        texts = [
                "This is a card.",
                "That is also a card.",
                "This is anonther card.",
                "That is the fourth card. ",
                "This is the fifth card.",
                "That is the last card."
        ];

        images: number[] = [0, 1, 2, 3, 4, 5];

        constructor() {}

        ngOnInit() {}

        get(index: number) {
                return {
                        name: this.names[index],
                        text: this.texts[index],
                        id: this.images[index]
                };
        }
}
Die Benutzernamen und Beschreibungen haben hier keine tiefere Bedeutung. Du kannst hier auch eigene, beliebige Bezeichnungen einsetzen. Als Nächstes die zugehörige HTML-Datei profile-row.component.html:
<app-profile-row [canFollow]="false" [id]="0" [name]="names[0]" [description]="texts[0]"/>
<div class="margin-bottom-15">
        <b>Freundesvorschl&auml;ge</b>
</div>
<app-profile-row
        *ngFor="let i of [1, 2, 3, 4, 5]"
        [id]="i"
        [name]="names[i]"
        [description]="texts[i]">
</app-profile-row>
Eine weitere bemerkenswerte Eigenschaft zeigt sich in der ersten Zeile des Codes. Hier wird deutlich, dass du auch direkt im HTML-Code die Eigenschaften der Komponentenklasse festlegen kannst. In diesem Fall wird das erste Profil mit „deinem“ Profil gefüllt und durch [canFollow]="false" wird sichergestellt, dass du dir selbst nicht folgen kannst. Die Attribute, die du ändern möchtest, werden dabei in eckige Klammern gesetzt. Und dort, wo die Komponente ProfileRow mit dem Tag <app-profile-row> eingebettet wird, kommt jetzt *ngFor zum Einsatz. Im String "let i of [1, 2, 3, 4, 5]" steht genau das Gleiche drin, was du beispielsweise auch in TypeScript schreiben würdest, in der Form for(let i of [1, 2, 3, 4, 5]).

Implementierung der restlichen Komponenten

Wenn du bis hierhin aktiv mitgemacht hast, lade dir gerne den restlichen Teil des Repositorys herunter, da dieselben Prinzipien auch bei den restlichen Komponenten angewandt werden. Darin sind nämlich noch die angepassten SCSS-Dateien enthalten, um der Anwendung ein ansprechendes Design zu verleihen. Außerdem befinden sich in der Anwendung zwei Icons aus dem Material-Design. Dazu installiere bitte folgendes Package:
C:\Users\Robert\angular-demo-application>ng add @angular/material
ℹ Using package manager: npm
✔ Found compatible package version: @angular/material@16.2.1.
✔ Package information loaded.

The package @angular/material@16.2.1 will be installed and executed.
Would you like to proceed? Yes
✔ Packages successfully installed.
? Choose a prebuilt theme name, or "custom" for a custom theme: (Use arrow keys)
> Indigo/Pink [ Preview: https://material.angular.io?theme=indigo-pink ]
  Deep Purple/Amber [ Preview: https://material.angular.io?theme=deeppurple-amber ]
  Pink/Blue Grey [ Preview: https://material.angular.io?theme=pink-bluegrey ]
  Purple/Green [ Preview: https://material.angular.io?theme=purple-green ]
? Set up global Angular Material typography styles? No
? Include the Angular animations module? Do not include
UPDATE package.json (1121 bytes)
✔ Packages installed successfully.
UPDATE angular.json (3098 bytes)
UPDATE src/index.html (604 bytes)
UPDATE src/styles.scss (510 bytes)
Außerdem ist es notwendig, dass du die Datei app.module.ts wie folgt anpasst:
import { AppComponent } from "./app.component";
import { AppRoutingModule } from "./app-routing.module";
import { BrowserModule } from "@angular/platform-browser";
import { CardComponent } from "./card/card.component";
import { FriendBoxComponent } from "./friend-box/friend-box.component";
import { HeaderComponent } from "./header/header.component";
import { MatIconModule } from "@angular/material/icon";
import { NgModule } from "@angular/core";
import { ProfileRowComponent } from "./profile-row/profile-row.component";
import { ProposalsComponent } from "./proposals/proposals.component";

@NgModule({
        declarations: [
                AppComponent,
                CardComponent,
                FriendBoxComponent,
                HeaderComponent,
                ProfileRowComponent,
                ProposalsComponent
        ],
        imports: [
                AppRoutingModule,
                BrowserModule,
                MatIconModule
        ],
        providers: [],
        bootstrap: [AppComponent]
})
export class AppModule { }

Wenn du das Repository erfolgreich geklont oder heruntergeladen hast und alle notwendigen Bibliotheken installiert sind, sollte die Anwendung jetzt wie folgt aussehen (Klicken zum Vergrößern):


Testen der Komponenten

Nun, da du die grundlegende Mini-Anwendung implementiert hast, ist es an der Zeit, sie zu testen. Dazu lade ich dich auf meinen zweiten Teil Einführung in Angular-Komponenten (Part 2) ein.

Quellenangaben


 

Wer Tests schreibt, sollte zwangsläufig auch Assertions verwenden. Bevor man Assertions verwenden kann, sollte man aber natürlich eine geeignete Assertionsbibliothek auswählen. Dieser Artikel vergleicht fünf der populärsten Bibliotheken: JUnit Assert, TestNG Assert, AssertJ, Hamcrest und Truth.

Weiterlesen

Feature Image der Selenium Conference Chicago 2023

Eröffnung-Keynote

Selenium by numbersUnter dem Motto „meetup automate innovate“ fand vom 28. bis 30. März die Selenium Konferenz in Chicago statt. Ich hatte das Vergnügen, diese besuchen zu. Die Keynote präsentierte Diego Molina von Sauce Labs mit einem Vortrag, in dem er den Stand der Entwicklung umriss (Selenium: State of the Union). Man kann es wohl nicht oft genug sagen, dass Selenium ein Werkzeug zur Browserautomatisierung ist, auf dem Frameworks aufsetzen können. Es sieht sich selbst als „low level“ Bibliothek oder Plattform, welche die Grundfunktionen bereitstellt.

Diego Moline legt besonderen Wert darauf zu betonen, dass das Selenium Projekt ein Gemeinschaftsprojekt ist und durch die Zusammenarbeit mit Anderen das Software-Testen verbessern möchte. Als prominentes Beispiel führt er an, wie Jason Huggins (Selenium) und Simon Stewart (WebDriver) zusammenarbeiten. Des Weiteren gibt er Ausblick auf kommende Verbesserungen und Entwicklungen, die in absehbarer Zukunft verfügbar werden. Wie der Selenium Manager, der die Einrichtung der Treiber vereinfacht. Besonders interessant auch die Entwicklung zu WebDriver BiDi, welches das Potenzial hat, die Browserautomatisierung auf ein neues Level zu heben.

Außerdem wird die Community hinter dem Projekt vorgestellt und einige Wege aufgezeigt, wie man sich in diese einbringen kann. Unter anderem direkt im Chat über IRC oder Slack. Oder das Melden eines Bugs im Tracker auf GitHub.

Verpasste Chancen

As automators we ... Are strategist, planners and advocates Support teams by speeding up feedback loops Help teams by automating more than just checksDie zweite Keynote des Eröffnungstages wurde von Erika Chestnut präsentiert. Ihr Themenschwerpunkt: Verpasste Chancen oder Missed Opportunities. Sie spricht darüber, wie Qualität leidet, wenn die QA-Mitarbeiter in Schubladen gesteckt werden. Denn so viele Bereiche beeinflussen die Qualität eines Produkts, dass man als QA-Verantwortlicher die Chancen ergreifen sollte, wo man sie sieht, um die eigene Firma zu verbessern. Als Tester braucht man ein umfassendes Verständnis für die Software, was sie tut, womit sie interagiert und wie die Kunden sie benutzt. Wenn das nicht klar kommuniziert wird, ist es auch die Aufgabe der QA-Verantwortlichen darauf zu verweisen und Lösungen aufzuzeigen.

Was macht Testautomatisierung?

What does an automator do?Die Keynote des zweiten Konferenztags präsentierte Mark Winteringham vom Ministry of Testing mit der berechtigten Frage, was genau machst du denn in der Testautomatisierung?  Unter Einbeziehung des Publikums geht er der Frage nach, was wir als Testautomatisierer denn täglich, wöchentlich und monatlich so machen. Er berichtet, wie er zu Beginn seiner Karriere als Erstes die Frage stellte, welches Tool er lernen sollte. Eine Analyse des Stellenmarkts zeigt auch, dass immer nach den Werkzeugen gefragt wird, die man kann.

Im weiteren Verlauf kommt er zu dem Punkt, dass die Tools nur Mittel zum Zweck sind. Das Ziel bei der Automatisierung ist, die sich wiederholenden Aufgaben wegzuautomatisieren, Überprüfungen einzubauen, die allen Beteiligten schnell Rückmeldung geben und diese Ergebnisse zu kommunizieren. Es gibt kein Framework, mit dem man alles erschlägt. Unsere Aufgabe ist, das richtige Werkzeug für die anstehende Aufgabe zu wählen, was auch heißt neue Werkzeuge auszuprobieren. Entsprechend auch eine Strategie entwickeln, was automatisiert werden soll, in welcher Reihenfolge und mit welchen Mitteln. Dazu gehört auch die Risikoabschätzung für die Aufgaben. Wo haben wir den größten Nutzen?

Empfehlungen

Ich kann jedem nahelegen, sich die drei verlinkten Keynotes anzusehen. Sie decken eine Menge allgemeingültige Bereiche des weiter gefassten Umfelds der Qualitätssicherung ab. Einige der Folgevorträge gehen zum Teil näher auf angesprochene Themen ein oder richten sich an spezielle Problemstellungen. Alle Vorträge wurden aufgezeichnet und sind auf dem YouTube-Kanal der Selenium Conference verfügbar.

untestable thingsDer Vortrag über WebDriver BiDi bietet einen detaillierteren Einblick in die Entwicklung und was uns dort erwartet. Sehr spezifisch war der Vortrag über Crawler von Noemi Ferrera, wenn auch eher selten in der Anwendung im Alltag des Testers (Shift Left), ist es doch immer hilfreich, außerhalb der eigenen Box etwas zu lernen. Wer kennt es nicht bei dem Test einer Webseite, diesen einen Knopf zu drücken, funktioniert manuell wunderbar, aber aus irgendeinem Grund scheitert die Automatisierung. Wem das vertraut vorkommt, dem sei der Vortrag UNTestable nahegelegt, in dem Shi Ling-Tai einige der gemeinen Konstrukte moderner Webseiten aufzeigt und wie man sie testet.

Fazit

Hier alle Vorträge aufzulisten, würde den Rahmen sprengen. Lest die Kurzbeschreibungen der Vorträge auf der Agenda-Seite der Selenium Conf Webseite. Schaut euch die Aufzeichnungen auf YouTube an. Interessiert euch ein Thema, seht es euch an. Und wenn ihr die Gelegenheit habt, persönlich auf ein Treffen zu gehen, nutz diese. Die Gespräche mit anderen Teilnehmern zwischen den Vorträgen waren auch sehr aufschlussreich. Menschen aus den unterschiedlichsten Branchen, mit ähnlichen Herausforderungen und unterschiedlichen Lösungen.

Mir hat es Spaß gemacht, die Konferenz zu besuchen und meine Auffassung, was alles in den Bereich der Qualitätssicherung fällt, wurde deutlich erweitert.  Wenn ihr ein Meetup besucht habt oder eines empfehlen wollt, lasst es mich in den Kommentaren wissen. Vielleicht trifft man sich dann schon bald persönlich.

Cloud Performance Testing Tools

Die Cloud Performance Testing Tools bieten die Möglichkeit, die Tests auf einer Cloud-Infrastruktur auszuführen. Der Hauptvorteil von Cloud-basierten Performance Test ist die Simulation verschiedener geografischer Standorte, die dabei helfen, die Last auf den Server aus verschiedenen Regionen, Ländern und Gebieten anzuwenden.

Das Konzept des Cloud-basierten Performance Test besteht darin, das Skript auf einem lokalen Computer zu erstellen, das Skript in der Cloud hochzuladen, das Online-Szenario zu erstellen, den Test auszuführen und den Bericht zu erstellen. Viele Cloud-basierte Tools haben ihr eigenes Skripting-Tool, während einige von ihnen Open-Source-Tools wie Apache JMeter verwenden.Das schnelle Wachstum der Cloud-basierten Technologie erhöht auch die Verwendung von Cloud-basierten Testwerkzeugen. Sie sind leistungsstark und einfach zu bedienen.

Vorteile:

  • Keine Hardwarekosten für den Lastgeneratoren
  • Es ist kein zusätzlicher Aufwand erforderlich, um den Lastgeneratoren einzurichten
  • Generell niedrigere Kosten als die Einrichtung vor Ort
  • Skalierbar
  • Keine Wartungskosten
  • Einfach zu verwenden
  • Meistens Open-Source-Tool-Unterstützung für Scripting (nicht alle)
  • Support stegt zur Verfügung
  • Echtzeitanalyse mit Live-Monitoren
  • Meistens Integration von Monitoring Tools
  • Geografische Standortsimulation
  • Reporting

Nachteile

  • Thema Datensicherheit und Datenchutz
  • Problem beim Testen der Intranet-Anwendung
  • Es ist gelerell  billiger, aber nicht kostenlos
  • Oft vebunden mit Netzwerkproblemen
  • Fehlende Unterstützung aller Protokolle
  • Manchmal Kompatibilitätsproblemen mit die Open-Source-Tool-Skripte
  • Fehlende Analyse der Testergebnisse
  • Nicht alle Tools unterstützen die Integration externer Monitoring

 

Top Cloud Performance Testing Lösungen

VERWANDTE BEGRIFFE

 

Im Zusammenhang mit der Qualitätssicherung von Software hat sich der Begriff Mutation Testing etabliert. Mithilfe von Mutationstests werden Änderungen am Quellcode durchgeführt, um die Tests zu testen, die die Software testen. 🙂

Ein populäres Werkzeug für solche Tests ist Stryker. Sehen wir uns an, wie uns Stryker dazu bewegt, qualitativere Tests zu schreiben.

Beispielprogramm – Setup

Das Beispielprojekt findet ihr auf GitHub.

Für dieses Tutorial verwenden wir Visual Studio Code und npm. PowerShell oder CMD öffnen und folgende Befehle eingeben, um die benötigten Ordner zu erstellen. Unter src wird unser Softwarecode liegen (da wir für Mutationstests den Quellcode brauchen) und die entsprechenden Tests unter test:

mkdir BaseConverter
cd BaseConverter
mkdir src
mkdir test
code .

Nun ist Visual Studio Code offen und wir können mit der Initialisierung des Projekts beginnen. Dazu im Terminal aus VS Code npm init eintippen und die Anweisungen im Terminal befolgen. Optional können die Felder ausgelassen werden und später in package.json nachträglich geändert werden.

Dependencies installieren

Als Testrunner und Assertion Library werden Mocha und Chai, sowie ts-node für die TypeScript Tests installiert. Um die Type Definitions für Mocha und Chai zu installieren, benötigen wir @types/chai und @types/mocha Packages.

npm install mocha chai ts-node --save-dev
npm install @types/chai @types/mocha --save-dev

Da wir mit einem TypeScript Projekt arbeiten, sollten wir tsc --init ausführen, was uns standardmäßig eine tsconfig.json erstellt.

Tests implementieren

Wir wollen den Ansatz von Test-Driven-Development verfolgen und zuerst die Tests für das Projekt BaseConverter implementieren, welches 2 Funktionen besitzt: Konvertieren einer Dezimalzahl ins binäre Format und andersherum. Dazu eine Datei unter test anlegen und die entsprechenden Tests schreiben:

import { expect } from 'chai';
import 'mocha';

describe('Base Converter Tests', () => {

    it('Should convert positive decimal to binary.', () => {
        expect(convert_decimal_to_binary(8))
            .to.equal('01000', 'Converted number was not as expected!');
    });

    it('Should convert negative decimal to binary.', () => {
        expect(convert_decimal_to_binary(-8))
            .to.equal('11000', 'Converted number was not as expected!');
    });

    it('Should convert negative binary to decimal.', () => {
        expect(convert_binary_to_decimal('11000'))
            .to.equal(-8, 'Converted number was not as expected!');
    });

    it('Should convert positive binary to decimal.', () => {
        expect(convert_binary_to_decimal('01000'))
            .to.equal(8, 'Converted number was not as expected!');
    });

});

Bevor wir die Tests starten, müssen wir in package.json „scripts“: { „test“: .. } anpassen, damit mocha die TypeScript Tests findet und ausführen kann:

"mocha -r ts-node/register test/**/*.ts"

Die Tests können einfach mit dem Befehl npm test ausgeführt werden, diese werden jedoch nicht kompiliert, weil die Methoden convert_decimal_to_binary() und convert_binary_to_decimal() fehlen. Also unter src/BaseConverter.ts diese implementieren:

function convert_decimal_to_binary(n: number): string {
    if(n == 0 ) return '0';

    const sign: string = n < 0 ? '1' : '0';

    let absoluteNumber: number = Math.abs(n);
    let convertedDecimal = '';

    while(absoluteNumber > 0) {
        const reminder: number = absoluteNumber % 2;
        convertedDecimal += reminder.toString();
        absoluteNumber = Math.floor(absoluteNumber / 2);
    }
    convertedDecimal += sign;
    const separator = '';
    return convertedDecimal.split(separator).reverse().join(separator);
}

function convert_binary_to_decimal(binaryNumber: string): number {
    const sign: number = binaryNumber.at(0) === '1' ? -1 : 1;
    const absoluteBinary: string = binaryNumber.slice(1);
    const absoluteDecimal: number = absoluteBinary.split('').reverse()
        .reduceRight((_, curr, index) => _ + Math.pow(2, index) * parseInt(curr), 0);
    return sign * absoluteDecimal;
}

export {
    convert_decimal_to_binary,
    convert_binary_to_decimal
};

Wenn alles richtig implementiert wurde, sollten die Tests jetzt alle erfolgreich laufen.

Stryker einsetzen

Die Tests waren alle erfolgreich, wir haben also abgeprüft, ob die implementierten Funktionen sowohl positive als auch negative Zahlen von einem Zahlensystem ins andere konvertiert. Mit Stryker sollten diese Tests auf Vollständigkeit überprüft werden. Folgende Befehle eingeben und wieder die Anweisungen im Terminal befolgen:

npm install --save-dev @stryker-mutator/core
npx stryker init

Ein letztes Package muss für unser TypeScript Projekt installiert werden, Stryker TypeScript Checker:

npm install --save-dev @stryker-mutator/typescript-checker
Stryker konfigurieren

Die Konfigurationsdatei stryker.conf.json muss zuletzt noch angepasst werden, damit Stryker richtig konfiguriert wird. Diese beinhalten u.a. Optionen für unseren Test Runner (in unserem Fall Mocha), welche Dateien mutiert werden sollen und TypeScript-spezifische Konfigurationen:

"mochaOptions": {
    "spec": ["test/**/*.js"],
    "package": "./package.json"
  },
"checkers": ["typescript"],
"tsconfigFile": "tsconfig.json",
"buildCommand": "tsc -b",
"mutate": [ "src/**/*.ts"]

Nach der Ausführung von npx stryker run gibt es im Ordner reporter einen HTML-Report.

Wir haben einen Mutation Score von 90% erreicht. Wenn wir uns den geänderten Quellcode ansehen, können wir herausfinden, welche Mutanten nicht von unseren Tests getötet worden sind. Hier haben wir einen Testfall vergessen, und zwar einen direkten Test mit 0:

it('Should convert decimal zero to binary.', () => {
        expect(convert_decimal_to_binary(0))
            .to.equal('0', 'Converted number was not as expected!');
});

Nachträglich hinzugefügt erreichen wir trotzdem keinen Mutation Score von 100%.

Warum? Laut Stryker hat der Mutant aus Zeile 2 mit der geänderten Bedingung zu false überlebt. Das ist auch richtig so, denn bei false wird die Zeile bei einem Parameter von 0 übersprungen und in Zeile 4 abgeprüft, ob diese Zahl unter 0 liegt. Wenn nicht, wird die Konstante sign auf 0 gesetzt, welche im Quellcode in Zeile 14 zum Rückgabewert der Funktion eingefügt wird convertedDecimal += sign; Die Programme sind also äquivalent! Die Abfrage auf n === 0 kann im Quellcode weggelassen werden und erreichen somit vollständige Testüberdeckung!

Ist das aber trotzdem richtig? Nicht ganz. Hier kommen die Einschränkungen von Stryker ins Spiel. Die Methode convert_binary_to_decimal() wurde gar nicht mutiert, da Stryker nicht die entsprechenden Mutationsoperatoren für die Logik hinter dem Code besitzt.

Zusammenfassung

Durch das Werkzeug Stryker zur Unterstützung von Mutationstests haben wir die Möglichkeit, unsere Tests auf Vollständigkeit zu testen. Falls es Mutanten eines Programms gibt, die trotz unserer Tests überleben, sollten wir uns Gedanken über bessere Tests machen. Nichtsdestotrotz kommt es auf die tatsächliche Implementierung der Software an und wie stark ein Werkzeug ein Programm mutieren kann, andernfalls kann es zu false positives kommen, da wir, wie in der obigen Grafik zu sehen ist, einen Mutationsscore von 100% erreichen, obwohl wir nicht alles getestet haben (z.B. Testfall von 0 aus dem Binärsystem ins Dezimalsystem fehlt, oder falls ein String für convert_binary_to_decimal() leer ist…).

Lizenzierte Performance Testing Tools

Lizenzierte Performance Test-Tools sind kommerzielle Tools. Diese Tools können nur durch den Kauf des Lizenzpakets bei dem jeweiligen Unternehmen verwendet werden. Die Testtool-Unternehmen bieten Lizenzen basierend auf:

  • Benutzeranzahl: Lizenz zum Generieren einer bestimmten Anzahl von Last Benutzer
  • Protokoll: Protokollspezifische Lizenz
  • Zeitraum: Lizenz für einen bestimmten Zeitraum

Einige Unternehmen bieten die Möglichkeit, ein benutzerdefiniertes Paket der Lizenzen zu erstellen.Abgesehen von der kostenpflichtigen Lizenz, stellen Unternehmen auch die Probe(Trial) Version des Tools aus, die eine begrenzte Benutzerzahl, generische Protokollunterstützung oder eine begrenzte Nutzungsdauer des Tools hat. Eine Testversion kann hilfreich sein, um einen PoC (proof of concept) durchzuführen.

Vorteile:

  • Umfangreiche Protokollunterstützung
  • Ein spezielles Support-Team steht normalerweise zur Verfügung
  • Normalerweise gutes Knowledge Base und Community
  • Weniger Herausforderungen bei der Skripterstellung als bei Open-Source-Tools
  • Benutzerfreundliche und einfache GUI
  • Unterstützt erweiterte Skriptaufzeichnung
  • Viele Tools unterstützen lokale und Cloud-Infrastrukturen
  • Einfache Integration mit Monitoren  und Profilers Tools
  • Live-Monitoren des Tests
  • Mehr Genauigkeit im Ergebnis
  • Funktion zur detaillierten Ergebnisanalyse
  • Gute Berichtsfunktion
  • Das Vertrauen des Kunden

Nachteile

  • Lizenzkosten
  • Meistens Betriebssystem spezifisch
  • Meistens ist die Installation schwieriger als bei Open-Source-Tools
  • Etwas schwierig, auf die neue Versionen zu aktualisieren
  • Sehr wenig oder keine Anpassung
  • Meistens für Für die ON Premise Version ist Hardware erforderlich
  • Die Generierung der Last Benutzer ist auf Lizenzen mit Benutzeranzahl beschränkt. Es kann das Benutzerlimit nicht überschreiten, obwohl die Hardware verfügbar ist.
  • Die Migration von einem Tool zum anderen ist nicht einfach. Es braucht Zeit, Geld und Mühe.

 

Top Lizenzierte Performance Testing Tools

  • LoadRunner 
  • Microfocus Performance Center
  • NeoLoad
  • SoapUI /LoadUI
  • Radview WebLOAD
  • SmartMeter.io
  • SmartBear LoadNinja
  • IBM Rational Performance Tester
  • WAPT
  • StressStimulus
  • AppLoader
  • Appvance.ai

VERWANDTE BEGRIFFE

 

Open Source Performance Testing Tools

Open Source-Performance-Testing-Tools sind frei verfügbar und benötigen keine kommerzielle Lizenz. Diese Tools sind eher einfach einzurichten als ein kommerziell lizenziertes Tool, aber auch mit bestimmte Einschränkungen verbunden.

 

 

 

Vorteile:

  • Keine Lizenzkosten
  • Die meisten sind Plattform unabhängig(laufen auf jedem Betriebssystem)
  • einfach zu installieren und einfach aufzurüsten
  • keiner explizites Hardware erforderlich, es sei denn, die Benutzerlast ist zu hoch
  • Unbegrenzte Benutzerlastgenerierungsfunktion (abhängig von der Lasterzeuger-Maschinenkonfiguration)
  • Benutzerfreundliche und einfache GUI
  • Die meisten Tools unterstützen die Skriptaufzeichnung
  • Meistens die grundlegenden Funktionen sind verfügbar
  • Meistens die Migration von einem Open-Source-Tool zu einem anderen Tool ist einfach (aber nicht in allen Fällen)

Nachteile

  • Begrenzte Protokollunterstützung
  • Kein dediziertes Support-Team
  • Skriptherausforderungen für komplexe Szenarien
  • Eingeschränkte Plugin-Unterstützung
  • Mangel an erweiterten Funktionen
  • Integration nur mit spezifischem Monitoren Tools
  • Programmiersprache Abhängigkeit(z.B. Beanshell, Groovy) für komplexe Logik
  • Fehlende Berichtsfunktion
  • Einschränkung der eingehenden Analyse

 

Top Open Source Performance Testing Tools

VERWANDTE BEGRIFFE

 

Übersicht

Testen Sie ein neues Tool oder bereiten eine Demo/POC vor? Hier sind einige nützliche Websites, die Sie immer zur Hand haben sollten.

Testseiten werden immer zum Üben benötigt. Sei es für Kurse, Workshops, Webinare, das Testen neuer Tools usw. Hier eine Liste von Testseiten, die sich zum Ausprobieren eignen.

Demo Sites:

 

  • Demoblaze ist ein beispielhaftes E-Commerce-System, das von BlazeMeter bereitgestellt wird, um die Automatisierung mit JMeter zu üben und es mit Blazemeter auszuführen. Es enthält sogar einen Abschnitt mit einem Video, damit Sie das HLS-Protokoll testen können.
  • OpenCart Für Schulungen kann es hilfreich sein, eine Version des Open-Source-E-Commerce-Systems OpenCart zu installieren.
  • WPmobilepack ist ein sehr einfaches E-Commerce-System, das sich hervorragend zum Testen geeignet.
  • Juice-Shop ist eine bekannte Website zum Testen von Sicherheitslücken.
  • Computer-Database ist eine von Gatling (Performance Test) bereitgestellte Testseite. Es ist eine Website mit einer Computerdatenbank, auf der es eine Liste mit mehreren Spalten und einem Suchfilter gibt.
  • JPetStore Demo ist ein von OctoPerf (Cloud SaaS Performance Testing) bereitgestelltes Testsystem. Wie der Name schon sagt, handelt es sich um eine Demo-Tierhandlung mit verschiedenen Angeboten, Filtern usw.
  • DemoQA enthält eine Vielzahl an Elementen, die auf Websites typisch sind. Die Seite ist gut darauf ausgerichtet, Testautomatisierung zu üben, mit verschiedenen Objekten in unterschiedlichem Kontext. Elemente einer Liste, die per Drag & Drop geordnet werden, Eingänge verschiedener Formate, Buttons und Alarme, usw.
  • SwagLabs ist eine weitere Demo-Web-Storefront, die zum Testen von Anmelde- und Warenkorbabläufen nützlich ist. Ein wichtiger Punkt bei diesem ist, dass es 4 verschiedene Logins gibt, die Sie für verschiedene Erfahrungen für dieselbe Site verwenden können; normaler, gesperrter, problematischer Benutzer und Benutzer mit Leistungsstörungen.
  • Selenium Easy ähnelt DemoQA, wird aber von Smartbear CrossBrowserTesting bereitgestellt.
  • Test Pages for automating ist voll von Beispielseiten, von Alan Richardson, auch bekannt als „Evil Tester“, die für automatisierte Prüfungen verwendet werden können.
  • The-Internet Dave Haeffner, Schöpfer von Elemental Selenium, bietet diese Seite an, um verschiedene Dinge wie Dropdown-Menüs, Hover usw. zu testen.
  • UI Testing Playground Diese Website ist möglicherweise kleiner als die anderen, enthält jedoch Edge Cases für Ladeverzögerungen, Mouseover-Verhalten, dynamische IDs und Automatisierungsprobleme, die sich aus verborgenen Schichten ergeben.
  • Basic Calculator bietet ein Objekt mit grundlegenden Funktionen, mit denen Sie Ihren ersten Versuch unternehmen können, Selenium zu verwenden, bereitgestellt von Mike Talks.
  • Swagger Pet Store ist eine andere Tierhandlung, die von Swagger.io bereitgestellt wird.
  • GlobalsQA  
  • Bank App(Parasoft) 
  • Advance UI Flows 
  • E2E Booking Web App(Katalon) 
  • Ultimate QA  -Formulare, Zielseite, Seiteninteraktionen
  • Basic Address Book  
  • CRM  
  • Telerik Demos  -SPAs, JQuery Seiten, andere komplexe UIs
  • App VWO  -Erweiterte Mausbewegung, Aktion, Frame Switch 
  • Magento Store 
  • React Shoping Cart
  • Cypress Real World App
  • ACME demo Banking(Applitools)
  • Restfull Booker
  • Compendium
  • Conduit (Angular)
  • OpenUI5

 

Public API Liste

 

Verwandte Themen

Abb. 1 - Postman-Logo

Übersicht

In diesem Artikel möchte ich demonstrieren, wie sich auf einfache Weise eine Swagger Spezifikation in Postman importieren lässt, was uns letztlich eine Collection von Testfall Grundgerüsten für alle spezifizierten Endpunkte anlegt.

Swagger und OpenAPI

Abb. 2 - SwaggerLogo

Abb. 1 – Swagger Logo

 

Bei OpenAPI handelt es sich um einen Industriestandard zur Beschreibung von REST-konformen API‘s, der von der OpenAPI Initiative verwaltet wird.
Swagger stellt letztlich ein Open-Source Framework für die Entwicklung von API’s für http-Webservices dar, das konform zur OpenAPI-Spezifikation ist. Swagger ist also das zugehörige Toolset.

Postman

Das Postman Projekt wurde 2012 ins Leben gerufen und ist ein skalierbares Tool für API-Integrationstests, dass sich in CICD Pipelines integrieren lässt. Inszwischen hat das Tool über 4 Mio. Nutzer, da es einige große Vorteile mit sich bringt:

  • Einsatz von Test-Collections – Hilfe bei der Organisation von Test-Suites, durch Strukturierungsmöglichkeiten innerhalb abgeschlossener Test-Collections durch Unterordner und multiple Requests.
  • Kollaboration – Es können mehrere Personen an einer Testsuite arbeiten, einmal durch den manuellen Import/Export aber auch durch die Verwendung von Repositories (Premium Version)
  • Environments – Es ermöglicht uns verschiedene Test-Umgebungen zu definieren und diese auf Test-Collections anzuwenden.
  • Debugging – Die Postman Konsole hilft bei der Fehleranalyse
  • CICD – Eine Integration in CICD Pipelines wird unterstützt. Hier sei vor allem das Kommandozeilentool Newman erwähnt, das in der CICD Automatisierung Verwendung findet.

Swagger Specification

Schauen wir uns exemplarisch eine REST-API in der Swagger-UI an:

Abb. 3 - SwaggerUI

Abb. 2 – SwaggerUI

oder die API-Docs die aus der Schnittstelle generiert werden:

Abb. 4 – Swagger Docs

Abb. 3 – Swagger Docs

Swagger Imports in Postman

Es gibt verschiedene Wege, eine Swagger-API Spezifikation in Postman hochzuladen und daraus die Grundgerüste für unsere Testfälle generieren zu lassen. Der Import wird hier z.B. im JSON Format durchgeführt, es sind aber auch andere Formate möglich.

Import via Datei (z.B. JSON)

Wir wollen eine Textdatei, im JSON Format, hochladen die in etwa wie folgt aussieht:

Abb. 5 – Datei Import

Abb. 4 – Datei Import

 

Wir wählen also in Postman den Button „Import“ im linken Bereich oben (Scratch Pad), wo wir auch unsere Collections anlegen können.

Abb 6 – Postman Scratch Pad

Abb. 5 – Postman Scratch Pad

 

Der folgende Dialog gibt uns verschiedene Auswahlmöglichkeiten, wir wählen „File“ und „Upload Files“:

Abb 7 – Postman File-Import Dialog

Abb. 6 – Postman File-Import Dialog

 

Im nächsten Dialog bestätigen wir mit „Import“:

Abb 8 – Postman File-Import Dialog (2)

Abb. 7 – Postman File-Import Dialog (2)

 

Wie wir in Abbildung 9 sehen, wird eine Projektstruktur in Reiter Collections angelegt.

Abb 9 – Postman Test-Collection

Abb. 8 – Postman Test-Collection

Import via Link zur Swagger Spezifikation

Hier wählen wir im Import Dialog die Option „Link“ und bestätigen mit „Continue“.

Abb 10 – Postman Import via Link

Abb. 9 – Postman Import via Link

 

Im folgenden Dialog bestätigen wir mit Import.

Abb 11 – Postman Import via Link (2)

Abb. 10 – Postman Import via Link (2)

 

Im Folgenden sehen wir, dass eine weitere Collection angelegt wurde, die der Ersten entspricht.

Abb 12 – Postman Test Collection (2)

Abb. 11 – Postman Test Collection (2)

Import via Raw-Text

Wir können aber auch einfach den Text (JSON) in den Postman Dialog kopieren und importieren.

Abb 13 – Postman Import via Raw-Text

Abb. 12 – Postman Import via Raw-Text

 

Vorbereitungs und Testroutinen

Wir müssen allerdings beachten, dass hier nur die reinen Requests, ohne Authentifizierung, Prerequisites oder Testscripts angelegt werden (Abb. 13, Abb. 14).

Die Authentifizierung/Authorisierung, Testvorbereitungslogik und die eigentliche Testlogik, müssen wir also selbst implementieren.

Abb 14 – Postman Testscripts

Abb. 13 – Postman Testscripts

 

Abb 15 – Postman Vorbereitungs-Skripte

Abb. 14 – Postman Vorbereitungs-Skripte

 

Import aus Code-Repository

Die Import-Funktion aus einem Git-Repository heraus ist eine der Premium-Funktionen von Postman und wird an dieser Stelle daher nicht im Detail erörtert. Es sei nur darauf verwiesen, dass dies mit dem sog. Postman-Workspace ebenfalls, mit wenigen einfachen Schritten, möglich ist.

 

Verwandte Themen