Travis CI Tutorial: Getting Started

In this Travis CI tutorial, learn how to set up the popular continuous integration service, and integrate with GitHub so your tests are run automatically. By Ellen Shapiro.

Leave a rating/review
Save for later
Share
You are currently viewing page 3 of 4 of this article. Click here to view the first page.

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:

github_travis_success

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:

github_has_badge

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:

xcode_add_to_test_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:

  1. 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.
  2. 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.
  3. Set up the test task and verify its initial value, then configure the cell.
  4. Make sure the checkbox has the proper starting value.
  5. Fake a tap on the checkbox by sending the TouchUpInside event which would be called when a user tapped on it.
  6. 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:

github_to_done_integration_fail

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:

travis_test_error-700x50

Scroll up a bit to see information about a crash which occurred while running the tests:

travis_fail_stack_trace

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:

github_to_done_pass

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 :]