あまり意識したことなかったのですが、XCTestがどのような順序で実行されるかについて、WWDC18の What's New in Testing で語られています。
テストは、デフォルトで名前順に実行される

これを確認するため、以下のようなXcodeプロジェクトを用意しました。
- 整数と、そのインクリメント機能を持つ
Singleton
オブジェクトを用意
import Foundation
final class Singleton {
static let object = Singleton()
private(set) var value: Int = 0
private init() {}
func increment() {
value += 1
}
}
-
TestCase1
〜TestCase9
までのテストケースを用意し、以下のようなテストコードを実装
import XCTest
@testable import ParallelXCTest
class TestCase1: XCTestCase {
override func setUpWithError() throws {
}
override func tearDownWithError() throws {
}
func testExample() throws {
Singleton.object.increment()
// 1と"TestCase1"の部分はTestCaseXクラスごとに異なる
XCTAssertEqual(Singleton.object.value, 1, "TestCase1")
}
}
このテストを実行してみると、確かに TestCase1
〜 TestCase9
の順に実行されました。
ランダムな順番でテストを実行する
Testターゲットのスキーム設定から Test > Info > Options...
を開き、 Randomize execution order
を有効にします。

この状態でテストを実行すると、テストケースがランダムに実行されます。
実行順に依存するテストため、このようにほとんど失敗してしまいます。

テストを並列実行する
先ほどのスキーム設定で Execute in parallel
を有効にすると、テストが並列に実行されます。

また先のWWDCセッションにて、並列実行がどのような単位および環境で実行されるか言及しています。
- テストの並列実行は、シミュレータのクローンを複数作成することで実現する
- 各クローンは元のシミュレータの「複製」であるため、クローン間でメモリやストレージへの変更は共有されない

- テストケース単位で各クローンに分配、あるテストケースが完了したら次のテストケースを実行...という形で実行する

TestCase1
〜 TestCase9
を並列実行させてみたところ、このようにシミュレータクローンは4つ作成されましたが、すべてのテストが1つのクローンで実行されてしまいました。

これはテストコードが軽量すぎたためで、負荷の代わりに以下のようなスリープを入れて再度並列実行させてみました。
final class Singleton {
...
func increment() {
Thread.sleep(forTimeInterval: 1)
value += 1
}
}
相変わらずテストは失敗しますが、テストケースが各クローンに分配されて並列実行されていることがわかります。

もうひとつ注目すべきは、Singleton.object.value
が各クローンごとに 1, 2, 3...
と増え、最後に実行されたテストケースであっても 9
になっていません。これはテストの並列実行が「クローン間でメモリやストレージへの変更は共有されない」ためです。
各テストケース間や実行順序に依存関係がない よう、アプリケーション設計やテストケースの実装が重要であることが理解できるかと思います。
ただ別の見方をすれば、setUpWithError()
でSingletonオブジェクト等の値を変更しても tearDownWithError()
で元に戻せば、他のクローンや後続のテストケースへは影響を与えないとも言えます。
XCTestの振る舞いを理解しておけば、広範により良いテストコードが書けるのではないかと思います!