Active Filters: Testing

We've covered UI Tests a fair amount over the years, but one thing has always stuck out: Running tests can be slow. Very slow.

Even if our individual tests themselves run quickly, the entire process is essentially "single threaded", slow to start up and complete each pass, and is prone to strange errors.

Today we'll take a look at Bluepill, a new tool from LinkedIn that can help us with all of this by running multiple iOS Simulators in parallel, simultaneously. Let's dive in.

We'll start by cloning the repository, then running the build script that comes inside:

./build.sh

This will build the command line tool. Once it's finsihed, we can copy the tool somewhere permanent:

cp build/Build/Products/Debug/bp /usr/local/bin

Then rename it to something we can more easily identify:

mv /usr/local/bin/bp /usr/local/bin/bluepill

(Note: /usr/local/bin is a common place to put command line tools on macOS thanks largely to the fact that Homebrew puts things there, so it's likely already in our $PATH).

Now that we have Bluepill installed, let's try it out.

We can head back to our project and run something like:

./bluepill -a ./Spaceships.app -s ./SpaceshipsUITests.xcscheme -o ./output/

This is great for quick runs, but ideally we'd be able to configure this sort of thing once and use it each time. Let's make a quick configuration file using JSON. We'll call it bluepill-config.json:

{
   "app": "./Spaceships.app",
   "scheme-path": "./SpaceshipsUITests.xcscheme",
   "output-dir": "./bluepill-logs/"
}

By default Bluepill will run 4 iOS Simulators simultaneously. Before we run our tests, let's turn that up a notch by adding one more option to our config file:

(This will cause our test to be run in up to 12 iOS Simulators at once. Very cool).

{
   "app": "./Spaceships.app",
   "scheme-path": "./SpaceshipsUITests.xcscheme",
   "output-dir": "./bluepill-logs/",
   "num-sims": 12
}

Finally, we can start our engines:

./bluepill -c bluepill-config.json

So awesome.

Not only are we saving tons of time this way, but Bluepill also does other helpful things for us, such as automatically retrying when the Simulator hangs or crashes. Neat.

We've only scratched the surface of what's possible with Bluepill. Be sure to check out the README for a full list of options and defaults.

Learn more about Bluepill at git.io/bluepill.

Testing network requests can be tricky. Generating mock data, handling HTTP routes, etc. Thing get complicated quickly. Today we'll check out a library from devlucky called Kakapo that can help us tame all this. Let's get started.

One of the best features of Kakapo is how easy it is start using. We can create a new Router for a domain, and start adding intercepted routes like this:

let router = Router.register("http://littlebitesofcocoa.com")

router.get("/bites") { request in
  return ["id" : 1, "title": "#1: View Controller Initialization ๐Ÿš€"]
}

In the above example, we're returning static data. Let's kick things up a notch and return some dynamic data. This is another place Kakapo really shines:

let db = KakapoDB()
db.create(Bite.self, number: 10)

router.get("/bites") { request in
  return db.findAll(Bite.self)
}

We can use this functionality to not only test network requests, but even to "stub out" responses before fully implementing backend services. Neat!

Once we've wired up some routes, we can make our network requests like normal:

let URL = NSURL(string: "http://littlebitesofcocoa.com/1")!

session.dataTaskWithURL(URL) { (data, _, _) in
  // handle response
}.resume()

We can even wire Kakapo up to Alamofire (Bite #93) like this:

let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.protocolClasses = [KakapoServer.self]
let manager = Manager(configuration: configuration)

This barely scratches the surface of what Kakapo has to offer.

From advanced dynamic mocking and database functionality, great routing, and a squeaky clean API, it's definitely worth a look.

Learn more about Kakapo at git.io/kakapo

Topics

#133: Code Coverage in Xcode ๐Ÿ› 

Topics

Code Coverage arrived with Xcode 7. It can help us visualize which parts of our code are not being tested enough (or at all). Let's dive in:

Before we can take advantage of Code Coverage, we'll need to enable it for our project. It's off by default.

We'll begin by editing our scheme, then selecting Test in the sidebar, then enabling the Gather coverage data checkbox.

Now we'll run our tests by going to Product > Test in the menu (or pressing U). Then we can check out the test log where we'll find a new โ€œCoverageโ€ tab.

Xcode will display all the functions in our code with a bar graph indicating how well โ€œcoveredโ€ they are by our tests (We can mouse over to get an exact percentage). In our case, we are at 0%, since we don't have any tests. Yikes! Let's fix that by adding a simple test for a function on our PersonViewModel:

func testFullName() {
  let person = Person(firstName: "Han", lastName: "Solo")
  let personVM = PersonViewModel(person: person)
  XCTAssertEqual(personVM.fullName, "Han Solo")
}

If we run our tests again, and check the Coverage tab, we can see we now have 50% test coverage for our tiny example project.

Last but not least, Xcode will also give us a heads up with a red shaded area in the right gutter on lines of code that aren't covered by our tests. Neat!

It's another fastlane friday here on LBOC. Today we'll be looking at another awesome tool in the fastlane suite called scan. It provides an easy way to run the tests of our iOS or OS X app. Let's dive in.

Before we begin, let's look at why a tool like scan can be helpful.

Xcode ships with a great command line tool called xcodebuild that allows to do all sorts of interesting things to our projects from the command line. It can be a bit verbose to configure though, and its output isn't very readable at a glance.

There's other tools like xcpretty that can help improve this output, but they take a fair amount of configuration as well.

That's where scan comes in. It takes care of all of this (plus a lot more) in one simple command: scan.

We'll start by installing scan:

gem install scan

Then we run our tests at anytime like this:

scan

This is all we need for basics usage. scan will auto-detect things like our workspace, but we can always configure things as well:

scan --scheme "app-store"

Like other fastlane tools, we can run scan init to generate a new Scanfile, where we can store all our configuration options:

scheme "Spaceships"
clean true
output_types "html"

Other Features

๐Ÿ“ƒ Displays nice output, stores original xcodebuild log in ~/Library/Logs/scan

๐Ÿ“„ Can generate HTML, JSON or JUnit reports

๐Ÿ“ฃ Can send well-formatted test results to Slack. Check out the slack_only_on_failure configuration option to only report failed tests.

scan also helps with resolving common Xcode oddities like duplicated simulators or simulators that stop responding. Finally, scan works great with tools continuous integration tools like Jenkins and services like Travis. Happy testing!

More info about scan can be found at git.io/scan

Topics

#129: More UI Testing Tips ๐Ÿšธ

Topics

Today we'll look at a couple more tips to get the most out of UI Testing in Xcode. (Covered previously in Bites #30, and #124).

Are We in a Test?

It'd be great if our app could know when it's running inside of a UI Test, then behave differently. We'll start by setting a flag that we can check for later. We'll add a launchArgument before we launch our app in our tests' setUp functions.

let app = XCUIApplication()
app.launchArguments = [ "IS_UI_TESTING" ]
app.launch()

Then, we'll add a new helper function to our app's code. We could add this to a helper class, or just make it a global function:

func isUITesting() -> Bool {
  return Process.arguments.contains("IS_UI_TESTING")
}

We can use this function throughout our code to do things like hit mock servers during testing, or simulate a camera preview when capturing screenshots with snapshot. (Bite #110).

Dismissing System Alerts

We have to jump through a couple of hoops here. First we'll need to set up what's called a "UI Interruption handler", which will execute a closure when the UI is interrupted by, for example, an alert being shown:

addUIInterruptionMonitorWithDescription("Authorization Prompt") {
  $0.buttons["Allow"].tap()
  return true
}

app.tap()

Then, after our app presents the authorization prompt, we'll need to .tap() on it once before the UI Interruption handler will fire. Inside our handler, we'll accept the prompt then return true to tell Xcode we've handled the UI interruption.

Have a UI Testing tip or trick of your own you'd like share? Send it along to hello@littlebitesofcocoa.com!

Topics

#124: More on UI Testing ๐Ÿšธ

Topics

We covered the basics of UI Testing in Xcode when it was first announced all the way back in Bite #30. It's a fantastic way to test our app's interface. Today we'll look at a few more UI Testing odds and ends. Let's dive in:

First, let's write a test to verify that one of our table views properly loads and displays its data. The data is loaded asynchronously (which should be mocked in our test, but that's a future Bite). We'll need to give our app a little time to load and render the data we're testing for. We can use an expectation to easily pull this off:

expectationForPredicate(
  NSPredicate(format: "count > 0"),
  evaluatedWithObject: app.tables.cells,
  handler: nil)

waitForExpectationsWithTimeout(5, handler: nil)
XCTAssertGreaterThan(app.tables.cells.count, 0)

Xcode will wait up to 5 seconds for the predicate we've passed in to be true (for there to be more than 0 cells in our table). After that the XCTAssert will be executed and evaluated.

We can change the simulated orientation of the device like this:

XCUIDevice.sharedDevice().orientation = .LandscapeLeft

Quite helpful for testing all that great new Adaptive UI code!

Finally, let's look at performing some interactions beyond simple taps. We can do things like long press an element:

app.buttons["+"].pressForDuration(0.5)

or perform simple pans using the convenience swipe functions:

app.tables["Crew"].swipeDown()

We can even execute more complex pan gestures using the pressForDuration(duration:thenDragToCoordinate:) function:

  let point = table.coordinateWithNormalizedOffset(CGVectorMake(0, 2)
  table.pressForDuration(0.5, thenDragToCoordinate: point)

Topics

#30: UI Testing ๐Ÿšธ

Topics

Testing is incredibly important in ensuring our app arrives to our users in as good a state as possible.

Xcode has had great unit testing support for a few releases now, but testing our UI has always been a challenge.

Instruments added support for it a while back, but itโ€™s never felt like a first class citizen... Until now!

Xcode 7 brings us an awesome, fully baked set of UI testing tools. We can even make Xcode write much of your test code for us by simply interacting with our app.

Here's an example test created using the default Master-Detail template in Xcode. It presses the add button 3 times, then verifies that 3 cells now exist in the table view:

func testExample() {
    let app = XCUIApplication()

    let masterNavigationBar = app.navigationBars["Master"]
    let addButton = masterNavigationBar.buttons["Add"]

    addButton.tap()
    addButton.tap()
    addButton.tap()

    XCTAssert(app.tables.childrenMatchingType(.Cell).count == 3)
}

Recording UI Tests couldn't be easier. We just write an new empty test function, put our cursor in the middle of it, and press record.

UI Testing uses Accessibility under the hood to identify and retrieve UI elements at runtime. Just one more reason to make our apps as accessible as possible!