Leave a rating/review
Like it or not, almost every program you’ll ever write will have bugs. It happens even to the best programmers. It’s just human nature - we’re imperfect!
So given that every program has bugs, the challenge becomes how to catch the bugs before they get to your users, rather than after.
One strategy is to do manual testing - go through your app looking for problems, and fix them.
Often this involves creating a detailed checklist of things to test, or a detailed testing script, so you make sure to remember to check for everything consistently.
Manual testing definitely has its place, but it can be time intensive, error-prone, and tedious.
So another strategy that can really help is to add what’s called unit testing into your app. This is where you write code to test your other code.
The nice thing about unit tests is that you write them once, and can use them forever. For eample, if you write a nice set of unit tests to make sure your app is working the way you want, when a new version of iOS or Swift comes out, you can run your tests to see if everything still works OK. And if something breaks, it helps you narrow down on what’s broken very quickly.
Unit Testing works particularly well when it comes to testing Data Models, because as we’ve seen they’re often plain old Swift objects. And if they have just one single responsibility, they’re usually really easy to test.
Best of all, Xcode makes Unit testing super easy with really nice built-in support.
The way it works is you create one or more “test cases”, which you can think of as a “group of related tests”.
It’s usually good to create one Test Case for each “chunk” of your app you want to test. For Bullseye, we just have one “chunk” we want to test: the Game data model, so we’ll create a single test case.
In each test case, you need to provide a set up and tear down method. The set up method does anything you need to do to get ready for the other tests.
For Bull’s Eye, we’ll make an instance of our Game struct and store it in a game property for future reference. The tear down method does anything you need to do when the tests are complete - in our case, we’ll set our game property to nil
, which you can think of as “nothing”, indicating that we’re done with the game instance.
Finally, you add a method into your test case for each test you want to run. You can put in any code you want into these tests, but at some point you should call special built-in functions that Apple has provided to test that what actually happened matches what you expect to happen.
These special built-in functions are called asserts; these are functions you can call to test if something you expect to work a certain way actually does in practice.
There are many different types of asserts like XCTAssertTrue, XCTAssertEqual, XCTAsssertGreaterThan, and so on. For this episode, we’re going to use XCTAssertEqual.
Once you’ve created your tests, Xcode provides several ways to run your tests with a click of a button, and see if your code is working the way you expect, or if there are any issues you need to look into.
Allright! Now that you have a high-level idea of how this all works, let’s switch to Xcode and give this a try.
Switch to the 6th tab in the Navigator (the Test Navigator).
Click the + button in the lower-left corner, then select New Unit Test Target… from the menu.
Accept defaults, and click Finish.
Open BullseyeTests.swift, and edit as follows:
import XCTest
@testable import Bullseye
class BullseyeTests: XCTestCase {
var game: Game!
override func setUpWithError() throws {
game = Game()
}
override func tearDownWithError() throws {
game = nil
}
func testExample() throws {
XCTAssertEqual(game.points(sliderValue: 50), 999)
}
}
Show the 3 ways to run the tests:
- Product ▸ Test or Command-U. Both of these run all test classes.
- Click the arrow button in the Test navigator.
- Click the diamond button in the gutter.
Change model to return 100 instead, and show a failure.