Entwicklung durch Testen: Verbesserung der Fähigkeiten

Im vorherigen Artikel haben wir theoretische Aspekte untersucht. Es ist Zeit zu üben.

Bild

Lassen Sie uns eine einfache Implementierung des Stacks in JavaScript mithilfe der Entwicklung durch Testen durchführen.

Stack - eine Datenstruktur, die nach dem Prinzip von LIFO organisiert ist: Last In, First Out. Es gibt drei Hauptoperationen auf dem Stapel:

push : Element hinzufügen
Pop : Löscht ein Element
Peek : Kopfelement hinzufügen

Erstellen Sie eine Klasse und nennen Sie sie Stapel. Angenommen, der Stapel hat eine feste Kapazität, um die Aufgabe zu verkomplizieren. Hier sind die Eigenschaften und Implementierungsfunktionen unseres Stacks:

Gegenstände : Gegenstände auf dem Stapel. Wir werden ein Array verwenden, um den Stack zu implementieren.
Kapazität : Stapelkapazität.
isEmpty () : Gibt true zurück, wenn der Stapel leer ist, andernfalls false.
isFull () : Gibt true zurück, wenn der Stapel die maximale Kapazität erreicht, d. h. wenn Sie kein weiteres Element hinzufügen können. Andernfalls wird false zurückgegeben.
push (Element) : Fügt ein Element hinzu. Gibt Full zurück, wenn der Stack voll ist.
pop : Entfernt das Element. Gibt leer zurück, wenn der Stapel leer ist.
peek () : Fügt ein Kopfelement hinzu.

Wir werden zwei Dateien stack.js und stack.spec.js erstellen . Ich habe die Erweiterung .spec.js verwendet, weil ich daran gewöhnt bin, aber Sie können .test.js verwenden oder ihm einen anderen Namen geben und ihn in __tests__ verschieben .

Da wir die Entwicklung durch Testen üben, werden wir einen fehlgeschlagenen Test schreiben.

Überprüfen Sie zunächst den Konstruktor. Um die Datei zu testen, müssen Sie die Stack-Datei importieren:

const Stack = require('./stack') 

Für diejenigen, die daran interessiert sind, warum ich hier keinen Import verwendet habe, unterstützt die neueste stabile Version von Node.js diese Funktion heute nicht. Ich könnte Babel hinzufügen, aber ich möchte dich nicht überladen.

Wenn Sie eine Klasse oder Funktion testen, führen Sie einen Test aus und beschreiben Sie, welche Datei oder Klasse Sie testen. Hier geht es um den Stapel:

 describe('Stack', () => { }) 

Dann müssen wir überprüfen, ob beim Initialisieren des Stapels ein leeres Array erstellt wird, und wir stellen die richtige Kapazität ein. Also schreiben wir den folgenden Test:

 it('Should constructs the stack with a given capacity', () => { let stack = new Stack(3) expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) 

Beachten Sie, dass wir toEqual verwenden und toBe nicht für stack.items verwenden, da diese nicht auf dasselbe Array verweisen.

Führen Sie nun den Garntest stack.spec.js aus . Wir führen Jest in einer bestimmten Datei aus, weil wir nicht möchten, dass andere Tests beschädigt werden. Hier ist das Ergebnis:

Bild

Stack ist kein Konstruktor . Natürlich. Wir haben unseren Stack noch nicht erstellt und keinen Konstruktor erstellt.

Erstellen Sie in stack.js Ihre Klasse, Ihren Konstruktor und exportieren Sie die Klasse:

 class Stack { constructor() { } } module.exports = Stack 

Führen Sie den Test erneut aus:

Bild

Da wir die Elemente im Konstruktor nicht festgelegt haben, erwartete Jest, dass die Elemente im Array gleich [] sind, aber sie wurden nicht definiert. Dann sollten Sie die Elemente initialisieren:

 constructor() { this.items = [] } 

Wenn Sie den Test erneut ausführen, wird der gleiche Fehler für die Kapazität angezeigt. Daher müssen Sie auch die Kapazität festlegen:

 constructor(capacity) { this.items = [] this.capacity = capacity } 

Führen Sie den Test aus:

Bild

Ja! Test bestanden . Hier ist was TDD ist. Ich hoffe, jetzt macht das Testen für Sie mehr Sinn! Weiter?

isEmpty


Um isEmpty zu testen, initialisieren wir einen leeren Stapel, testen, ob isEmpty true zurückgibt, fügen ein Element hinzu und überprüfen es erneut.

 it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { let stack = new Stack(3) expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) 

Wenn Sie den Test ausführen, sollte der folgende Fehler angezeigt werden:

 TypeError: stack.isEmpty is not a function 

Um dieses Problem zu lösen, müssen wir isEmpty in der Stack- Klasse erstellen:

 isEmpty () { } 

Wenn Sie den Test ausführen, sollte ein weiterer Fehler angezeigt werden:

 Expected: true Received: undefined 

IsEmpty wird nichts hinzugefügt. Der Stapel ist leer, wenn keine Elemente enthalten sind:

 isEmpty () { return this.items.length === 0 } 

isFull


Dies ist dasselbe wie isEmpty , da diese Übung diese Funktion mit TDD testet. Die Lösung finden Sie ganz am Ende des Artikels.

Drücken Sie


Hier müssen wir drei Dinge testen:

  • Ein neues Element muss oben im Stapel hinzugefügt werden.
  • push gibt "Full" zurück, wenn der Stack voll ist;
  • Ein kürzlich hinzugefügter Artikel muss zurückgegeben werden.

Erstellen Sie einen weiteren Block mit description for push . Wir setzen diesen Block in den Hauptblock.

 describe('Stack.push', () => { }) 

Element hinzufügen


Erstellen Sie einen neuen Stapel und fügen Sie ein Element hinzu. Das letzte Element im Elementarray sollte das Element sein, das Sie gerade hinzugefügt haben.

 describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { let stack = new Stack(3) stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) }) 

Wenn Sie den Test ausführen, werden Sie feststellen, dass Push nicht definiert ist und dies normal ist. push benötigt einen Parameter, um dem Stapel etwas hinzuzufügen:

 push (element) { this.items.push(element) } 

Test erneut bestanden. Hast du etwas bemerkt? Wir behalten eine Kopie dieser Zeile:

 let stack = new Stack(3) 

Das ist sehr nervig. Glücklicherweise haben wir eine beforeEach- Methode, mit der Sie vor jedem Testlauf einige Anpassungen vornehmen können. Warum nicht davon profitieren?

 let stack beforeEach(() => { stack = new Stack(3) }) 

Wichtig:



Der Stapel muss vor jedem deklariert werden . Wenn Sie es in der beforeEach- Methode definieren, wird die Stapelvariable nicht in allen Tests definiert, da sie sich nicht im richtigen Bereich befindet.

Wichtiger als wichtig: Wir müssen auch eine afterEach- Methode erstellen. Die Stapelinstanz wird jetzt für alle Tests verwendet. Dies kann einige Schwierigkeiten verursachen. Fügen Sie unmittelbar nach jedem diese Methode hinzu:

 afterEach(() => { stack.items = [] }) 

Jetzt können Sie die Stapelinitialisierung in allen Tests entfernen. Hier ist die komplette Testdatei:

 const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should constructs the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) describe('Stack.push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) }) }) 

Rückgabewertprüfung


Es gibt einen Test:

 it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) }) 

Wenn Sie den Test ausführen, erhalten Sie:

 Expected: 2 Received: undefined 

Nichts kehrt in Push zurück ! Wir müssen das beheben:

 push (element) { this.items.push(element) return element } 

Geben Sie voll zurück, wenn der Stapel voll ist


In diesem Test müssen wir zuerst den Stapel füllen, ein Element hinzufügen, sicherstellen, dass dem Stapel nichts Zusätzliches hinzugefügt wurde und der Rückgabewert Voll ist .

 it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') }) 

Dieser Fehler wird angezeigt, wenn Sie den Test ausführen:

 Expected: 3 Received: 4 

Also wird der Artikel hinzugefügt. Das wollten wir. Zuerst müssen Sie überprüfen, ob der Stapel voll ist, bevor Sie etwas hinzufügen:

 push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element } 

Test bestanden.

Übung: Pop und Peek

Es ist Zeit zu üben. Testen und implementieren Sie Pop and Peek .

Tipps:

  • Pop ist Push sehr ähnlich
  • Peek ist auch wie Push
  • Bisher haben wir den Code nicht überarbeitet, da dies nicht erforderlich war. In diesen Funktionen kann es eine Möglichkeit geben, Ihren Code zu überarbeiten, nachdem Sie die Tests geschrieben und bestanden haben. Machen Sie sich keine Sorgen, ändern Sie den Code, dafür sind Tests erforderlich.

Schauen Sie sich die unten stehende Lösung nicht an, ohne es selbst zu versuchen. Der einzige Weg zum Fortschritt besteht darin, zu versuchen, zu experimentieren und zu üben.

Lösung


Wie ist die Übung? Hast du es getan Wenn nicht, lassen Sie sich nicht entmutigen. Das Testen kostet Zeit und Mühe.

 class Stack { constructor (capacity) { this.items = [] this.capacity = capacity } isEmpty () { return this.items.length === 0 } isFull () { return this.items.length === this.capacity } push (element) { if (this.isFull()) { return 'Full' } this.items.push(element) return element } pop () { return this.isEmpty() ? 'Empty' : this.items.pop() } peek () { return this.isEmpty() ? 'Empty' : this.items[this.items.length - 1] } } module.exports = Stack const Stack = require('./stack') describe('Stack', () => { let stack beforeEach(() => { stack = new Stack(3) }) afterEach(() => { stack.items = [] }) it('Should construct the stack with a given capacity', () => { expect(stack.items).toEqual([]) expect(stack.capacity).toBe(3) }) it('Should have an isEmpty function that returns true if the stack is empty and false otherwise', () => { expect(stack.isEmpty()).toBe(true) stack.items.push(2) expect(stack.isEmpty()).toBe(false) }) it('Should have an isFull function that returns true if the stack is full and false otherwise', () => { expect(stack.isFull()).toBe(false) stack.items = [4, 5, 6] expect(stack.isFull()).toBe(true) }) describe('Push', () => { it('Should add a new element on top of the stack', () => { stack.push(2) expect(stack.items[stack.items.length - 1]).toBe(2) }) it('Should return the new element pushed at the top of the stack', () => { let elementPushed = stack.push(2) expect(elementPushed).toBe(2) }) it('Should return full if one tries to push at the top of the stack while it is full', () => { stack.items = [1, 2, 3] let element = stack.push(4) expect(stack.items[stack.items.length - 1]).toBe(3) expect(element).toBe('Full') }) }) describe('Pop', () => { it('Should removes the last element at the top of a stack', () => { stack.items = [1, 2, 3] stack.pop() expect(stack.items).toEqual([1, 2]) }) it('Should returns the element that have been just removed', () => { stack.items = [1, 2, 3] let element = stack.pop() expect(element).toBe(3) }) it('Should return Empty if one tries to pop an empty stack', () => { // By default, the stack is empty expect(stack.pop()).toBe('Empty') }) }) describe('Peek', () => { it('Should returns the element at the top of the stack', () => { stack.items = [1, 2, 3] let element = stack.peek() expect(element).toBe(3) }) it('Should return Empty if one tries to peek an empty stack', () => { // By default, the stack is empty expect(stack.peek()).toBe('Empty') }) }) }) 

Wenn Sie sich die Dateien ansehen, werden Sie sehen, dass ich die dreifache Bedingung in Pop and Peek verwendet habe. Das habe ich überarbeitet. Die alte Implementierung sah folgendermaßen aus:

 if (this.isEmpty()) { return 'Empty' } return this.items.pop() 

Die Entwicklung durch Testen ermöglicht es uns, den Code nach dem Schreiben der Tests umzugestalten. Ich fand eine kürzere Implementierung, ohne mir Gedanken über das Verhalten meiner Tests zu machen.

Führen Sie den Test erneut aus:


Bild

Tests verbessern die Codequalität. Ich hoffe, Sie verstehen jetzt die vollen Vorteile des Testens und werden TDD häufiger verwenden.

Haben wir Sie davon überzeugt, dass das Testen entscheidend ist und die Qualität des Codes verbessert? Lesen Sie vielmehr Teil 2 des Artikels über Entwicklung durch Testen. Und wer das erste nicht gelesen hat, folgt dem Link .

Source: https://habr.com/ru/post/de430876/


All Articles