Open Xcode and go to File | New | Project. Navigate to iOS | Application | Single View App, and click on Next. Put in the name FirstDemo, select the language Swift, and check Include Unit Tests. Uncheck Use Core Data and Include UI Tests, and click on Next. The following screenshot shows the options in Xcode:
Xcode sets up a project ready for development in addition to a test target for your unit tests. Open the FirstDemoTests folder in the Project Navigator. Within the folder, there are two files: FirstDemoTests.swift and Info.plist. Select FirstDemoTests.swift to open it in the editor.
What you see here is a test case. A test case is a class comprising several tests. In the beginning, it's good practice to have a test case for each class in the main target.
Let's go through this file step by step:
import XCTest
@testable import FirstDemo
Every test case needs to import the XCTest framework. It defines the XCTestCase class and the test assertions that you will see later in this chapter.
The second line imports the module FirstDemo. All the code you write for the app will be in this module. By default, classes, structs, enums, and their methods are defined as internal. This means that they can be accessed within the module. But the test code lives outside of the module. To be able to write tests for your code, you need to import the module with the @testable keyword. This keyword makes the internal elements of the module accessible to the test case.
Next, we'll take a look at the class declaration:
class FirstDemoTests: XCTestCase {
Nothing special here. This defines the FirstDemoTests class as a subclass of XCTestCase.
The first two methods in the class are as follows:
override func setUp() {
super.setUp()
// Put setup code here. This method is called ...
}
override func tearDown() {
// Put teardown code here. This method is called ...
super.tearDown()
}
The setUp() method is called before the invocation of each test method in the class. Here, you can insert the code that should run before each test. You will see an example of this later in this chapter.
The opposite of setUp() is tearDown(). This method is called after the invocation of each test method in the class. If you need to clean up after your tests, put the necessary code in this method.
There are two test methods in the template provided by Apple:
func testExample() {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your ...
}
func testPerformanceExample() {
// This is an example of a performance test case.
self.measure {
// Put the code you want to measure the time of here.
}
}
}
The first method is a normal test. You will use this kind of test a lot in the course of this book.
The second method is a performance test. It is used to test methods or functions that perform time-critical computations. The code you put into the measure closure is called 10 times, and the average duration is measured. Performance tests can be useful when implementing or improving complex algorithms and to make sure that their performance does not decline. We will not use performance tests in this book.
All the test methods that you write have to have the test prefix; otherwise, the test runner can't find and run them. This behavior allows easy disabling of tests--just remove the test prefix of the method name. Later, you will take a look at other possibilities to disable some tests without renaming or removing them.
Now, let's implement our first test. Let's assume that you have a method that counts the vowels of a string. A possible implementation looks like this:
func numberOfVowels(in string: String) -> Int {
let vowels: [Character] = ["a", "e", "i", "o", "u",
"A", "E", "I", "O", "U"]
var numberOfVowels = 0
for character in string {
if vowels.contains(character) {
numberOfVowels += 1
}
}
return numberOfVowels
}
Add this method to the ViewController class in ViewController.swift.
This method does the following things:
- First, an array of characters is defined containing all the vowels in the English alphabet.
Without the [Character] type declaration right after the name of the constant, this would be created as an array of strings, but we need an array of characters here.
- Next, we define a variable to store the number of vowels. The counting is done by looping over the characters of the string. If the current character is contained in the vowels array, numberOfVowels is increased by one.
- Finally, numberOfVowels is returned.
Open FirstDemoTests.swift methods (the methods with the test prefix). Add the following method to it:
func test_NumberOfVowels_WhenPassedDominik_ReturnsThree() {
let viewController = ViewController()
let string = "Dominik"
let numberOfVowels = viewController.numberOfVowels(in: string)
XCTAssertEqual(numberOfVowels, 3,
"should find 3 vowels in Dominik")
}
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at
http://www.packtpub.com. If you have purchased this book from elsewhere, you can visit
http://www.packtpub.com/support and register to have the files emailed directly to you.
This test creates an instance of ViewController and assigns it to the viewController constant. It defines a string to use in the test. Then, it calls the function that we want to test and assigns the result to a constant. Finally, the test method calls the XCTAssertEqual(_, _) function to check whether the result is what we expected.
To run the tests, go to Product | Test, or use the command + U shortcut. Xcode compiles the project and runs the test. You will see something similar to what is shown in this screenshot:
The green diamond with a checkmark on the left-hand side of the editor indicates that the test passed. So, this is it. This is your first unit test. Step back for a moment and celebrate. This could be the beginning of a new development paradigm for you.
Now that we have a test that proves that the method does what we intended, we are going to improve the implementation. The method looks like it has been translated from Objective-C. But this is Swift. We can do better. Open ViewController.swift, and replace the numberOfVowels(in:) method with this swifter implementation:
func numberOfVowels(in string: String) -> Int {
let vowels: [Character] = ["a", "e", "i", "o", "u",
"A", "E", "I", "O", "U"]
return string.characters.reduce(0) {
$0 + (vowels.contains($1) ? 1 : 0)
}
}
Here, we make use of the reduce function, which is defined in the array type. Run the tests again (command + U), to make sure that this implementation works the same as the one earlier.
Before we move on, let's recap what we have seen so far. First, you learned that you could easily write code that tests your code. Secondly, you saw that a test helped improve the code because now you don't have to worry about breaking the feature when changing the implementation.
To check whether the result of the method is as we expected, we used XCTAssertEqual(_, _). This is one of many XCTAssert functions that are defined in the XCTest framework. The next section shows the most important ones.