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 example, 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.
In code, all of the asserts start with “XCT”.
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.
All right! Now that you have a high-level idea of how this all works, let’s switch to Xcode and give this a try.
Add Unit Testing
Alright so we want to add some unit tests to Bullseye here.
And the way we do this is this is your project navigator. Remember a long time ago I said there were other navigators?
There’s one specifically for testing! So if you hover your cursor over the tabs, they’ll each show a little tool tip that show you what kind of navigator this is.
And we want the Test navigator.
Right now it should say no tests.
To add the ability to add tests, click the plus button to add test targets. Go ahead and select new unit test target.
And we can leave all of this as defaults and just click finish right here.
We’ll switch back to the project navigator. And it’s created a new group for us called BullseyeTests and a Bullseyetest Swift file.
So, open that new Swift file up, and again I like to use two spaces so I’m going to command A to hit everything and then control I to indent it the way that I like it.
Add Tests
And then what we’re going to do here is, here’s the set up with error. Which as we discussed is where you do your initial setup for all of your tests.
And our initial setup’s going to be extremely simple. We just need to create an instance of our game struct.
So first we need a property for that. So remember to create a property you type in var game, which is the name of our property, colon, and then the type of the property which is a game. And we’re going to put an exclamation mark at the end.
You’ll learn all about this later on in this learning path, so I won’t go too much into it.
class BullseyeTests: XCTestCase {
var game: Game!
Briefly, when you see an exclamation mark at the end like this, we’re saying it’s an optional variable. It may or may not be set to a value.
So when Bullseye tests is first created this is going to have nothing in it, it’s going to be empty, or nil, as another way of saying it. After we call setupWithError it’s going to have a value, which is an instance of our game.
So in other words game may be empty, like when this thing first starts up. Or it may have a value. But by putting the exclamation mark at the end we’re going to say even though it might not have a value, treat it as if the value will definitely be there.
Because in this case we know set up with error is always called. But like I said we’re going to go way more into optionals later on. For now just know we need to put an exclamation mark here. If you know that much it’ll be fine for right now.
Alright. So in set up with error, here, we need to set this game property to an instance of our game. So we’re going to say game equals game and then parentheses like so.
override func setUpWithError() throws {
game = Game()
}
And now it’s giving us error, it says it can’t find game in the scope.
That’s because we have to import our Bullseye code into this file. That may seem silly, but by default, everything in this Bullseye target is separate from the Bullseye Tests target. So we need to say, hey, you can use the code from Bullseye in this file.
All of your import statements should go at the top of the file. So we can add a new one right under “import XCTest”.
And the way you do this is you put testable up front, and then you put import, and then you put in whatever your product is named. And ours is called Bullseye, like so.
import XCTest
@testable import Bullseye
If the error doesn’t do away immediate, you can try building with Command-B, or cleaning the project with Command-K.
Once the error is gone, we’ll deal with the tear down.
We’re going to get rid of the value of the game property here. When you have an optional value, you can remove the value from it by setting it to nil. So, say “game equals nil”. And once that runs, the game property will be empty again.
override func tearDownWithError() throws {
game = nil
}
Alright, for test example we’re just going to delete all of these comments. And do a little test. And we’re going to call XCTAssertEqual. This is saying these two values that you pass in should be the same.
And for the first value, call game.points. And we’ll say 50 for the slider value. It doesn’t really matter because right now the method always returns 999, or it should at least.
And since we know that, we can press tab to get to the next argument, and put “999” for the second value to make sure this method is doing what we expect.
func testExample() throws {
XCTAssertEqual(game.points(sliderValue: 50), 999)
}
And then let me delete this other performance example ‘cause we don’t need that. And this is kind of a bare bones example of a test file.
Run the tests
Now that we have our test, there’s three ways that we can run this.
First of all we can go to Product - Test. And what that’ll do is it’ll run all of your tests.
You see here it runs the app, which may take a minute or two the first time you do this, and then runs the test, and you can see here that we have some green check marks over here and in our test navigator we see green check marks everywhere.
Another way you can run tests is if you wanna run a specific test or a group of tests you can click this little check mark in the gutter, and it will run that specific test or the group of tests.
You can also click the buttons over here to run them from the test navigator if that’s what you’d prefer.
We’ve seen example of it working but let me show you what happens when it doesn’t work.
So I’m going to go back to game.swift and say you know we had a typo and it was returning 99 instead of 999.
So, again I could go to product test, or keyboard shortcut for that which is Command-U.
And now we see red instead. And if I click on this red X, it’ll tell you what went wrong wrong.
So it says 99 which is what it’s returning does not equal our expectation of 999.
Now we can go back to the Game file and change that to what it should be: 999.
Hit Command-U again to run our test.
And this time everything succeeds. And we go back to our test navigator. We see green check marks everywhere.