I took the path less travelled and made an Ant-based build. . . besides running tests form the cmd-line, it does Test Coverage Reporting (i.e. highlighted classes/lines that haven't been addressed by a test case) and outputs an HTML (CI Server friendly) report.
<?xml version="1.0" encoding="UTF-8"?>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ CONFIGURATION ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<property file="build-configuration.properties"/>
<path id="headers">
<fileset dir="${source.main.dir}">
<include name="**/*.h"/>
</fileset>
</path>
<path id="classes">
<fileset dir="${source.main.dir}">
<include name="**/*.m"/>
<include name="**/*.mm"/>
<include name="**/*.cpp"/>
<exclude name="**/main.m"/>
</fileset>
</path>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PUBLIC TARGETS (can depend on other targets) ~~~~~~~~~~~~~~~~~~~~~~~~ -->
<target name="build" depends="
--init,
--clean,
--app.build,
--api.report"/>
<target name="fast">
<property name="fast" value="yes"/>
</target>
<target name="release" depends="
--init,
--clean,
</target>
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ END PUBLIC TARGETS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PRIVATE TARGETS (MUST NOT depend on other targets!) ~~~~~~~~~~~~~~~~~~~ -->
<target name="--init">
<exec executable="xcode-select" outputproperty="xcode.path">
<arg line="-print-path"/>
</exec>
<property name="xcode.path.message">
Xcode path is not set. Please use run xcode-select from the cmd-line, to specify location of Xcode tools.
${line.separator}Example: sudo xcode-select -switch /Applications/Xcode.app/Contents/Developer
</property>
<fail unless="xcode.path"
message="${xcode.path.message}"/>
<mkdir dir="${target.dir}"/>
<mkdir dir="${temp.dir}"/>
<mkdir dir="${temp.dir}/coverage-data"/>
<mkdir dir="${reports.dir}"/>
<mkdir dir="${reports.dir}/tests"/>
</target>
<target name="--clean" unless="fast">
<mkdir dir="${target.dir}"/>
<mkdir dir="${temp.dir}"/>
<fail unless="xcode.path" message="Xcode path is not set"/>
<echo message="Based on xcode-select, simulator path is: ${xcode.path}/${simulator.path}"/>
<echo file="${temp.dir}/quit-simulator.script">
tell application "${xcode.path}/${simulator.path}" to quit
</echo>
<exec executable="osascript" failonerror="true" failifexecutionfails="true">
<arg line="${temp.dir}/quit-simulator.script"/>
</exec>
<delete dir="${target.dir}"/>
</target>
<target name="--simulator.launch">
<echo file="${temp.dir}/fg-simulator.script">
tell application "${xcode.path}/${simulator.path}" to launch
tell application "${xcode.path}/${simulator.path}" to activate
</echo>
<echo message="Launching the iPhone simulator for unit tests. . "/>
<echo message="Leave it running in the foreground, or things won't work."/>
<exec executable="osascript" failonerror="true" failifexecutionfails="true">
<arg line="${temp.dir}/fg-simulator.script"/>
</exec>
</target>
<target name="--app.build">
<exec executable="${xcodebuild}" failonerror="true">
<arg line="-project ${
module.name}.xcodeproj -sdk iphonesimulator${module.sdk.version} -target ${
module.name} TEST_AFTER_BUILD=NO CONFIGURATION_BUILD_DIR='${target.dir}'"/>
</exec>
</target>
<target name="--tests.build">
<exec executable="${xcodebuild}" failonerror="true">
<arg line="-project ${
module.name}.xcodeproj -sdk iphonesimulator${module.sdk.version} -target Tests TEST_AFTER_BUILD=NO CONFIGURATION_BUILD_DIR='${target.dir}'"/>
</exec>
</target>
<target name="--tests.run">
<mkdir dir="${reports.dir}/tests"/>
<property name="tests.report" value="${reports.dir}/tests/test-results.txt"/>
<exec executable="${basedir}/ios-sim" outputproperty="tests.output" failonerror="false"
failifexecutionfails="false">
<arg line="--setenv CEDAR_HEADLESS_SPECS=1"/>
</exec>
<!-- Write report to disk -->
<echo file="${tests.report}" message="${tests.output}"/>
<!-- Print report to console -->
<echo message="${tests.output}"/>
<loadfile srcfile="${tests.report}" property="tests.failed">
<filterchain>
<filterreader classname="org.apache.tools.ant.filters.LineContains">
<param type="contains" value="FAILURE"/>
</filterreader>
</filterchain>
</loadfile>
<loadfile srcfile="${tests.report}" property="tests.exception">
<filterchain>
<filterreader classname="org.apache.tools.ant.filters.LineContains">
<param type="contains" value="EXCEPTION"/>
</filterreader>
</filterchain>
</loadfile>
<echo file="${temp.dir}/quit-simulator.script">
tell application "${simulator.path}" to quit
</echo>
<exec executable="osascript" failonerror="true" failifexecutionfails="true">
<arg line="${temp.dir}/quit-simulator.script"/>
</exec>
<fail if="tests.failed" message="Tests failed. Report is at ${tests.report}"/>
<fail if="tests.exception" message="Tests failed. Report is at ${tests.report}"/>
</target>
<exec executable="${xcodebuild}" failonerror="true">
<arg line="CONFIGURATION_BUILD_DIR='${target.dir}' -workspace ${
module.name}.xcworkspace/ -scheme ${
module.name}"/>
</exec>
</target>
<target name="--assemble.coverage.data">
<echo message="Coverage Files: ${gcno.list}"/>
<mkdir dir="${temp.dir}/coverage-data"/>
<pathconvert pathsep=", " property="gcno.list" refid="classes">
<mapper>
<chainedmapper>
<flattenmapper/>
<globmapper from="*.m" to="**/*.gcno"/>
</chainedmapper>
</mapper>
</pathconvert>
<echo message="Coverage info files: ${gcno.list}"/>
<copy todir="${temp.dir}/coverage-data" flatten="true">
<fileset dir="${user.home}/Library/Developer/Xcode/DerivedData" includes="${gcno.list}"/>
</copy>
<pathconvert pathsep=", " property="gcda.list" refid="classes">
<mapper>
<chainedmapper>
<flattenmapper/>
<globmapper from="*.m" to="**/*.gcda"/>
</chainedmapper>
</mapper>
</pathconvert>
<echo message="Coverage data files: ${gcda.list}"/>
<copy todir="${temp.dir}/coverage-data" flatten="true">
<fileset dir="${user.home}/Library/Developer/Xcode/DerivedData" includes="${gcda.list}"/>
</copy>
</target>
<target name="--coverage.report">
<property name="coverage.reports.dir" value="${reports.dir}/coverage"/>
<mkdir dir="${coverage.reports.dir}"/>
<echo file="${temp.dir}/geninfo.sh">
#!/bin/sh
geninfo ${temp.dir}/coverage-data/*.gcno --no-recursion --output-filename \
#Remove symbols we're not interested in.
</echo>
<chmod perm="+x" file="${temp.dir}/geninfo.sh"/>
<exec executable="${temp.dir}/geninfo.sh" failonerror="true" failifexecutionfails="true"/>
<echo file="${temp.dir}/genhtml.sh">
#!/bin/sh
genhtml --no-function-coverage --no-branch-coverage -o ${coverage.reports.dir} \
--prefix ${source.main.dir} ${temp.dir}/${
module.name}.info
</echo>
<chmod perm="+x" file="${temp.dir}/genhtml.sh"/>
<exec executable="${temp.dir}/genhtml.sh" failonerror="true" failifexecutionfails="true"/>
</target>
<target name="--api.report">
<property name="docs.dir" value="${user.home}/Library/Developer/Shared/Documentation/Docsets"/>
<mkdir dir="${docs.dir}"/>
<pathconvert pathsep=" " property="doc.files.list" refid="headers"/>
<exec executable="doxygen"/>
</target>
</project>