Jul 16, 2017
Convert Xcode Plist Test Reports To JUnit XML
Let’s say you are an iOS developer, working on an amazing app for your company. You use Jenkins for continuous integration. So you like having your unit tests automated and Jenkins displaying beautiful charts and trends, right ?
The Problem
The thing is Xcode likes to export its unit tests reports as property list (plist) files and Jenkins prefers to ingest JUnit XML reports. You have to find the missing link.
Solutions
The Obvious (Lazy) One
You might think :
let’s just use
fastlane
. It’s a great tool, it packages everything a mobile developer could wish for when it comes to automating builds.
You are right : there is fastlane scan
for testing and it uses xcpretty to format xcodebuild
output. Xcpretty
does a really good job at keeping Jenkins happy by converting tests reports to the JUnit XML format.
There’s also trainer, which does exactly what you want here. Hell, it was even made by KrauseFx, creator of fastlane ! But you should be familiar with Ruby first to understand the code and the installation process is similar to fastlane
, obviously.
You could find a Jenkins plug-in, too, but it already handles JUnit so well it would be sad to go down this road.
The Brave One
However, for reasons like the cost of dependency to third party code - or some other I should write about in another post - , you might prefer going deeper in understanding what needs to be achieved, and you feel confident that you can fulfill the requirements on your own, quenching your thirst of knowledge. You are brave. Let’s clap to that 👏.
What does an Xcode unit test report look like ?
- First, run your unit test by pressing
cmd+U
. - Then, locate the plist report at
[DERIVED_DATA_PATH]/[YOUR_PROJECT]-[SOMETHING]/Logs/Tests/[STUFF]_TestSummaries.plist
- Open it with your favorite text editor.
What you are currently looking at is an XML file. Since JUnit reports are obviously in an XML format, all we need is a tool which takes in XML, adapts it to suit the desired XML destination format, and outputs it.
How to adapt to the right XML format ?
After some research, the xsltproc
command could help you. From its man
entry :
xsltproc
is a command line tool for applying XSLT stylesheets to XML documents.
Yes, well, thank you sir - errr man
- but what does XSLT mean ? If you have not clicked on the link to the wikipedia page yet :
XSLT (Extensible Stylesheet Language Transformations) is a language for transforming XML documents into other XML documents […].
Bingo ! Using xsltproc
, you will transform your [STUFF]_TestSummaries.plist
file provided by Xcode into report.junit
to feed Jenkins on, by applying an XSLT stylesheet.
The « implementation »
Here is the anatomy of the xsltproc
command :
xsltproc [{-o | --output} {DESTINATION-FILE | DIRECTORY}] [STYLESHEET] {XML-SOURCE-FILE | -}
Thus, the full command will look like the following :
xsltproc -o [DESTINATION_PATH]/report.junit \
[WHERE_YOU_WANT]/plist_to_junit.xsl \
[DERIVED_DATA_PATH]/Logs/Test/*_TestSummaries.plist
All you need now is a nice stylesheet. Let's cut to the chase and let me provide you with some magical stylesheet that - although imperfect - should work :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<testsuites>
<xsl:apply-templates select="//dict[key = 'Tests']"/>
</testsuites>
</xsl:template>
<xsl:template match="//dict[key = 'Tests']">
<testsuite name="{string[2]}">
<xsl:apply-templates select="//dict[key = 'Tests']//dict[key = 'TestStatus']"/>
</testsuite>
</xsl:template>
<xsl:template match="//dict[key = 'Tests']//dict[key = 'TestStatus']">
<testcase classname="{../../string[1]}" name="{string[1]}" time="{real[1]}">
<xsl:if test="key[2] = 'FailureSummaries'">
<failure message="{array/dict/string[2]}">
<xsl:value-of select="concat(array/dict/string[1], ':', array/dict/integer[1])"/>
</failure>
</xsl:if>
</testcase>
</xsl:template>
</xsl:stylesheet>
Save this code in a file named plist_to_junit.xsl
.
Now in your unit test script, which will be executed by Jenkins :
#!/bin/bash -e
xcodebuild [PARAMS] clean build test
xsltproc -o [DESTINATION_PATH]/report.junit \
[WHERE_YOU_WANT]/plist_to_junit.xsl \
[DERIVED_DATA_PATH]/Logs/Test/*_TestSummaries.plist
Conclusion (for the brave)
There you go ! The plist file generated by Xcode is now converted into a proper JUnit XML report, using a simple command line tool already installed on you mac, and a small transformation file. No third party tool necessary.
Yet, the xsl file is definitely perfectible, so feel free to try it out and tell me what you think, and how you made it even better.
Edit (March 15th, 2018)
Here is a simpler and more accurate stylesheet which will avoid counting tests twice when you have multiple test targets :
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<testsuites>
<testsuite name="All Unit Tests">
<xsl:apply-templates select="//dict[key = 'TestStatus']"/>
</testsuite>
</testsuites>
</xsl:template>
<xsl:template match="//dict[key = 'TestStatus']">
<testcase classname="{../../string[1]}" name="{string[1]}" time="{real[1]}">
</testcase>
</xsl:template>
</xsl:stylesheet>