Testing Introduction

How To Start Unit Testing in Swift

Last week I wrote about code quality and one area I covered was unit testing. Unit testing is the process of validating that pieces of your code operate as you expect. As you can imagine, this is a very useful aid in software development. If you’re only using your eyes to determine if your app is functioning you’re leaning on hope. The hope, being, that the house doesn’t come crumbling down since you can’t confidently say that everything works. The moment you ship the app to the store is one filled with anxiety. Undoubtably you discover a soul-crushing bug(s) that you scramble to push to the store, waiting on the Apple overlords.

A plumber doesn’t just walk into your house, “fix” an issue, and walk out without verifying that the issue is fixed. A new medicine isn’t born out of a lab and immediately sent out for mass consumption. Your city doesn’t hope the emergency alert systems work, they routinely test the systems. NASA and SpaceX are not magicians, they are testing systems constantly. I don’t think I have to convince you of the value here. No, we’re not often building systems that can cause death, but we are building systems that people may use on a daily basis to help them do something. They’d like the things they use to work.

The problem is that most of us feel like it is an immense waste of time. What is a waste of time is releasing software over-and-over again and running into the same critical issues that you “thought” were fixed. I’ve had the displeasure of working with people who didn’t care about code quality and it just beat us down every time we thought we had something to stand on. That said, testing is ultimately an investment. You will be giving up time to build and maintain a test structure so make sure you apply it where it makes sense.

By the time you finish this article you’ll have a much better awareness of testing.

  • You’ll see how think about testing and where to apply it
  • You’ll think about code differently, since it has an immense impact on your ability to test
  • You’ll learn how to write basic unit tests with Swift and see how it fits together with Xcode
  • You’ll learn how to make sure your code is continuously being tested when making changes

Consider this an introduction. I plan to explore much more testing in the future so sign up for my newsletter to know when that happens.

Note: Aware of Test-Driven Development (TDD) or Behavior-Driven Development (BDD) testing? Fair warning, I don’t want you to worry about either of those. I want you to worry about testing code. If that comes before or after, great! In future articles I will explore each of those and you can decide if the path fits into your workflow.

Creating Testable Code

If you already have an iPhone built it can feel like a daunting task to integrate testing into the mix. It’s no joke, coming in after something has been built IS daunting. That’s why it pays to pay attention at the beginning.

You need to be writing testable code. Testable code isn’t methods that contain 100 lines of code and performing several tasks. Your code needs to be focused, the structure needs to be sound, and you should avoid making one method do several things. If you don’t have testable code it makes the process much more difficult. You spend more time cleaning (refactoring) code than writing tests.

Introduce a Test Plan

When you begin writing your app you should be thinking about how you’re going to test the pieces you’re making. What are the critical components? Usually, this falls right on the lap of your model. These pieces are unlikely to change very much, but they often heavily relied upon.

So start with your model logic.

If all you’ve tested is the model you would be sitting in a pretty good position. At least you have a good idea that the internal bits are working well.

Beyond the model you’ll want to move to testing user interface behavior. This is especially important given that you need to validate layout changes on an iPhone 6+ didn’t blow things up for someone using an older model.

Ultimately, don’t feel like you need 100% test coverage. The key is that you’re acting and slicing away at it.

Getting Started

Feel free to grab the sample project. Additionally, you should be able to follow along by creating your own project.

[alert color=”yellow”]github.com project[/alert]

When you create a new Xcode project you’ll have a testing target added automatically. If your project does not have a testing target then you’ll want to navigate to File > New > Target. Alternatively, you can navigate to your project settings (General) and press the plus (+) button on the lower left under the targets column.

Adding a test bundle

A test target comes preloaded with a your first test file. In my example this is titled dojo_testingTests.swift. Feel free to make many test files, each with a descriptive name that tells you what kind of tests are in it. You may have noticed a couple of methods already in the file.

The setUp and tearDown methods provide you with an area to put code that you will need for each of your tests. Instead of duplicating the code in each test you can add the code to the setUp method. When you need to clean things up use the tearDown method.

A Simple Test

The easiest test we could possibly make right now is one that verifies that the view within our ViewController loads.

func testViewControllerLoad() {
    let vc = ViewController()
    
    XCTAssertNotNil(vc.view, "View did not load for ViewController")
}

If you created a project to follow along you’ll notice that you’re getting an error Use of unresolved identifier ‘ViewController’. This is because our test target does not know about our ViewController. Code within each target is internal by default, not public. You can select ViewController.swift and add it to the test target via the File Inspector.

Test Target

Now if you go back to your test file it will know about the ViewController.

Make sure your console is visible before running your tests (CMD + Shift + C). You can now press CMD + U to run your test.

Simple test passed

An even better way to visualize how things are going is by navigating to the Test navigator by pressing CMD + 5. Here you can run all of your tests, or isolate the execution to just a single test. Hover over one of the tests (or source file names) to begin the test. If all goes well you’ll see a lot of green check marks! If something does manage to go south then you can easily navigate to, fix, and re-test that test.

Visualize Testing

Testing a Model

Let’s say we’re building an app that helps us track our portfolio. As you can imagine, when we are dealing with money it becomes very important that we get numbers correct. If we don’t then people will lose faith, rage, and come after you with pitch forks. Seriously, don’t mess with money emotions!

In our example we have a Portfolio object named XMCPortfolio. Right now the only thing we can do is fetch a current holding and purchase a stock. So then we are concerned that

  1. We can properly add a stock to our portfolio and determine that it calculated the proper total cost amount
  2. We can add additional shares

If you look at our example project you can see here that we’re not doing anything too crazy, but we do have a couple of calculations that we need to make sure are properly computed.

When we are running our tests we have the ability to set breakpoints to pause execution. Below you’ll see that a test failed and I wanted to see what the variable was holding at the time of the failure.

Debugging Tests

Available Condition Checks

You’ve seen a couple of condition checks already, but XCTest comes loaded with several more (which you can find in XCTest):

  • XCTAssert
  • XCTAssertEqual
  • XCTAssertEqualWithAccuracy
  • XCTAssertFalse
  • XCTAssertGreaterThan
  • XCTAssertGreaterThanOrEqual
  • XCTAssertLessThan
  • XCTAssertLessThanOrEqual
  • XCTAssertNil
  • XCTAssertNotEqual
  • XCTAssertNotEqualWithAccuracy
  • XCTAssertNotNil
  • XCTAssertTrue
  • XCTFail

Continuous Integration

Now that we have tests in our app we want to make sure that we are continuously testing our app with them each time we perform a commit. Instead of remembering to test before each commit you can take advantage of a CI tool to help you. This is especially important as your team grows since it’s difficult to rely on every person committed to running tests. If you’re a one-man band this isn’t so critical, however it can still help.

You can roll with a CI tool like Jenkins which can be running on your own server. Or you can fire up an Xcode Server to perform CI duties.

If you would rather not deal with your own server then you should look into using an alternate service like Ship.io or CircleCI or Travis CI.

I recently gave Ship.io (currently in Beta) a spin and ran into trouble (builds passing when they shouldn’t), so I wouldn’t go down that path right now.

CircleCI is also in beta. You’ll need to contact support to gain access. They have a much cleaner user interface, which makes me hopeful. So far what I’ve experienced has been great. I had one minor issue and their team pounced on it.

Travis CI is what I see used most often around Github since they provide a free option for open source projects. This option still requires a bit of configuration to get running, whereas the other options do a good job detecting your need.

Challenge

This weekend you should grab the example project and try adding new functionality while testing it. It’ll get you away from your own project (if you have one) while being a very focused experience. Here are some ideas:

  • Sell shares
  • Track current price (whatever you set it to) & verify the earnings

[alert color=”yellow”]github.com project[/alert]

Takeaway

Take some time to start testing your app. Don’t overcomplicate it if you’re just starting out. Focus on testing model code and then move into testing more elements.

Expect to see more on testing soon. Until then, be sure to join my monthly newsletter to stay informed!

Your Turn

Why are you not testing?


Posted

in

by

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *