You’ve got 99 problems, and testing is one of ’em!
Developers know that testing an application thoroughly is necessary to catch problems before they affect users. Forgetting to test can result in complications like annoyed clients, ranting one-star reviews in the App Store, and bruises from smacking yourself in the head for letting simple mistakes slip through the net.
But remembering to run tests before each commit or merge can be tough if you have to do it manually. What’s a time-crunched developer to do?
Continuous Integration
Thankfully, Continuous Integration can save the day. Continuous Integration, often abbreviated to just CI, is the process of automatically building and running tests whenever a change is committed.
Now, Apple has their own solution for this with Xcode Bots, which are designed to run on OS X Server. But the downside of Apple’s solution is that you, yes you have to manage the entire process. You have to set up and maintain OS X Server and Xcode versions on the server, figure out access control for viewing results, and handle provisioning and signing issues. Sounds like a lot of work, right? You don’t have time for this; you have code to write, apps to design, and happy hour to get to – that beer isn’t going to drink itself.
Shout it to the cosmos with me: there must be an easier way!
Travis CI
Luckily, the cosmos heard us, and responded with Travis CI.
What is Travis CI?
Usually simply called Travis, it’s a Continuous Integration service that is free for open-source projects and has a monthly fee for closed-source projects based on how many simultaneous builds you want to run.
What does it do?
Travis sets up “hooks” with GitHub to automatically run tests at specified times. By default, these are set up to run after a pull request is created or when code is pushed up to GitHub.
In this Travis CI tutorial, you’ll use a public GitHub repo and the free version of Travis to set up tests that run every time you try to merge new changes into that repo.
- You already have a GitHub account. If you don’t, sign up for a free one here.
- Git is installed on your system. You can check this by opening up Terminal and typing which git. If there’s a result – typically /usr/bin/git – then you’re good to go. If not, you can download an installer from the Git website here.
Getting Started
Let’s do this! Download the starter project, then open up the zip file and put the resulting MovingHelper folder on your Desktop so you can find it easily. MovingHelper is a to-do list app which, as you might suspect from the name, helps manage tasks related to moving.
Build and run your project in Xcode; you’ll see the following:
Use the picker to choose a date a little less than a month from the current date, then tap the Create Tasks button. You’ll see the following:
The app has created a list of tasks. The red sections are past-due tasks, while the green sections are upcoming tasks.
Looking through the code, you’ll see that a few tests have been set up. Execute the tests by using the Command-U shortcut, and they will quickly run and pass:
So far so good, right? Now that you know the tests are passing, you’re ready to get GitHub and Travis set up to run them automatically.
Setting Up Git and GitHub
First, you’ll create a local Git repo with the files in the starter project. Fire up Terminal, then change the directory to your desktop folder:
cd ~/Desktop/MovingHelper |
Next, initialize a local repository on your computer:
git init |
Next, add everything in the MovingHelper folder – since you’re already in it, just type:
git add --all |
Finally, commit all the code:
git commit -m "Starter project from raywenderlich.com" |
Now that everything is committed locally, it’s time to create a public repository on GitHub. This is what Travis will watch for changes.
Head over to github.com and make sure you’re logged in to your account. In the top right corner of the page, there’s a plus sign with a little down arrow next to it – click it and select New repository:
You will see a page to set up the new repository:
The owner will be you. Name the repo MovingHelper, give it a brief description, make sure it’s public, and don’t add a README, license, or .gitignore as these are all included in the sample project. Next, click the big green Create repository button. You’ll see a page explaining how to get your code to GitHub.
Leave this page open in a tab of your browser – you’ll want to come back to it shortly.
Set Up Travis
Open a new tab in your browser and go to travis-ci.org to get started using the free version of Travis. In the upper right corner is a button which allows you to sign in using your GitHub account:
Use this button to sign up for Travis. Since you’re already signed in to GitHub, you should not need to sign in again. If you haven’t already signed up for Travis, you’ll need to agree to the permissions they request:
Travis needs access to read and write webhooks, services, and commit statuses. That way that it can create the automated “hooks” it needs to automatically run when you want it to.
Click the green Authorize Application button. GitHub will ask you to verify your password:
Enter your password and click Confirm password. Now you’re on the Travis “getting-started” page.
Your avatar and GitHub username are in the upper right hand corner:
Click that to be taken to your Travis profile page. You’ll see an alphabetical list of all your public repos. If you haven’t set up Travis previously, they should all be unchecked.
Scroll down to MovingHelper:
Flick the switch to turn it on:
There! Travis is now watching for changes to your MovingHelper repository.
Pushing to GitHub
Go back to the tab with your newly created GitHub repo. Copy the commands from the “…or push an existing repository from the command line” section:
Copy the text from that section either manually or by clicking the clipboard icon on the right, then paste it into Terminal and press enter. This adds your new GitHub repo as a remote and pushes everything up to it.
Since Travis is now watching this repo, it will notice this push and put a build in the line of all the other open source builds waiting to be run.
Whenever your tests run, you’ll get an email that contains something like this:
Ruh roh! What happened? Click on the big Build #1 Failed to be taken to the results of the failed build:
That warning at the bottom contains one specific line that explains why the build failed:
Could not find .travis.yml, using standard configuration. |
What does that mean? Well, .travis.yml file uses YAML to tell Travis how to set up a build. Since Travis works with many different languages, it doesn’t know how to build your specific project without some information about what kind of project it is.
To get a quick look at some of Travis’ best features requiring very little configuration, check out a new branch from the command line by typing the following into Terminal:
git checkout -b travis-setup |
Terminal will confirm that you created and checked out a new branch:
Switched to a new branch 'travis-setup' |
Next, open your plain-text editor of choice. TextWrangler is particularly helpful here because it highlights the syntax of YAML files automatically, but any plain-text editor will work.
Create a new document and save it in the root of your repo as .travis.yml.
Add the following five lines to your new .travis.yml file:
language: objective-c #1 osx_image: xcode6.4 #2 xcode_project: MovingHelper.xcodeproj #3 xcode_scheme: MovingHelper #4 xcode_sdk: iphonesimulator8.4 #5 |
Note that YAML will disregard anything prefixed with a # as a comment. Here’s what you’re telling Travis to do with each line:
- Build a project using … Objective-C!? Don’t panic! Even though your project is in Swift, Travis only uses the
objective-c
value to know to build with Xcode command line tools. Since Xcode knows how to tell what’s in Swift and what’s in Objective-C, your Swift project will be just fine. :] - Use the Xcode 6.4 tools to create the build, since you’re using Swift 1.2. This presently requires specifying which VM image you want to use – in this case
xcode6.4
. - Use the specified Xcode project file. Note that if you have a project you want to build using an .xcworkspace (for example, a project using CocoaPods), you can replace the xcode_project parameter with
xcode_workspace
and use your .xcworkspace file as the value instead of your .xcodeproj. - Use the specified scheme to decide what tests to run. Since your default scheme is called MovingHelper, Travis should use that scheme.
- Run the tests on an iPhone simulator, because doing so does not require setting up code signing (which is not covered in this tutorial).
Make sure your .travis.yml file is saved, then add and commit it to Git:
git add .travis.yml git commit -m "Added .travis.yml file" |
Next, push your branch up to your remote:
git push -u origin travis-setup |
Reload the webpage for your MovingHelper GitHub repo. You should see something like this, indicating that the branch has made it up to GitHub:
Click the green Compare & pull request button.
Change the title of the pull request to Travis Setup:
Click the green Create pull request button, and Travis will automatically start working. As soon as your build completes, you’ll see something like this on your GitHub page:
Argh! You’ve added the .travis.yml file like you were supposed to, so why isn’t it working?
Click one of the Details links to see the results of this build. A new error leads you directly to the problem:
D’oh! Travis knows the name of the scheme, but because it was automatically created and isn’t shared in your GitHub repository, Travis can’t see it. Fix that by going back to Xcode, and from the scheme drop-down, selecting Edit Scheme…
When the scheme editor comes up, check the Shared checkbox at the bottom of the panel:
Click the Close button, then add and commit all shared data (which will include the new shared scheme):
git add MovingHelper.xcodeproj/xcshareddata git commit -m "Added shared scheme" |
Push up to GitHub again:
git push -u origin travis-setup |
Since you already have an open pull request, Travis will immediately know that you added changes and start building again:
Once the build completes, you should see what you’ve been waiting for: green!
All is well indeed. Click on Show all checks and the dialog will expand, showing you the builds which passed:
Click on either Details link, and you’ll be taken to Travis’ output. You can scroll through and see the details of how your project was built and how your tests ran, but the bottom line – and the good news – is all the way at the end:
Each item with a green checkmark next to it is a passing test – and as you can see with the happy green text at the end, all of the tests are passing! Woohoo!
Go back to your GitHub page and click the green Merge pull request button, then click Confirm merge to officially merge your changes.
Hello, World!
Now that your tests are running automatically, it’s time to tell other people that your tests are passing by adding a badge to your README which shows the current status of the build on Travis.
Before you go too far, make sure you’re up to date with everything in your master branch:
git checkout master git pull origin master |
Switch back to your travis-setup branch and merge the changes from master into it:
git checkout travis-setup git merge master |
Now that the merge commit has been merged back into your travis-setup branch, open up the README.md file from the root folder of your project in your markdown or plain-text editor of choice.
Add the following lines to the end of the file:
####Master branch build status: ![](https://travis-ci.org/[your-username]/MovingHelper.svg?branch=master) |
Don’t forget to replace [your-username]
with your actual GitHub username.
You’ve just added a link to a graphic which will be a “passing” or “failing” badge served up by Travis based on the status of your build for the branch specified in the branch
URL query parameter.
Save the changes to the README, then add, commit, and push them up:
git add . git commit -m "Add Travis badge to README" git push origin travis-setup |
Go back to the GitHub page. Follow the same steps as before to create a new pull request. Name this new pull request Badges, and click Create pull request.
Travis will once again do its business – and since you didn’t change any of the code, the tests will continue to pass:
Again, click the Merge pull request and then Confirm merge buttons to merge your changes. Once merged, you’ll see your badge right on your main MovingHelper GitHub page:
Breaking the Build
Now that you’ve gotten a couple of passing pull requests without changing any code, it’s time to take things to the next level: breaking the build. :]
Start by bringing your master branch up to date with the latest changes you just merged in:
git checkout master git pull origin master |
To see the problem you want to fix, build and run the application, and check off one of the boxes. Build and run again. The box is no longer checked. Oops!
When you get a report of a bug from a tester or a user, it’s a good idea to write a test that illustrates the bug and shows when it is fixed. That way, when the tests are run you can be confident that the bug hasn’t magically reappeared – commonly known as a regression.
Let’s make sure that when you mark a task done in the list, the app remembers. Create a new branch for this work and name it to-done:
git checkout -b to-done |
Open up Xcode and go to the TaskTableViewCell.swift file. You can see in tappedCheckbox()
that there’s a TODO
comment instead of the actual code to mark a task as done. For the cell to communicate the task state change, it will need a reference to the task and a delegate to communicate the change to. Add variables for these two items below the outlets:
var currentTask: Task? public var delegate: TaskUpdatedDelegate? |
Since cells are reused, clear the values of these variables before the cell is reused by overriding prepareForReuse()
and resetting each value to nil
:
public override func prepareForReuse() { super.prepareForReuse() currentTask = nil delegate = nil } |
Add a line to the top of configureForTask(_:)
to store the current task:
currentTask = task |
Replace the TODO
in tappedCheckbox()
with code to mark the task as done and notify the delegate of the change:
if let task = currentTask { task.done = checkbox.isChecked delegate?.taskUpdated(task) } |
Finally, go to MasterViewController.swift, and in tableView(_:cellForRowAtIndexPath:)
, add a line just above where the cell is returned, setting the MasterViewController
as the delegate of the cell:
cell.delegate = self |
Build and run. Check off an item, then stop the app. Build and run again. Hooray, the item is still checked off!
Commit your changes:
git add . git commit -m "Actually saving done state" |
Automation
Now that you have fixed the bug, it’s time to write a test which Travis can run automatically. That way if things change, you’ll know immediately.
First, select the MovingHelperTests group in the Xcode sidebar, then choose File\New\File… and select the iOS\Source\Swift File template. Name this new file TaskCellTests.swift, and make sure it’s being added to the test target, not the main target:
Next, set up the basic test case class by replacing the existing import
statement with the following:
import UIKit import XCTest import MovingHelper class TaskCellTests: XCTestCase { } |
Add a test which verifies that when the checkbox in a TaskTableViewCell
is tapped, the associated task is updated:
func testCheckingCheckboxMarksTaskDone() { let cell = TaskTableViewCell() //1 let expectation = expectationWithDescription("Task updated") //2 struct TestDelegate: TaskUpdatedDelegate { let testExpectation: XCTestExpectation let expectedDone: Bool init(updatedExpectation: XCTestExpectation, expectedDoneStateAfterToggle: Bool) { testExpectation = updatedExpectation expectedDone = expectedDoneStateAfterToggle } func taskUpdated(task: Task) { XCTAssertEqual(expectedDone, task.done, "Task done state did not match expected!") testExpectation.fulfill() } } //3 let testTask = Task(aTitle: "TestTask", aDueDate: .OneMonthAfter) XCTAssertFalse(testTask.done, "Newly created task is already done!") cell.delegate = TestDelegate(updatedExpectation: expectation, expectedDoneStateAfterToggle: true) cell.configureForTask(testTask) //4 XCTAssertFalse(cell.checkbox.isChecked, "Checkbox checked for not-done task!") //5 cell.checkbox.sendActionsForControlEvents(.TouchUpInside) //6 XCTAssertTrue(cell.checkbox.isChecked, "Checkbox not checked after tap!") waitForExpectationsWithTimeout(1, handler: nil) } |
This is what each part does:
- Create an expectation for which to wait. Since the delegate is a separate object from the test, you may not hit the success block immediately.
- Create an inline struct, conforming to the test delegate, which allows you to check and see whether it was called or not. Since you want this struct to tell you when the expectation has been met, and do a check based on a value you pass it, you make it accept both the expectation and the expected values as parameters.
- Set up the test task and verify its initial value, then configure the cell.
- Make sure the checkbox has the proper starting value.
- Fake a tap on the checkbox by sending the
TouchUpInside
event which would be called when a user tapped on it. - Make sure everything gets updated – starting with the checkbox by verifying its state has updated, and then wait for the expectation to be fulfilled to make sure the delegate is updated with the new value.
Build the test, but don’t run it – it’s time to be lazy, kick back, and let Travis do it for you. Commit your changes and push them up to the remote:
git add . git commit -m "Test marking tasks done" git push -u origin to-done |
Create a new pull request following the steps you used previously, and call it To-Done. As you probably guessed from the instruction not to run your tests, this build fails:
Click the Details link to get the details of the build failure. Scroll all the way to the bottom, where you’ll see the following:
Scroll up a bit to see information about a crash which occurred while running the tests:
D’oh! The force-unwrap of an IBOutlet
didn’t work, so the test crashed. Why would that be?
If you think about how the TaskTableViewCell
is normally created – through the cell reuse queue managed by a view controller loaded from a storyboard – this crash makes sense. The cell isn’t being loaded from the storyboard, so the IBOutlets
don’t get hooked up.
Fortunately, this isn’t too hard to fix – grab a reference to a cell from an instance of MasterViewController
instantiated from the storyboard, and use its tableView(_:cellForRowAtIndexPath:)
method to grab a valid cell.
Add the following lines at the top of testCheckingCheckboxMarksTaskDone()
, wrapping the code you already added in the if
statement:
var testCell: TaskTableViewCell? let mainStoryboard = UIStoryboard(name: "Main", bundle: nil) if let navVC = mainStoryboard.instantiateInitialViewController() as? UINavigationController, listVC = navVC.topViewController as? MasterViewController { let tasks = TaskLoader.loadStockTasks() listVC.createdMovingTasks(tasks) testCell = listVC.tableView(listVC.tableView, cellForRowAtIndexPath: NSIndexPath(forRow: 0, inSection: 0)) as? TaskTableViewCell //REST OF CODE YOU ALREADY ADDED GOES HERE } |
Next, to make sure the test doesn’t pass if listVC
is somehow nil
, add an else
clause to the if let
which fails the test if it gets hit:
} else { XCTFail("Could not get reference to list VC!") } |
Now update your existing test code to use the cell you’ve just generated. Replace:
let cell = TaskTableViewCell() |
with:
if let cell = testCell { //REST OF THE CODE BELOW SETTING UP THE CELL GOES HERE } else { XCTFail("Test cell was nil!") } |
Once again, be lazy and let glorious automation do your work for you. Build the test to make sure the code compiles, but don’t run it. Commit your changes and push them up to the remote:
git add . git commit -m "Update grabbing cell for test" git push -u origin to-done |
Again, you have an existing pull request, so when Travis runs the tests, you should see good news in your GitHub repo:
Click the Merge pull request button, then the Confirm merge button, and you’re done.
Congratulations! Thanks to the effort you’ve put in as you’ve worked through this Travis CI tutorial, you now have a base of tests you can use to make sure you don’t break anything as you improve the application, and Travis is set up to run them automatically. No more running tests manually – and there’s still time for happy hour :]
Where To Go From Here
You can download the finished project here.
This tutorial has only scratched the surface of what Travis CI can do. No, it won’t fetch you coffee, or beer, but Travis is useful for considerably more than just running tests.
Further Capabilities of Travis
- Using post-build hooks, it can upload the results of your build to an AWS S3 bucket automatically with minimal configuration.
- You can set up pre-build scripts to install and post-build remove certificates from the keychain to create signed builds.
- If you’re creating signed builds, you can also add post-build scripts to automatically upload builds to HockeyApp or iTunes Connect whenever tests pass after a merge.
Travis isn’t always sunshine and lollipops, however.
A few caveats to keep in mind:
- New versions of Xcode are not typically made available until they’ve been publicly released. This means you can’t use Travis to build a version of your app that’s using a beta SDK.
- Since they have a paid service, Travis has an incentive to upgrade everything in a timely fashion. Sometimes, however, that incentive doesn’t cause them to upgrade fast enough for everyone’s tastes. If you always need to be on the bleeding edge, keep this in mind.
- Build machines can be a bit slower than your local machine. Particularly if you’re running UI tests with KIF, you may run in to instances where the slowness of the build machine means you see race conditions you wouldn’t see on real devices, or test flakiness on the build server you don’t see locally.
- You get a lot of information in the logs from Travis, but you can’t get crash logs without setting up scripts to upload them to a third-party service after a build complete.
- All tests are run on simulators. If you have tests which must be run on a device, Xcode Bots is a better choice since it can run on both simulators and real devices – although this comes with the responsibility to manage provisioning and signing.
Want to know more?
If you’re interested in learning more about continuous integration with Travis, check out the following documentation:
- General Build Configuration Guidelines, which give a good overview of the Travis build process.
- Travis Objective-C documentation, which also covers Swift projects.
- Travis OS X CI Environment documentation, which helps determine what is or is not included in the default environment on OS X, as well as stock environment variables you can access in your .travis.yml file.
I’ve hope you’ve enjoyed this Travis CI. If you’ve got and questions then please feel free to ask them in the comments below!
The post Travis CI Tutorial: Getting Started appeared first on Ray Wenderlich.