xccov: Generating Code Coverage Reports

With the release of Xcode 9.3, Apple included a new command line tool called "xccov". It can be used to view code coverage reports as JSON, which can then be used to automate code coverage workflows.

In this tutorial we'll see how to generate and view code coverage reports. We'll then write a Swift script to process the report.

Generating Reports

The first step is to generate the report using the xcodebuild command. On my Mac, I made a new directory called "CoverageScript" at the path "~/Documents/Dev/". I have an open source project that has unit tests at "~/Documents/Dev/CodableKeychain". To generate a code coverage report, I would run:

xcodebuild -project ~/Documents/Dev/CodableKeychain/CodableKeychain.xcodeproj/ -scheme CodableKeychainMobile -derivedDataPath CodableKeychain/ -destination 'platform=iOS Simulator,OS=11.4,name=iPhone 7' -enableCodeCoverage YES clean build test CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO

If you're following along with your own project, you would change the project path (-project) and scheme name (-scheme) to match your project. The xcodebuild command will put all of the derived data into a new directory in the present working directory (~/Documents/Dev/CoverageScript in my case) at the path you passed in for -derivedDataPath ("CodableKeychain" in my case).

Within that directory, the coverage report will be at "Logs/Test" with the extension ".xccovreport".

Outputting Reports As JSON

Now that you have a coverage report, you can use the new xccov command to generate a JSON file. Run the following in Terminal, replacing "CodableKeychain" with the derived data directory you passed in above:

xcrun xccov view CodableKeychain/Logs/Test/*.xccovreport --json > coverage.json

This will create a JSON file, "coverage.json", in the current directory.

Processing The Report

We're now ready to write a Swift script that will process the report. Since it's a JSON file, we can use the Codable protocol to decode the report.

First, let's create a Swift file and make it executable. Run the following in Terminal:

touch process-coverage.swift
chmod +x process-coverage.swift

For more on Swift scripting, see this tutorial

Now open the file in Xcode. The first thing we'll do is define a struct to represent the coverage report:

#!/usr/bin/env swift

import Foundation

struct CoverageReport: Codable {

    let lines: Int
    let coverage: Double

    private enum CodingKeys: String, CodingKey {
        case lines = "coveredLines"
        case coverage = "lineCoverage"
    }

}

Next, let's decode the coverage.json file we generated earlier. We'll pass the file path as an argument to the script, so that when we ultimately run the script it will look like this:

./process-coverage.swift coverage.json

To decode the report, add the following to the Swift script:

let arguments = CommandLine.arguments
guard arguments.count == 2 else {
    print("Error: Invalid arguments.")
    exit(0)
}
let file = CommandLine.arguments[1]
let currentDirectoryPath = FileManager.default.currentDirectoryPath
let filePath = currentDirectoryPath + "/" + file
guard let json = try? String(contentsOfFile: filePath, encoding: .utf8), let data = json.data(using: .utf8) else {
    print("Error: Invalid JSON")
    exit(0)
}
guard let report = try? JSONDecoder().decode(CoverageReport.self, from: data) else {
    print("Error: Could not decode the report.")
    exit(0)
}

Here we're first checking that there are two arguments (the script command is the first), then building the file path, and finally reading and decoding the JSON from the file.

Now that we have the report as a Swift value, we can easily format a message about code coverage to print to the Console:

let percentFormatter = NumberFormatter()
percentFormatter.numberStyle = NumberFormatter.Style.percent
percentFormatter.minimumFractionDigits = 1
percentFormatter.maximumFractionDigits = 2
guard let coveragePercent = percentFormatter.string(from: NSNumber(value: report.coverage)) else {
    print("Error: Could not generate code coverage percentage.")
    exit(0)
}
print("\(coveragePercent) code coverage based on \(report.lines) lines.")

If you've been following along with your own project, you should now be able to run ./process-coverage.swift coverage.json in Terminal, which prints a message telling you about the project's code coverage!


In this tutorial we saw how to use xcodebuild, xccov, and a Swift script to generate, read, and process Xcode code coverage reports. These tools make it much easier to integrate coverage metrics into automated testing workflows. For example, an integration with a source control tool like GitHub could enforce coverage thresholds before allowing a pull request to be merged.

The full script is available here.