Skip to content

Commit 11f1fbc

Browse files
committed
[xctest] Add test reporters
Not everyone wants to be notified of test results in the same way. Some people prefer a concise view that fits on a single line, with dots indicating success and "F" to signify failure: ".....FF..F" Some prefer a detailed view, containing data on how long each test took to run, etc. Objects that conform to the reporter protocol are responsible for formatting output related to test execution. Besides providing a way to customize test output, reporters are essential for another reason: as a way to unit test XCTest itself. Since XCTest currently prints to stdout, it's difficult to test it without running it and inspecting its output. A forthcoming commit will add tests that use a test double as a reporter, making assertions on what it output by XCTest.
1 parent 735e602 commit 11f1fbc

File tree

1 file changed

+51
-8
lines changed

1 file changed

+51
-8
lines changed

XCTest/XCTest.swift

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,31 @@ import Glibc
1717
import Darwin
1818
#endif
1919

20+
/// Not everyone wants to be notified of test results in the same way.
21+
///
22+
/// Some people prefer a concise view that fits on a single line, with
23+
/// dots indicating success and "F" to signify failure: ".....FF..F"
24+
///
25+
/// Some prefer a detailed view, containing data on how long each test took
26+
/// to run, etc.
27+
///
28+
/// Objects that conform to the reporter protocol are responsible for
29+
/// formatting output related to test execution.
30+
public protocol Reporter {
31+
/// Sent when XCTest wishes to log arbitrary information regarding the
32+
/// a test event.
33+
///
34+
/// - Parameter message: The message to log.
35+
func log(message: String)
36+
}
37+
38+
/// This reporter simply prints each message it is given to stdout.
39+
private struct StdOutReporter: Reporter {
40+
func log(message: String) {
41+
print(message)
42+
}
43+
}
44+
2045
public protocol XCTestCaseProvider {
2146
// In the Objective-C version of XCTest, it is possible to discover all tests when the test is executed by asking the runtime for all methods and looking for the string "test". In Swift, we ask test providers to tell us the list of tests by implementing this property.
2247
var allTests : [(String, () -> ())] { get }
@@ -27,7 +52,6 @@ public protocol XCTestCase : XCTestCaseProvider {
2752
}
2853

2954
extension XCTestCase {
30-
3155
public var continueAfterFailure: Bool {
3256
get {
3357
return true
@@ -45,7 +69,11 @@ extension XCTestCase {
4569
XCTCurrentTestCase = self
4670
let method = "\(self.dynamicType).\(name)"
4771
var duration: Double = 0.0
48-
print("Test Case '\(method)' started.")
72+
73+
for reporter in XCTReporters {
74+
reporter.log("Test Case '\(method)' started.")
75+
}
76+
4977
var tv = timeval()
5078
let start = withUnsafeMutablePointer(&tv, { (t: UnsafeMutablePointer<timeval>) -> Double in
5179
gettimeofday(t, nil)
@@ -67,7 +95,11 @@ extension XCTestCase {
6795
if XCTCurrentFailures.count > 0 {
6896
result = "failed"
6997
}
70-
print("Test Case '\(method)' \(result) (\(round(duration * 1000.0) / 1000.0) seconds).")
98+
99+
for reporter in XCTReporters {
100+
reporter.log("Test Case '\(method)' \(result) (\(round(duration * 1000.0) / 1000.0) seconds).")
101+
}
102+
71103
XCTAllRuns.append(XCTRun(duration: duration, method: method, passed: XCTCurrentFailures.count == 0, failures: XCTCurrentFailures))
72104
XCTCurrentFailures.removeAll()
73105
XCTCurrentTestCase = nil
@@ -82,8 +114,9 @@ extension XCTestCase {
82114
}
83115
let averageDuration = totalDuration / Double(tests.count)
84116

85-
86-
print("Executed \(tests.count) test\(testCountSuffix), with \(totalFailures) failure\(failureSuffix) (0 unexpected) in \(round(averageDuration * 1000.0) / 1000.0) (\(round(totalDuration * 1000.0) / 1000.0)) seconds")
117+
for reporter in XCTReporters {
118+
reporter.log("Executed \(tests.count) test\(testCountSuffix), with \(totalFailures) failure\(failureSuffix) (0 unexpected) in \(round(averageDuration * 1000.0) / 1000.0) (\(round(totalDuration * 1000.0) / 1000.0)) seconds")
119+
}
87120
}
88121

89122
// This function is for the use of XCTestCase only, but we must make it public or clients will get a link failure when using XCTest (23476006)
@@ -115,7 +148,10 @@ internal struct XCTRun {
115148
///
116149
/// This function will not return. If the test cases pass, then it will call `exit(0)`. If there is a failure, then it will call `exit(1)`.
117150
/// - Parameter testCases: An array of test cases to run.
118-
@noreturn public func XCTMain(testCases: [XCTestCase]) {
151+
/// - Parameter reporters: An array of reporters. Each will be sent messages regarding the execution of the tests.
152+
@noreturn public func XCTMain(testCases: [XCTestCase], reporters: [Reporter] = [StdOutReporter()]) {
153+
XCTReporters.appendContentsOf(reporters)
154+
119155
for testCase in testCases {
120156
testCase.invokeTest()
121157
}
@@ -130,7 +166,11 @@ internal struct XCTRun {
130166
failureSuffix = ""
131167
}
132168
let averageDuration = totalDuration / Double(XCTAllRuns.count)
133-
print("Total executed \(XCTAllRuns.count) test\(testCountSuffix), with \(totalFailures) failure\(failureSuffix) (0 unexpected) in \(round(averageDuration * 1000.0) / 1000.0) (\(round(totalDuration * 1000.0) / 1000.0)) seconds")
169+
170+
for reporter in XCTReporters {
171+
reporter.log("Total executed \(XCTAllRuns.count) test\(testCountSuffix), with \(totalFailures) failure\(failureSuffix) (0 unexpected) in \(round(averageDuration * 1000.0) / 1000.0) (\(round(totalDuration * 1000.0) / 1000.0)) seconds")
172+
}
173+
134174
exit(totalFailures > 0 ? 1 : 0)
135175
}
136176

@@ -140,13 +180,16 @@ struct XCTFailure {
140180
var line: UInt
141181

142182
func emit(method: String) {
143-
print("\(file):\(line): error: \(method) : \(message)")
183+
for reporter in XCTReporters {
184+
reporter.log("\(file):\(line): error: \(method) : \(message)")
185+
}
144186
}
145187
}
146188

147189
internal var XCTCurrentTestCase: XCTestCase?
148190
internal var XCTCurrentFailures = [XCTFailure]()
149191
internal var XCTAllRuns = [XCTRun]()
192+
internal var XCTReporters = [Reporter]()
150193

151194
public func XCTAssert(@autoclosure expression: () -> BooleanType, _ message: String = "", file: StaticString = __FILE__, line: UInt = __LINE__) {
152195
if !expression().boolValue {

0 commit comments

Comments
 (0)