Blog

bevkjbvkbdkdxoeoziejdoiehz fiugebfuyegwik

Objective-C code coverage

"If you like it you should have put a test on it." ~ Beyonce Knowles, Software Developer.

source

How do we even coverage?

You have written some tests for new code or maybe are trying to improve the quality of an existing open source component. That's cool, but how do you get a sense of what tests are still missing?

The answer is of course code coverage - if you are unfamiliar with the concept, take a look at Jon Reid's excellent article on the topic. Thankfully, enabling this has pretty much become trivial, thanks to the nice work of Mark Larsen in form of slather:

$ slather setup path/to/project.xcodeproj
$ # Run the testsuite
$ slather coverage -s path/to/project.xcodeproj

This is great for getting an overview and also for integrating code coverage into CI via Coveralls. However, it does not really help us to find the uncovered bits in the codebase.

PuncoverPlugin

The obvious answer is that this should be visible right next to the code. Enter PuncoverPlugin:

Screenshot of the plugin in action

You can install it via Alcatraz and it integrates with slather, so there is nothing to set up if you already use it. Simply run

$ slather coverage -g path/to/project.xcodeproj

to generate a .gutter.json file. The plugin will pick up that file and display the information right where you need it, in Xcode's gutter.

Gutter JSON

This is a simple file format I came up with to store information which should be displayed by PuncoverPlugin:

{ "/some/file": [
      {
        "line": 23,
        "long_text": "Some longer text in a tooltip when hovering over the line.",
        "name": "main",
        "short_text": "gutter text",
        "background_color":"0x35CC4B"
      }
] }

The example tells the plugin to display the given short_text right next to line 23 and color it with that background_color. The long_text value will be displayed on hover. This makes the plugin really flexible, as it allows integration with other tools to display arbitrary information in the Xcode editor gutter. You can find some information about other integrations in the README and maybe come up with some of your own.

Test Jam

Of course you are a nice person and all your projects already have 100% code coverage, but there is still a nice opportunity to use this plugin: the CocoaPods Test Jam, a community event we are doing to improve the test coverage of popular Pods. You should participate for some great fun!

Xcode 6.3 beta 2 sadness

So we are all excited about all the new features those Swift betas bring us, but we also lose some things. From the Swift 1.2 release notes:

• Swift now detects discrepancies between overloading and overriding in the Swift type system and the effective behavior seen via the Objective-C runtime. (18391046, 18383574)

What this also takes away is overriding Objective-C methods in extensions, a technique that I use in my CLI test runner xctester:

extension XCTestCase {
    [...]

    func recordFailureWithDescription(description: String!, inFile filePath: String!, atLine lineNumber: UInt, expected: Bool) {
        [...]
    }
}

The -recordFailureWithDescription:inFile:lineNumber:expected: selector is already declared in XCTestCase, but up until now, Swift was kind enough to let us do this anyway.

With 1.2, this gets significantly more verbose. I am utilizing an old ObjC runtime friend imp_implementationWithBlock() to do this, but where to we put that code and how do we create a compatible block?

Global variables are lazy in Swift, so they're not an option, neither is +load in a dummy class. I opted for the unelegant solution of using +initialize and instantiating a dummy object like this:

class LolSwift: NSObject {
    override class func initialize() {
        let recordFailure_IMP = imp_implementationWithBlock(unsafeBitCast(recordFailure_block, AnyObject.self))
        let recordFailure_method = class_getInstanceMethod(XCTestCase.self, "recordFailureWithDescription:inFile:atLine:expected:")
        let recordFailure_old_IMP = method_setImplementation(recordFailure_method, recordFailure_IMP)
    }
}

and then later

let l = LolSwift()

This works, but how do we actually form that recordFailure_block? A simple Swift closure converted will crash at runtime 😭. What we need is an @objc_block, defined like this:

let recordFailure_block : @objc_block (sself: XCTestCase, description: String!, filePath: String!, lineNumber: UInt, expected: Bool) -> Void = { (sself, description, filePath, lineNumber, expected) -> (Void) in
    [...]
}

Let there be much rejoicing, the tests are running again \o/.