TypeScript und Unit-Tests: so sichern wir unsere JavaScript-Applikationen

Gepostet am in KategorienNews, Wissen

Striktere Programmierung und Tests sichern unsere Anwendungen gegen Angriffe und sparen viel Zeit in der Fehlerbehebung!

Unit-Tests

Was sind Unit-Tests?

Ein Modultest (auch von englisch unit test als Unit-Test oder als Komponententest bezeichnet) wird in der Softwareentwicklung angewendet, um die funktionalen Einzelteile (Units) von Computerprogrammen zu testen, d. h., sie auf korrekte Funktionalität zu prüfen.

Im Klartext: Anwendungen bestehen aus vielen einzelnen Funktionen. Jede dieser Funktionen erfüllt im Idealfall genau eine Aufgabe. Entweder manipuliert sie einen Wert, gibt eine Information zurück, oder führt auf Grundlage einer Entscheidung andere Funktionen aus.

Sobald man eine Funktion schreibt, legt man fest, was diese Funktion machen soll. Und ob diese Funktion das auch wirklich macht, oder Fehler ordentlich behandelt wenn etwas schief läuft, das testet man mit automatisierten Tests. So stellt man sicher, dass, während die Anwendung wächst und wächst, und Daten und Funktionen immer wieder geändert und erweitert werden, Funktionen die bereits abgeschlossen sind immer noch genau die Funktionalität liefern, die sie sollen.

Wozu brauchen wir Unit-Tests?

Unit-Tests sind bei der Programmierung von Web-Anwendungen nichts neues und auch kein Hexenwerk. Jedoch machen sie nicht unbedingt bei jedem Projekt Sinn, oder sind im Budget mit unter zu bekommen.

Bei kleineren, überschaubaren Projekten z.B., bei denen Zeit und Budget sowieso knapp sind, ist es schwierig die notwendige Zeit noch in Code zu stecken, den sowohl Kunde als auch Anwender am Ende gar nicht mitbekommen.

Bei großen, kontinuierlich wachsenden Projekten sind Tests dagegen eine große Absicherung. Man kann mit ihnen verhindern, dass eine neue Funktion eine andere alte Funktion in ihrer Wirksamkeit beeinflusst. Also dass z.B. bereits abgeschlossene Komponenten und Features ein anderes Verhalten an den Tag legen, als sie ursprünglich sollten, und das obwohl man diese Komponenten am Ende vielleicht gar nicht angefasst hat.

Sogenannte Side-Effects können aber immer wieder auftreten, wenn man sich nicht durch Tests absichert.

Natürlich bieten auch Tests keine hundertprozentige Garantie, dass die Anwendung damit komplett Bug-Frei ist, aber sie helfen Probleme zu erkennen, bevor eine neue Version der Anwendung veröffentlicht wird.

Wie hoch ist der Aufwand?

Der Aufwand für Komponenten-Tests ist tatsächlich nicht zu unterschätzen, auch wenn er immer von der eigenen Sorgfalt abhängt.

Ein kleines Beispiel: Wir haben eine Vue-Funktion, die in Abhängigkeit des aktuellen Zustands der Komponente eine andere Funktion mit false oder true ausführen soll:

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  methods: {
    swipeAction(direction: string): boolean {
      if (this.isEditable) {
        if (direction === 'left' && this.isVisible === false) {
          this.emitToggleCard(true)
          return true
        } else if (direction === 'right' && this.isVisible === true) {
          this.emitToggleCard(false)
          return true
        }
      }
      return false
    }
  }
})
</script>

Um die Funktion vollkommen durch Tests abzusichern müssen wir nun folgende Sachen testen:

  • Gibt die Funktion false zurück, wenn this.isEditable === false ist?
  • Ruft die Funktion this.emitToggleCard mit false auf, wenn der Parameter direction === 'right' und this.isVisible === true ist?
  • Ruft die Funktion this.emitToggleCard mit true auf, wenn der Parameter direction === 'left' und this.isVisible === true ist?

Und so sehen die Tests dazu aus:

    /**
     * SwipeAction
     */
    describe('SwipeAction', () => {
      const stub = jest.fn()
      let cmp: any

      beforeEach(() => {
        cmp = createCmp({ isEditable: true, cardId: '1', isHeadline: false, isVisible: true })
        cmp.setMethods({ emitToggleCard: stub })
      })

      it('swipeAction calls toggleCard() with `true`', () => {
        cmp.setProps({ isVisible: false })
        cmp.vm.swipeAction('left')

        expect(stub).toBeCalledWith(true)
      })

      it('swipeAction calls toggleCard() with false', () => {
        cmp.setProps({ isVisible: true })
        cmp.vm.swipeAction('right')

        expect(stub).toBeCalledWith(false)
      })

      it('swipeAction returns false when card is not editable', () => {
        cmp.setProps({ isEditable: false })
        expect(cmp.vm.swipeAction('left')).toBe(false)
      })
    })

Wir schreiben hier also 3 Funktionen um EINE kleine Funktion zu testen. Und es ginge natürlich noch ausführlicher.


TypeScript

Was ist TypeScript?

TypeScript ist eine durch Microsoft entwickelte Programmiersprache, die auf den Vorschlägen zum zukünftigen ECMAScript-6-Standard basiert. Sprachkonstrukte von TypeScript, wie Klassen, Vererbung, Module, anonyme Funktionen und Generics wurden auch in ECMAScript 6 übernommen.

TypeScript ist also eine Programmiersprache die in reguläres JavaScript kompiliert. Der o.g. ECMAScript-Standard ist lediglich die nächste Versionsbezeichnung des weltweiten JavaScript-Standards.

Microsoft als Entwickler hinter TypeScript steckt viel Aufwand und Energie in die kontinuierliche Weiterentwicklung der Sprache, mit viel Zusammenarbeit mit der weltweiten Community.

Erst kürzlich erschien Version 3.0; gefühlt erscheint jeden Monat ein neues Update mit tollen Features und Bug-Fixes.

Wozu TypeScript?

Der große Vorteil von TypeScript: es schränkt die Freiheiten von regulären JavaScript ein. Klingt erstmal negativ, macht aber in punkto Sicherheit auf jeden Fall Sinn!

In JavaScript findet keine Typisierung von Variablen oder Funktionen statt. D.h. man muss beim erstellen einer Variable nicht angeben ob sie einen String, ein Objekt, einen Bool-Wert oder etwas anderes enthalten soll.

Ebenso können Funktionen alle möglichen Arten von Parametern erwarten und im Zweifelsfall alle möglichen Arten von Werten zurückgeben.

Dies macht den Code nicht nur anfälliger für Fehler und Sicherheitslücken, sondern für andere Entwickler auch schwerer lesbar.

TypeScript erleichtert also die Zusammenarbeit im Team, weil sich Code anderen Teammitgliedern schneller erschließt und einfach logischer aufgebaut ist.

Wie schwer ist der Einstieg?

Startet man neu mit TypeScript (und hat vorher nur JavaScript/ES6 geschrieben) könnte man von den vielen Möglichkeiten und Anforderungen tatsächlich etwas erschlagen werden.

Alles will definiert werden. Wirklich alles. Ein älteres jQuery-Plugin einbinden? Fehler: jQuery-Type muss erstmal definiert werden.

Ein Objekt mit Werten zusammenbauen? Zuerst das Objekt bitte erstmal vollständig mit allen Werten und Typen definieren!

Kleines Beispiel: ein Card-Objekt. Das Objekt soll wie folgt aufgebaut werden:

const card = {
  isHeadline: true,
  isVisible: true,
  isUserContent: false,
  isNote: false,
  hasNote: false,
  id: 'f9dcdc54-56dd-4baa-8c6e-bdcab5b03456',
  title: 'Pick-up A: I’m from Greenich',
  content: '',
  userInfo: {
    author: 'Herr Mustermann',
    created: '2018-08-01T09:06:30.410Z',
    updated: '2018-08-02T09:06:30.410Z'
  }
} 

Um dieses Objekt so anlegen zu können, und es später in Funktionen weiterverwenden zu können, müssen dafür sogenannte Interfaces definiert werden, welche wie folgt aussehen:

interface IUserinfo {
  author: String
  created: String | Date
  updated: String | Date
}

interface ICard {
  isHeadline: Boolean
  isVisible: Boolean
  isUserContent: Boolean
  isNote: Boolean
  hasNote: Boolean
  id: String
  title: String
  content: String
  userInfo: IUserinfo | undefined
}

Mit diesen Interfaces können wir nun sagen, dass die Variable card den Type ICard besitzt, und nicht einfach ein undefiniertes Objekt mit zufälligen Inhalten ist:

const card: ICard = {
  ..
}

Das nur als kleines Beispiel, was möglich ist. Der Vorteil: ähnlich wie mit den Unit-Tests bestimmt man selbst, wie strikt man arbeitet. Man kann auch alle Warnungen von TypeScript ignorieren, und es wird trotzdem funktionaler Code erzeugt.

Veröffentlicht von Lars Eichler

Lars ist Frontend-Entwickler bei 1000°DIGITAL GmbH. Sein Herz schlägt für JavaScript, CSS-Magic und Usability. Alle Beiträge von Lars Eichler