Skip to content

Commit a32fd1e

Browse files
author
Mike Ferris
committed
Merge pull request #40 from briancroom/allow_setup_teardown_as_override
[SR-460] Provide XCTestCase a class to allow overriding setUp() and tearDown()
2 parents e072d55 + 179d765 commit a32fd1e

File tree

12 files changed

+175
-71
lines changed

12 files changed

+175
-71
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,14 @@ You may add tests for XCTest by including them in the `Tests/Functional/` direct
5858

5959
### Additional Considerations for Swift on Linux
6060

61-
When running on the Objective-C runtime, XCTest is able to find all of your tests by simply asking the runtime for the subclasses of `XCTestCase`. It then finds the methods that start with the string `test`. This functionality is not currently present when running on the Swift runtime. Therefore, you must currently provide an additional property called `allTests` in your `XCTestCase` subclass. This method lists all of the tests in the test class. The rest of your test case subclass still contains your test methods.
61+
When running on the Objective-C runtime, XCTest is able to find all of your tests by simply asking the runtime for the subclasses of `XCTestCase`. It then finds the methods that start with the string `test`. This functionality is not currently present when running on the Swift runtime. Therefore, you must currently provide an additional property, conventionally named `allTests`, in your `XCTestCase` subclass. This method lists all of the tests in the test class. The rest of your test case subclass still contains your test methods.
6262

6363
```swift
6464
class TestNSURL : XCTestCase {
65-
var allTests : [(String, () -> Void)] {
65+
static var allTests : [(String, TestNSURL -> () throws -> Void)] {
6666
return [
6767
("test_URLStrings", test_URLStrings),
68-
("test_fileURLWithPath_relativeToURL", test_fileURLWithPath_relativeToURL ),
68+
("test_fileURLWithPath_relativeToURL", test_fileURLWithPath_relativeToURL),
6969
("test_fileURLWithPath", test_fileURLWithPath),
7070
("test_fileURLWithPath_isDirectory", test_fileURLWithPath_isDirectory),
7171
// Other tests go here
@@ -81,10 +81,10 @@ class TestNSURL : XCTestCase {
8181
}
8282
```
8383

84-
Also, this version of XCTest does not use the external test runner binary. Instead, create your own executable which links `libXCTest.so`. In your `main.swift`, invoke the `XCTMain` function with an array of instances of the test cases that you wish to run. For example:
84+
Also, this version of XCTest does not use the external test runner binary. Instead, create your own executable which links `libXCTest.so`. In your `main.swift`, invoke the `XCTMain` function with an array of the test cases classes that you wish to run, wrapped by the `testCase` helper function. For example:
8585

8686
```swift
87-
XCTMain([TestNSString(), TestNSArray(), TestNSDictionary()])
87+
XCTMain([testCase(TestNSString.allTests), testCase(TestNSArray.allTests), testCase(TestNSDictionary.allTests)])
8888
```
8989

9090
The `XCTMain` function does not return, and will cause your test app to exit with either `0` for success or `1` for failure.

Sources/XCTest/XCTAssert.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ private func _XCTEvaluateAssertion(assertion: _XCTAssertion, @autoclosure messag
104104
/// to it evaluates to false.
105105
///
106106
/// - Requires: This and all other XCTAssert* functions must be called from
107-
/// within a test method, as indicated by `XCTestCaseProvider.allTests`.
107+
/// within a test method, as passed to `XCTMain`.
108108
/// Assertion failures that occur outside of a test method will *not* be
109109
/// reported as failures.
110110
///

Sources/XCTest/XCTestCase.swift

Lines changed: 49 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,44 @@
88
//
99
//
1010
// XCTestCase.swift
11-
// Base protocol (and extension with default methods) for test cases
11+
// Base class for test cases
1212
//
1313

14-
public protocol XCTestCase : XCTestCaseProvider {
15-
func setUp()
16-
func tearDown()
14+
/// This is a compound type used by `XCTMain` to represent tests to run. It combines an
15+
/// `XCTestCase` subclass type with the list of test methods to invoke on the test case.
16+
/// This type is intended to be produced by the `testCase` helper function.
17+
/// - seealso: `testCase`
18+
/// - seealso: `XCTMain`
19+
public typealias XCTestCaseEntry = (testCaseClass: XCTestCase.Type, allTests: [(String, XCTestCase throws -> Void)])
20+
21+
public class XCTestCase {
22+
23+
public required init() {
24+
}
25+
26+
public func setUp() {
27+
}
28+
29+
public func tearDown() {
30+
}
31+
}
32+
33+
/// Wrapper function allowing an array of static test case methods to fit
34+
/// the signature required by `XCTMain`
35+
/// - seealso: `XCTMain`
36+
public func testCase<T: XCTestCase>(allTests: [(String, T -> () throws -> Void)]) -> XCTestCaseEntry {
37+
let tests: [(String, XCTestCase throws -> Void)] = allTests.map({ ($0.0, test($0.1)) })
38+
return (T.self, tests)
39+
}
40+
41+
private func test<T: XCTestCase>(testFunc: T -> () throws -> Void) -> XCTestCase throws -> Void {
42+
return { testCaseType in
43+
guard let testCase: T = testCaseType as? T else {
44+
fatalError("Attempt to invoke test on class \(T.self) with incompatible instance type \(testCaseType.dynamicType)")
45+
}
46+
47+
try testFunc(testCase)()
48+
}
1749
}
1850

1951
extension XCTestCase {
@@ -26,55 +58,55 @@ extension XCTestCase {
2658
// TODO: When using the Objective-C runtime, XCTest is able to throw an exception from an assert and then catch it at the frame above the test method. This enables the framework to effectively stop all execution in the current test. There is no such facility in Swift. Until we figure out how to get a compatible behavior, we have decided to hard-code the value of 'true' for continue after failure.
2759
}
2860
}
29-
30-
public func invokeTest() {
31-
let tests = self.allTests
61+
62+
internal static func invokeTests(tests: [(String, XCTestCase throws -> Void)]) {
3263
var totalDuration = 0.0
3364
var totalFailures = 0
3465
var unexpectedFailures = 0
3566
let overallDuration = measureTimeExecutingBlock {
3667
for (name, test) in tests {
37-
let method = "\(self.dynamicType).\(name)"
68+
let testCase = self.init()
69+
let fullName = "\(testCase.dynamicType).\(name)"
3870

3971
var failures = [XCTFailure]()
4072
XCTFailureHandler = { failure in
41-
if !self.continueAfterFailure {
42-
failure.emit(method)
73+
if !testCase.continueAfterFailure {
74+
failure.emit(fullName)
4375
fatalError("Terminating execution due to test failure", file: failure.file, line: failure.line)
4476
} else {
4577
failures.append(failure)
4678
}
4779
}
4880

49-
XCTPrint("Test Case '\(method)' started.")
81+
XCTPrint("Test Case '\(fullName)' started.")
5082

51-
setUp()
83+
testCase.setUp()
5284

5385
let duration = measureTimeExecutingBlock {
5486
do {
55-
try test()
87+
try test(testCase)
5688
} catch {
5789
let unexpectedFailure = XCTFailure(message: "", failureDescription: "threw error \"\(error)\"", expected: false, file: "<EXPR>", line: 0)
5890
XCTFailureHandler!(unexpectedFailure)
5991
}
6092
}
6193

62-
tearDown()
94+
testCase.tearDown()
6395

6496
totalDuration += duration
6597

6698
var result = "passed"
6799
for failure in failures {
68-
failure.emit(method)
100+
failure.emit(fullName)
69101
totalFailures += 1
70102
if !failure.expected {
71103
unexpectedFailures += 1
72104
}
73105
result = failures.count > 0 ? "failed" : "passed"
74106
}
75107

76-
XCTPrint("Test Case '\(method)' \(result) (\(printableStringForTimeInterval(duration)) seconds).")
77-
XCTAllRuns.append(XCTRun(duration: duration, method: method, passed: failures.count == 0, failures: failures))
108+
XCTPrint("Test Case '\(fullName)' \(result) (\(printableStringForTimeInterval(duration)) seconds).")
109+
XCTAllRuns.append(XCTRun(duration: duration, method: fullName, passed: failures.count == 0, failures: failures))
78110
XCTFailureHandler = nil
79111
}
80112
}
@@ -90,12 +122,4 @@ extension XCTestCase {
90122

91123
XCTPrint("Executed \(tests.count) test\(testCountSuffix), with \(totalFailures) failure\(failureSuffix) (\(unexpectedFailures) unexpected) in \(printableStringForTimeInterval(totalDuration)) (\(printableStringForTimeInterval(overallDuration))) seconds")
92124
}
93-
94-
public func setUp() {
95-
96-
}
97-
98-
public func tearDown() {
99-
100-
}
101125
}

Sources/XCTest/XCTestCaseProvider.swift

Lines changed: 0 additions & 18 deletions
This file was deleted.

Sources/XCTest/XCTestMain.swift

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,11 +48,31 @@ internal struct XCTRun {
4848
/// Starts a test run for the specified test cases.
4949
///
5050
/// 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)`.
51-
/// - Parameter testCases: An array of test cases to run.
52-
@noreturn public func XCTMain(testCases: [XCTestCase]) {
51+
/// Example usage:
52+
///
53+
/// class TestFoo: XCTestCase {
54+
/// static var allTests : [(String, TestFoo -> () throws -> Void)] {
55+
/// return [
56+
/// ("test_foo", test_foo),
57+
/// ("test_bar", test_bar),
58+
/// ]
59+
/// }
60+
///
61+
/// func test_foo() {
62+
/// // Test things...
63+
/// }
64+
///
65+
/// // etc...
66+
/// }
67+
///
68+
/// XCTMain([ testCase(TestFoo.allTests) ])
69+
///
70+
/// - Parameter testCases: An array of test cases run, each produced by a call to the `testCase` function
71+
/// - seealso: `testCase`
72+
@noreturn public func XCTMain(testCases: [XCTestCaseEntry]) {
5373
let overallDuration = measureTimeExecutingBlock {
54-
for testCase in testCases {
55-
testCase.invokeTest()
74+
for (testCase, tests) in testCases {
75+
testCase.invokeTests(tests)
5676
}
5777
}
5878

Tests/Functional/ErrorHandling/main.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
#endif
2828

2929
class ErrorHandling: XCTestCase {
30-
var allTests: [(String, () throws -> ())] {
30+
static var allTests: [(String, ErrorHandling -> () throws -> Void)] {
3131
return [
3232
// Tests for XCTAssertThrowsError
3333
("test_shouldButDoesNotThrowErrorInAssertion", test_shouldButDoesNotThrowErrorInAssertion),
@@ -39,7 +39,7 @@ class ErrorHandling: XCTestCase {
3939
("test_canButDoesNotThrowErrorFromTestMethod", test_canButDoesNotThrowErrorFromTestMethod),
4040

4141
// Tests for throwing assertion expressions
42-
("test_assertionExpressionCanThrow", test_assertionExpressionCanThrow),
42+
("test_assertionExpressionCanThrow", test_assertionExpressionCanThrow),
4343
]
4444
}
4545

@@ -103,4 +103,4 @@ class ErrorHandling: XCTestCase {
103103
}
104104
}
105105

106-
XCTMain([ErrorHandling()])
106+
XCTMain([testCase(ErrorHandling.allTests)])

Tests/Functional/FailingTestSuite/main.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
#endif
2323

2424
class PassingTestCase: XCTestCase {
25-
var allTests: [(String, () throws -> ())] {
25+
static var allTests: [(String, PassingTestCase -> () throws -> Void)] {
2626
return [
2727
("test_passes", test_passes),
2828
]
@@ -34,7 +34,7 @@ class PassingTestCase: XCTestCase {
3434
}
3535

3636
class FailingTestCase: XCTestCase {
37-
var allTests: [(String, () throws -> ())] {
37+
static var allTests: [(String, FailingTestCase -> () throws -> Void)] {
3838
return [
3939
("test_passes", test_passes),
4040
("test_fails", test_fails),
@@ -56,6 +56,6 @@ class FailingTestCase: XCTestCase {
5656
}
5757

5858
XCTMain([
59-
PassingTestCase(),
60-
FailingTestCase(),
59+
testCase(PassingTestCase.allTests),
60+
testCase(FailingTestCase.allTests),
6161
])

Tests/Functional/FailureMessagesTestCase/main.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@
7878

7979
// Regression test for https://github.com/apple/swift-corelibs-xctest/pull/22
8080
class FailureMessagesTestCase: XCTestCase {
81-
var allTests : [(String, () throws -> Void)] {
81+
static var allTests: [(String, FailureMessagesTestCase -> () throws -> Void)] {
8282
return [
8383
("testAssert", testAssert),
8484
("testAssertEqualOptionals", testAssertEqualOptionals),
@@ -194,4 +194,4 @@ class FailureMessagesTestCase: XCTestCase {
194194
}
195195
}
196196

197-
XCTMain([FailureMessagesTestCase()])
197+
XCTMain([testCase(FailureMessagesTestCase.allTests)])

Tests/Functional/NegativeAccuracyTestCase/main.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
// Regression test for https://github.com/apple/swift-corelibs-xctest/pull/7
2424
class NegativeAccuracyTestCase: XCTestCase {
25-
var allTests: [(String, () throws -> ())] {
25+
static var allTests: [(String, NegativeAccuracyTestCase -> () throws -> Void)] {
2626
return [
2727
("test_equalWithAccuracy_passes", test_equalWithAccuracy_passes),
2828
("test_equalWithAccuracy_fails", test_equalWithAccuracy_fails),
@@ -48,4 +48,4 @@ class NegativeAccuracyTestCase: XCTestCase {
4848
}
4949
}
5050

51-
XCTMain([NegativeAccuracyTestCase()])
51+
XCTMain([testCase(NegativeAccuracyTestCase.allTests)])

Tests/Functional/SingleFailingTestCase/main.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@
1414
#endif
1515

1616
class SingleFailingTestCase: XCTestCase {
17-
var allTests: [(String, () throws -> ())] {
17+
static var allTests: [(String, SingleFailingTestCase -> () throws -> Void)] {
1818
return [
19-
("test_fails", test_fails),
19+
("test_fails", test_fails)
2020
]
2121
}
2222

@@ -25,4 +25,4 @@ class SingleFailingTestCase: XCTestCase {
2525
}
2626
}
2727

28-
XCTMain([SingleFailingTestCase()])
28+
XCTMain([testCase(SingleFailingTestCase.allTests)])
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// RUN: %{swiftc} %s -o %{built_tests_dir}/TestCaseLifecycle
2+
// RUN: %{built_tests_dir}/TestCaseLifecycle > %t || true
3+
// RUN: %{xctest_checker} %t %s
4+
// CHECK: Test Case 'SetUpTearDownTestCase.test_hasValueFromSetUp' started.
5+
// CHECK: In setUp\(\)
6+
// CHECK: In test_hasValueFromSetUp\(\)
7+
// CHECK: In tearDown\(\)
8+
// CHECK: Test Case 'SetUpTearDownTestCase.test_hasValueFromSetUp' passed \(\d+\.\d+ seconds\).
9+
// CHECK: Executed 1 test, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
10+
// CHECK: Test Case 'NewInstanceForEachTestTestCase.test_hasInitializedValue' started.
11+
// CHECK: Test Case 'NewInstanceForEachTestTestCase.test_hasInitializedValue' passed \(\d+\.\d+ seconds\).
12+
// CHECK: Test Case 'NewInstanceForEachTestTestCase.test_hasInitializedValueInAnotherTest' started.
13+
// CHECK: Test Case 'NewInstanceForEachTestTestCase.test_hasInitializedValueInAnotherTest' passed \(\d+\.\d+ seconds\).
14+
// CHECK: Executed 2 tests, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
15+
// CHECK: Total executed 3 tests, with 0 failures \(0 unexpected\) in \d+\.\d+ \(\d+\.\d+\) seconds
16+
17+
#if os(Linux) || os(FreeBSD)
18+
import XCTest
19+
#else
20+
import SwiftXCTest
21+
#endif
22+
23+
class SetUpTearDownTestCase: XCTestCase {
24+
static var allTests: [(String, SetUpTearDownTestCase -> () throws -> Void)] {
25+
return [
26+
("test_hasValueFromSetUp", test_hasValueFromSetUp),
27+
]
28+
}
29+
30+
var value = 0
31+
32+
override func setUp() {
33+
super.setUp()
34+
print("In \(__FUNCTION__)")
35+
value = 42
36+
}
37+
38+
override func tearDown() {
39+
super.tearDown()
40+
print("In \(__FUNCTION__)")
41+
}
42+
43+
func test_hasValueFromSetUp() {
44+
print("In \(__FUNCTION__)")
45+
XCTAssertEqual(value, 42)
46+
}
47+
}
48+
49+
class NewInstanceForEachTestTestCase: XCTestCase {
50+
static var allTests: [(String, NewInstanceForEachTestTestCase -> () throws -> Void)] {
51+
return [
52+
("test_hasInitializedValue", test_hasInitializedValue),
53+
("test_hasInitializedValueInAnotherTest", test_hasInitializedValueInAnotherTest),
54+
]
55+
}
56+
57+
var value = 1
58+
59+
func test_hasInitializedValue() {
60+
XCTAssertEqual(value, 1)
61+
value += 1
62+
}
63+
64+
func test_hasInitializedValueInAnotherTest() {
65+
XCTAssertEqual(value, 1)
66+
}
67+
}
68+
69+
XCTMain([
70+
testCase(SetUpTearDownTestCase.allTests),
71+
testCase(NewInstanceForEachTestTestCase.allTests)
72+
])

0 commit comments

Comments
 (0)