Unit Testing in Swift

Solange eine Programmiersprache Neuland ist, wird man sich wohl kaum mit Unit Tests auseinandersetzen.[1] So habe auch ich um dieses Thema lange Zeit einen großen Bogen gemacht. Während der Einarbeitung in Swift ist man schließlich mit derartig viel Neuem beschäftigt, dass man kaum die Lust verspürt, auch noch Unit Testing auf den Lernplan zu setzen. Womöglich ist einem auch nicht bewußt, welchen Sinn diese Tests haben mögen. Dabei kann einem Unit Testing viel Ärger ersparen, denn es ermöglicht dem Entwickler etwaige Bugs bereits frühzeitig zu erkennen. Früher oder später ist es deswegen auf jeden Fall ratsam, dieses Thema anzugehen.

XCTest, XCTestCase

Fangen wir von vorne an: Xcode beinhaltet ein Framework mit der Bezeichnung XCTest. Wenn Ihr ein neues Projekt beginnt und die Option “Include Unit Tests” aktiviert, wird eine Datei mit der Bezeichnung “[Projektname]Tests.swift” angelegt. Ihr werdet feststellen, dass dieses Datei die Importanweisung import XCTest enthält, also das besagte Framework importiert wird. Angenommen, das Projekt hätte den Namen “Testing1”, dann würde folgender Code vorhanden sein:[2]

import XCTest
@testable import Testing1

class Testing1Tests: XCTestCase {
// ...

}

Wie Ihr sehen könnt, wurde eine Subklasse (Unterklasse, abgeleitete Klasse) von XCTestCase erzeugt. Die Zeile

@testable import Testing1

ist übrigens erforderlich, damit Euer Code auch tatsächlich getestet werden kann. Das Attribut @testable wurde mit Swift 2 eingeführt. Dadurch hat das Target Unit Test Zugriff auf andere Module, mithin auf das Modul, das Euren eigentlichen Programmcode enthält. Andernfalls – und das war vor Swift 2 der Fall – müssten Eure Klassen/Methoden explizit mit public gekennzeichnet sein. Nur dann wäre der Zugriff von einem anderen Modul aus möglich. Dank des @testable bedarf es keiner Kennzeichnung als public und Ihr könnt ohne Weiteres mit den Unit Tests beginnen.

Innerhalb von

class Testing1Tests: XCTestCase {

}

werden nun die Tests geschrieben. Quasi als Vorlage sind schon einige Methoden vorhanden. Anhand der Kommentare könnt Ihr bereits sehen, wann diese Methoden aufgerufen werden:

  • Die Methode setUp() wird ausgeführt, bevor irgendwelche Test-Methoden aufgerufen werden
  • Die Methode tearDown() wird danach ausgeführt.

Womöglich bedarf es vor der Durchführung der Tests der Initialisierung etwaiger Objekte. Dies kann dann in setUp() erfolgen. In tearDown() könnten hingegen erforderliche “Aufräumarbeiten” stattfinden.

Es stehen unterschiedliche Arten von Tests – oder besser gesagt Assertions – zur Verfügung, die von XCTest bereitgestellt werden. Dabei gibt es einen Test, den man als eine Art “Basis-Test” bezeichnen könnte. Es handelt sich um XCTAssert().

Falls bei diesem Test true zurückgegeben wird, gilt der Test als bestanden, andernfalls wird eine Fehlermeldung angezeigt. In der Praxis könnte das folgendermaßen aussehen: Erstellt eine neue Datei, wählt als iOS-Source die Vorlage “Swift File” aus und gibt ihr den Namen MyCalculations. Fügt in diese Datei folgenden Code ein:

class MyCalculations {

        func calculateTwoValues() ->Int {

            let a = 2
            let b = 8
            let result = a + b
            return result

        }    
}

Zugegebenermaßen macht die Funktion calculateTwoValues()nicht allzu viel Sinn, aber es geht ja an dieser Stelle primär darum, sich mit dem Unit Testing auseinanderzusetzen.

Nun fügt Ihr in der Datei Testing1Tests.swift folgenden Code hinzu:

func testResult() {
    let c = MyCalculations()
    let myResult = c.calculateTwoValues()
    XCTAssert(myResult > 0)
}

Anschließend verwendet Ihr die Tastenkombination cmd+ U oder wählt im Menü

-> Product -> Test

aus, um den Test zu starten. Bereits nach kurzer Zeit wird dieser Test als erfolgreich bestanden gekennzeichnet. Denn mit diesem Test soll überprüft werden, ob das Ergebnis der Addition größer als null ist. Oder anders ausgedrückt: Ob die Behauptung, dass myResult > 0 wahr (true) ist. Und das ist in der Tat so, denn 2 + 8 ergibt fraglos 10 und 10 is größer als 0.

Nun existieren neben diesem sehr allgemeinen Test wesentlich speziellere Assertions. Gerade für Boolean Tests gibt es

XCTAssertTrue
XCTAssertFalse

In diesem Beispiel hätte ich daher auch

func testResult() {
    let c = MyCalculations()
    let myResult = c.calculateTwoValues()
    XCTAssertTrue(myResult > 0)
}

schreiben können. Da dieser Test true zurückgibt, wäre er also erfolgreich gewesen.

Hier ein Überblick über die zur Verfügung stehenden Test Assertions:

  • XCTAssert
  • XCTAssertEqual
  • XCTAssertNotEqual
  • XCTAssertEqualObjects
  • XCTAssertNotEqualObjects
  • XCTAssertEqualWithAccuracy
  • XCTAssertNotEqualWithAccuracy
  • XCTAssertTrue
  • XCTAssertFalse
  • XCTAssertGreaterThan
  • XCTAssertGreaterThanOrEqual
  • XCTAssertLessThan
  • XCTAssertLessThanOrEqual
  • XCTAssertNil
  • XCTAssertNotNil

Code Coverage

In Xcode 7 existiert ein ziemlich nützliches Tool: Code Coverage. Damit wird überprüft und angezeigt, ob und wie oft Code getestet wurde. Zur Aktivierung von Code Coverage bedarf es der Anpassung des Test Schemes unter

-> Product -> Scheme -> Edit Scheme

Hier wählt Ihr Test aus und aktiviert “Gather coverage data”. Sobald ein Test durchgeführt wurde, erhaltet Ihr im Report Navigator (cmd + 8) unter Coverage einen Überblick darüber, wie gut Euer Code von den vorhandenen Tests abgedeckt wird. Am rechten Rand des Source Code Editors erscheinen zudem auch Zahlen, die anzeigen, wie oft der entsprechende Code-Abschnitt getestet wurde.


  1. Unit Testing in der englischsprachigen Wikipedia. Im deutschen Sprachgebrauch spricht man auch vom Modul- oder Komponententest; nähere Ausführungen dazu gibt es in der deutschsprachigen Wikipedia.  ↩
  2. Eine Test Case Class kann auch nachträglich angelegt werden. Dafür öffnet man den Dialog für das Erzeugen einer neuen Datei und wählt als Template “Test Case Class” aus. Dabei ist darauf zu achten, dass als Subklasse “XCTestCase” eingetragen ist.  ↩

Durch Benutzung dieser Website erklären Sie sich mit der Verwendung von Cookies einverstanden. Mehr Informationen

Die Verwendung von Cookies dient dazu, Inhalte und Anzeigen zu personalisieren, Funktionen für soziale Medien anbieten zu können und die Zugriffe auf diese Website zu analysieren. Außerdem werden Informationen zur Nutzung dieser Webseite an Partner für soziale Medien, Werbung und Analysen weitergegeben.

Schließen