Your First iOS & SwiftUI App: An App from Scratch

Feb 13 2023 · Swift 5.7, iOS 16, Xcode 14

Part 3: Coding in Swift

22. Intro to Unit Testing

Episode complete

Play next episode

Next
About this episode

Leave a rating/review

See forum comments
Cinema mode Mark complete Download course materials
Previous episode: 21. The Swift Standard Library Next episode: 23. Intro to Test-Driven Development

Get immediate access to this and 4,000+ other videos and books.

Take your career further with a Kodeco Personal Plan. With unlimited access to over 40+ books and 4,000+ professional videos in a single subscription, it's simply the best investment you can make in your development career.

Learn more Already a subscriber? Sign in.

Heads up... You've reached locked video content where the transcript will be shown as obfuscated text.

Like it or not, almost every program you'll ever write will have bugs. It happens to even 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 can 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 tests to 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 okay, and if something breaks, it helps you narrow down what's broken really 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 that 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 setup and teardown method. The setup method does anything you need to do to get ready for the other tests. For Bullseye we'll make an instance of our game struct and store it in a game property for future reference. The teardown 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 expected 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 AssertTrue, AssertEqual, AssertGreaterThan, and so on. For this episode, we're going to use AssertEqual. In code, all of the asserts start with XCT. Once you've created your test, 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 it a try. So we want to add some unit tests to Bullseye here, and the way we'll do this is in your project navigator. Remember a long time ago I said that there were other navigators? There's one specifically for testing. So if you hover your cursor over the tabs, they'll each show you a little tool tip that shows you what kind of navigator it is, and we want the Test navigator. And right now it should say there's no tests. To add the ability to add tests, click the Plus button to add test targets. It's all the way at the bottom here. 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 BullseyeTests 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 grab everything and then Control I to indent it the way I like it. Here's setUpWithError, 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 again is Game, this time capitalized. 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. 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 BullseyeTests is first creating this, it's 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 drops 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 like it will definitely have a value. Because in this case we know that setUpWithError is always going to be called. But like I said, we're going to go way more into optionals later on. So for now, just know we need to put an exclamation mark here. All right, so in setUpWithError here, we need to set this Game property to an instance of our game. So we're going to say game = Game and then parenthesis like so. And now it's giving us an 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 BullseyeTests target. So we need to say, "Hey, you can use the code from Bullseye in this file, it's fine." 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 upfront, and then you put import, and then you put in whatever your product is named. And in our case it's Bullseye, like so. If the error doesn't go away immediately, you can try building with Command B or cleaning the project with Command K. Once the error is gone, we'll deal with the teardown. 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 = nil. And once that runs, the game property will be empty again. All right, for the test example, we're just going to delete all of these comments and do a little test, and we're going to call XCT AssertEqual. 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 at least it should. And since we know that, we can tab over to get to the next argument and put 999 for the second value to make sure this method is doing what we expect. And then let me delete this other performance example, because we don't need that. And now we're left with a pretty bare-bones example of a test file. Now that we have our test there's three ways that we can run it. 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 it runs the test. And you can see here that we have some green check marks over here in our test navigator, we see green check marks everywhere. Another way you can run tests is if you want to run a specific test or a group of tests, you can click this little check mark in the gutter and it will run just that specific test, or at the top here, the group of tests. You can also click the buttons over here to run them from the test navigator if that's what you prefer. We've seen examples of a test 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 we had a typo, and it was returning 99 instead of 999. So again, I could go to Product, Test, or the keyboard shortcut for that, which is Command U. And now we see red instead. And if I click on this red X it will tell me what went wrong. So it says 99, which is what it's returning, does not equal our expectation of 999. So 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 tests. And this time everything succeeds, and we go back to our test navigator, and we see green check marks everywhere.