XCTest
is the framework that provides the foundation for writing unit tests for Swift and Xcode projects. The range of functions
include test infrastructure, test case- and method definition, assertions, failure recording, performance measurement and UI interaction.
Terminology and Classification
First of all let’s clear up some terms that will eventually pop up and might cause some confusion.
XCTest
Is the name of apple’s testing framework and also the name of the abstract base class that provides shared functionality for test cases and test suites.
XCTestCase
Is used to combine multiple tests together that are being executed. Usually a XCTestCase
is subclassed and the different tests are methods of the subclass.
XCTestSuite
Is a collection of XCTests
. Those can either be a XCTestCase
or XCTestSuite
itself.
XCTestRun
Is used to collect information about the execution of a XCTest
. It keeps track of the start- and end date, executed tests, failures, success, etc.
Both XCTetCase
and XCTestRun
are usually managed by Xcode and Swift. Therefore mostly one will not
come into contact with anything other than XCTestCase
s but it’s good to have at least heard about the other terms.
Structure of a test case
Leaving the technicalities aside for now let’s dive into the actual basics of writing tests. Usually a test scheme consists of a number of XCTestCases
which in term hold
a number of singular tests. A test method is an instance method without parameters and no return value. The method name needs to begin with a lowercase test
.
Consider the following example of a ShoppingCart.
struct ShoppingCart {
private(set) var items: [String] = []
mutating func add(_ item: String) {
items.append(item)
}
mutating func remove(_ item: String) {
items.removeAll(where: { $0 == item })
}
}
This is a bare bone implementation of a shopping basket entity. There are the different items in the basket and methods to add/remove them. In the course of this series the implementation is going to
change and grow in complexity. Alongside with it I’m going to iterate on the respective XCTestCase
.
To cover the status quo the following test case is going to test all of the capabilities of the current ShoppingCart.
final class ShoppingCartTests: XCTestCase {
func testAddItem() {
// 1
var sut = ShoppingCart()
let shoppingItem = "Apple"
// 2
sut.add(shoppingItem)
// 3
XCTAssertEqual(sut.items, [shoppingItem])
}
func testRemoveItem() {
var sut = ShoppingCart()
sut.add("Pear")
sut.remove("Pear")
XCTAssertEqual(sut.items, [])
}
}
The test case contains two tests testAddItem
and testRemoveItem
.
- Firstly the tests preconditions are setup. An instance of the ShoppingCart (sut = System under Test) and a ShoppingItem are beining initialised.
- The test calls the
add
method that is supposed to be tested. - The desired behaviour is verified by asserting if, by calling
add
, the provided items is added to the ShoppingCart.
testRemoveItem
works in a very similar way but in order to have a meaningful test result the shopping cart should have at least one item in it.
The next post is going to break those seemingly robust tests. But don’t worry, I’m going to fix them straight away.
See you next time 💙