NSAssert("#testing".existing)XCTAssert(everybodyDoingTesting)一つのテストケースに複数のアサーションを書いてしまうと、もしアサーションの評価が失敗だったときにどれが失敗したのかがわかりにくいという問題です。 の「詳しくは」先のコメント https://qiita.com/hmhmsh/items/842e795d4b2a41556b5b#comment-a9bd5d57270c1eebfee2 ですが、XCTAssert系は失敗しても止まらないですよ。XCTSAssert 系メソッドを書いてます。「XCTAssert系は失敗しても止まらない」ので、↓を作ったときは失敗の数が減っていくのを確認しながらバグフィックスしました。 https://github.com/koher/EasyImagy/blob/dev-0.4.0/Tests/EasyImagyTests/ExtrapolationTests.swift (edited)continueAfterFailure が false の場合は1つのアサーションが失敗したら、そのテストメソッドはそれ以降実行されない、 true なら続きも実行される。デフォルトは true (edited)guard して XCTFail → return してました。 (edited)false になってます…。 true なら続きも実行される。ですかね。恐縮っす。
(edited)XCTAssert系は失敗しても止まらないのもcontinueAfterFailureも知りませんでした...ありがとうございます。Discussion The default is true. Set this property to false within a test method to end test execution as soon as a failure occurs.
https://developer.apple.com/documentation/xctest/xctestcase/1496260-continueafterfailurefunc urlOrNil1(urlString: String?) -> URL? { guard let urlString = urlString else { return nil } return URL(string: urlString) } func urlOrNil2(urlString: String?) -> URL? { return urlString.flatMap(URL.init(string:)) } 後者の書き方を使うことが多かったのだけど、前者だとnilを返すパターンをテストできてるかどうかcode coverageを使えばチェックできる事に気付いた。Optional の map や flatMap を使ってましたが、今はほとんど guard let 使ってます。可読性のために。 ?. も必要最小限しか使わないですね。nil になるのがロジックエラーなら)よく使います。できるだけ、それが nil にならない理由をコメントをつけるようにしています(本当はコメントではなくエラーメッセージにしてくれるものがあれば標準であればいいですが)。! を含むロジックエラーは Swift でテストできないのが難点ですね。precondition に引っかかることをテストしたいこととか結構よくあります・・・。precondition に引っかかったらクラッシュしちゃいません?テストで precondition 等の失敗をハンドルする方法あるんですっけ?! の失敗も。x に負の数を入れて正しくエラーになることをテストしたいです。 func foo(_ x: Int) { preconditon(x >= 0) // x を使う処理 }RuntimeException なので catch できるんですが・・・。precondition や ! の失敗を階層的に食い止められるようになるならかなり良さそうだと妄想してます。var x: String? = nil var y: String = x!precondition にひっかかるテストはできるかも。
static let all: DispatchSource.ProcessEvent static let exec: DispatchSource.ProcessEvent static let exit: DispatchSource.ProcessEvent static let fork: DispatchSource.ProcessEvent static let signal: DispatchSource.ProcessEvent! の失敗やオーバーフローにどう対処するかって話をしてるし、それを actor で isolation する話っぽいから、目指してるものとしてはドンピシャのように思うけど、中身はそうじゃないのかな? (edited)viewDidAppear(:) で呼ばれるメソッド等をテストしたい場合って皆さんどうしてますか? let viewController = HogeViewController() window.rootViewController = viewController window.makeKeyAndVisible() let expectation = XCTestExpectation(description: "wait viewDidAppear") DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { XCTContext.runActivity(named: "viewDidAppearでメソッドがコールされた事を確認", block: { (_) in XCTAssertTrue(viewController.calledViewDidAppear) }) expectation.fulfill() } wait(for: [expectation], timeout: 0.2) みたいなコードが乱立するとユニットテストの時間がどんどん長くなるんじゃないかという事を危惧しています。When running several simulators simultaneously you may exceed the maximum number of processes or maximum open file limits. If this happens attempts to start new processes or open more files will fail. You can recover by shutting down some simulators or closing open programs. macOS 10.13 automatically scales these limits based on available system memory. On earlier releases these limits are fixed values. These limits can be increased by using launchctl. For more information, choose Help > Simulator Help and navigate to Troubleshoot Simulator > Insufficient resources. (31179087)XCTAssert(xs.contains(4)) | | | | false 4 [1, 2, 3] SwiftPowerAssertがそこそこ安定してきたのでバージョン0.1.0として公開しました。 https://github.com/kishikawakatsumi/SwiftPowerAssert 現時点でもTDDスタイルの開発などにけっこう便利に使えるような気がします。 簡単に試せるようにオンラインのPlaygroundも用意しました。 https://swift-power-assert.kishikawakatsumi.com/ 使ってみてうまく動かないパターンを教えてもらえると嬉しいです。fatalError() や preconditionFailure(), Optionalの!やtry!などで発生したエラー、すなわちSwiftのエラーの分類で言うところの**Universal...struct Example { let a: String let b: String // ... } let example = Example() XCTAssertTrue(example :> (a: "foo", b: "bar")) (edited):> を提供する感じですねa > b を a < b とかに書き換えたときに、ちゃんとテストが検知できるか、みたいなのを検査するイメージです (edited)Supported mutation operators Math: Add,Sub,Mul,Div: replaces + with -, - with +, * with /, / with *. Negate Condition: inverses conditions like A -> !A or == -> !=. Remove Void Function Call: removes a function which doesn't have a return value. Replace Call: replaces function calls with Int, Float and Double return values with number 42. Alpha state, not enabled by default. Scalar Value: replaces 0 with 1, other numbers with 0. Alpha state, not enabled by default. AND <-> OR: replaces && -> || and vice versa. Alpha state, not enabled by default.func group: (id: Int) -> Observable<Group> init( group: @escaping ((_ id: Int) -> Observable<Group>) = { _ in Observable.never() } ) { self.group = group } とかやれたら最高なんですけど (edited)protocol Animal { func group(id: Int) -> Observable<Group> func bark() -> String } まずはAnimalを使ったモジュールのテストを書いていこうというとき、現状では↓のような汎用のスタブを作り struct StubAnimal { func group(id: Int) -> Observable<Group> { return _group(id) } private var group: ( id: Int) -> Observable<Group> func bark() -> String { return _bark() } private var _bark: () -> String init( group: @escaping ((_ id: Int) -> Observable<Group>) = { _ in Observable.never() } , bark: @escaping (() -> String) = { _ in "meow" } ) { _group = group _bark = bark } } SUTに関係ない関数はデフォルト引数で済ませられるよう、また、何に関心を持っているか、何がSUTへの間接入力になりうるかが明確になるようにしています。 (edited)func testX() { let x: Float80 = 0 XCTAssertEqual(x, 0) // OK XCTAssertEqual(x, 0, accuracy: 1e-4) // EXC_BAD_INSTRUCTION } XCTestこれだけでクラッシュしててひどいpreconditionFailure ? https://github.com/apple/swift/blob/master/stdlib/public/Darwin/XCTest/XCTest.swift#L391expectCrashが欲しい…/// Asserts that an expression crashes. /// - Important: **The expression will not be evaluated if current process is being debugged.** /// - Parameters: /// - expression: An `expression` that can crash. /// - message: An optional description of the failure. /// - file: The file in which failure occurred. /// Defaults to the file name of the test case in which this function was called. /// - line: The line number on which failure occurred. /// Defaults to the line number on which this function was called. /// - signalHandler: An optional handler for signal that are produced by `expression`. /// - stdoutHandler: An optional handler for stdout that are produced by `expression`. /// - stderrHandler: An optional handler for stderr that are produced by `expression`. /// - Returns: A value of type `T?`, the result of evaluating the given `expression`. /// `nil` if expression crashed. @discardableResult public func XCTAssertCrash<T>( _ expression: @escaping @autoclosure () -> T, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line, signalHandler: (Int32) -> Void = { _ in }, stdoutHandler: (String) -> Void = { _ in }, stderrHandler: (String) -> Void = { _ in } ) -> T? (edited)XCTAssertCrashesの方が良いだろうか。ThrowAssertion()ってのがあるのね。 https://github.com/Quick/Nimble/blob/master/Sources/Nimble/Matchers/ThrowAssertion.swiftfatalErrorになる挙動も真似したいとか。Nimbleは、僕が避けたMach exceptionsの仕組みを使うライブラリを利用してXcodeのlldbと共存出来てるのすごい。 https://github.com/mattgallagher/CwlPreconditionTesting (edited)XCTAssertUnrecoverableはsigactionが内部で使われてるから、僕のと同じ制限があるな。setjump, longjumpを使わずに、別Threadでクロージャを実行して、trap有無を確認してからThread.exit()な感じにした。--parallelを使うと全部別プロセスになるから、問題なくなる。 (edited)Thread.exit()しない方が良いかも。
XCTAssertCrash()を作ったとして、XCTAssertNoCrash()は必要なのか? (edited)XCTAssertNoCrash()は不要ぽい。swift api-digesterを使ってる。 https://github.com/apple/swift-nio/blob/master/scripts/check_no_api_breakages.sh#L100-L101 swift api-digester -sdk "$sdk" -diagnose-sdk \ --input-paths "$tmpdir/api-old/$f" -input-paths "$tmpdir/api-new/$f" 2>&1 \ > "$report" 2>&1XCTAssertCrash()をMac Exception仕様への書き換えがほぼ終わったので、iOSデバイス上のテストでどうなるか確認してみた。assert系は実機だと全部ブレークポイントになってるから、デバッガで設定するブレークポイントと区別をつけるのが無理かも? (edited)assert系は実機だと全部ブレークポイントになってるから、デバッガで設定するブレークポイントと区別をつけるのが無理かも? CustomTraversable それっぽいものが!async な API をテストするときのベストプラクティス的な方法ってありますか?↓ここに書かれている extension ? https://github.com/Quick/Quick/issues/1084#issuecomment-1007665281 (edited)
1func XCTAssertGreaterThanOrPrint(_ actual: Float, _ expected: Float, file: StaticString = #file, line: UInt = #line) { if actual > expected { print("actual: \(actual)") return } XCTAssertGreaterThan(actual, expected, file: file, line: line) } こんな感じで print 付きのラッパーを作るとかですかね。 成功だけどエディタ上に表示する方法は知らないです、無い気がします。 (edited)
1Test Suite 'GSetTests' started at 2023-05-02 09:56:52.910 Test Case '-[LWWElementDictionaryTests.GSetTests testAppend]' started. Test Case '-[LWWElementDictionaryTests.GSetTests testAppend]' passed (0.001 seconds). Test Case '-[LWWElementDictionaryTests.GSetTests testCompare]' started. Test Case '-[LWWElementDictionaryTests.GSetTests testCompare]' passed (0.001 seconds). Test Case '-[LWWElementDictionaryTests.GSetTests testContains]' started. Test Case '-[LWWElementDictionaryTests.GSetTests testContains]' passed (0.000 seconds). Test Case '-[LWWElementDictionaryTests.GSetTests testMerge]' started. Test Case '-[LWWElementDictionaryTests.GSetTests testMerge]' passed (0.000 seconds). Test Case '-[LWWElementDictionaryTests.GSetTests testReplace]' started. Test Case '-[LWWElementDictionaryTests.GSetTests testReplace]' passed (0.000 seconds). Test Case '-[LWWElementDictionaryTests.GSetTests testSubscript]' started. /Users/katsumi/Developer/LWWElementDictionary/LWWElementDictionaryTests/GSetTests.swift:70: error: -[LWWElementDictionaryTests.GSetTests testSubscript] : XCTAssertNil failed: "a" Test Case '-[LWWElementDictionaryTests.GSetTests testSubscript]' failed (0.015 seconds). Test Suite 'GSetTests' failed at 2023-05-02 09:56:52.929. Executed 6 tests, with 1 failure (0 unexpected) in 0.018 (0.019) seconds コマンドラインから実行したとき(xcodebuild test ~)は個々のテストケースのログが出なくなってる? Test suite 'GSetTests' started on 'My Mac - xctest (57795)' Test case 'GSetTests.testAppend()' passed on 'My Mac - xctest (57795)' (0.001 seconds) Test case 'GSetTests.testCompare()' passed on 'My Mac - xctest (57795)' (0.001 seconds) Test case 'GSetTests.testContains()' passed on 'My Mac - xctest (57795)' (0.000 seconds) Test case 'GSetTests.testMerge()' passed on 'My Mac - xctest (57795)' (0.001 seconds) Test case 'GSetTests.testReplace()' passed on 'My Mac - xctest (57795)' (0.008 seconds) Test case 'GSetTests.testSubscript()' failed on 'My Mac - xctest (57795)' (0.021 seconds)-parallel-testing-enabled NO にしたら出るようになったけどどういうことなんだろう?コンソール出力が混ざるのを恐れているっていうことなのかな。
defaults write com.apple.dt.xcodebuild ParallelTestRunnerStdoutMirroringEnabled -bool YES このUser Defaultsを設定すると並列テストが有効でもログがちゃんと出るのをラボで教えてもらった。
10swift test の出力結果の視認性が良くなったのは個人的にはかなり良かったです. XCTest の出力結果はよく目を凝らして読まないと成功したのか失敗したのかどのテストケースが失敗したのかがわからなくて辛かったので.
1@Test の arguments でテストケースを増やしてますが,0.10.0 時点ではこんな感じですね.(xcodebuild じゃないのでそこで違いはあるかもしれないですが) https://github.com/kkebo/zyphy/actions/runs/9456496307/job/26048489628#step:7:387 (edited)
1◇ Test run started. ↳ Testing Library Version: 69d59cfc76e5daf498ca61f5af409f594768eef9 ◇ Test "html5lib-tests" started. ◇ Test namedCharRef() started. ◇ Test basicHTML() started. (中略) ◇ Passing 1 argument testCase → \u000B (scriptData) to "html5lib-tests" ◇ Passing 1 argument testCase → \u000B (cdataSection) to "html5lib-tests" ✘ Test "html5lib-tests" recorded an issue with 1 argument testCase → \u000B (plaintext) at HTML5LibTests.swift:45:5: Expectation failed: (tokenizer.sink.errors.count → 0) == (testCase.errors.count → 1) ✘ Test "html5lib-tests" recorded an issue with 1 argument testCase → \u000B (rcdata) at HTML5LibTests.swift:45:5: Expectation failed: (tokenizer.sink.errors.count → 0) == (testCase.errors.count → 1) ✘ Test "html5lib-tests" recorded an issue with 1 argument testCase → \u000B (scriptData) at HTML5LibTests.swift:45:5: Expectation failed: (tokenizer.sink.errors.count → 0) == (testCase.errors.count → 1) ◇ Passing 1 argument testCase → \u000C to "html5lib-tests" ◇ Passing 1 argument testCase → \u000C (plaintext) to "html5lib-tests" ✘ Test "html5lib-tests" recorded an issue with 1 argument testCase → \u000B (rawtext) at HTML5LibTests.swift:45:5: Expectation failed: (tokenizer.sink.errors.count → 0) == (testCase.errors.count → 1) ◇ Passing 1 argument testCase → \u000C (scriptData) to "html5lib-tests" ◇ Passing 1 argument testCase → \u000C (rawtext) to "html5lib-tests" ✘ Test "html5lib-tests" recorded an issue with 1 argument testCase → \u000B (cdataSection) at HTML5LibTests.swift:45:5: Expectation failed: (tokenizer.sink.errors.count → 1) == (testCase.errors.count → 2) ◇ Passing 1 argument testCase → \u000C (rcdata) to "html5lib-tests" ◇ Passing 1 argument testCase → \u000C (cdataSection) to "html5lib-tests" (中略) ✘ Test "html5lib-tests" failed after 0.174 seconds with 190 issues. ✘ Test run with 3 tests failed after 0.175 seconds with 190 issues.@Test の arguments でテストケースを増やしてますが,0.10.0 時点ではこんな感じですね.(xcodebuild じゃないのでそこで違いはあるかもしれないですが) https://github.com/kkebo/zyphy/actions/runs/9456496307/job/26048489628#step:7:387 (edited)✘ Test "html5lib-tests" recorded an issue with 1 argument testCase → Truncated doctype start at HTML5LibTests.swift:44:5: Expectation failed: (tokenizer.sink.tokens → [Tokenizer.Token.comment(["!", "D", "O", "C"]), Tokenizer.Token.eof]) == (testCase.tokens → [Tokenizer.Token.comment(["D", "O", "C"]), Tokenizer.Token.eof]) ± inserted [Tokenizer.Token.comment(["!", "D", "O", "C"])], removed [Tokenizer.Token.comment(["D", "O", "C"])]✘ Test namedCharRef() recorded an issue at HTMLEntitiesTests.swift:16:5: Expectation failed: (Metadata(duration: .seconds(0), resolution: .init(width: 1920, height: 1080)) → Metadata(duration: 0.0 seconds, resolution: Foundation.CGSize(width: 1920.0, height: 1080.0))) == (Metadata(duration: .seconds(90), resolution: .init(width: 3840, height: 2160)) → Metadata(duration: 90.0 seconds, resolution: Foundation.CGSize(width: 3840.0, height: 2160.0)))CustomDebugStringConvertible を真面目に実装しているかデフォルト実装ならちゃんと出るみたいですね. 逆に CustomDebugStringConvertible の debugDescription を "hoge" とか返すようにしていると残念なことになっちゃいます.(Data の比較とか) ✘ Test namedCharRef() recorded an issue at HTMLEntitiesTests.swift:23:5: Expectation failed: (Metadata(duration: .seconds(0), resolution: .init(width: 1920, height: 1080)) → hoge) == (Metadata(duration: .seconds(90), resolution: .init(width: 3840, height: 2160)) → hoge) (edited)Data は inserted と removed で Array の時と同じくイテレートできる要素単位で教えてくれるっぽいです. ✘ Test namedCharRef() recorded an issue at HTMLEntitiesTests.swift:22:5: Expectation failed: ("hoge".data(using: .utf8)! → 4 bytes) == ("fuga".data(using: .utf8)! → 4 bytes) ± inserted [104, 111, 101], removed [102, 117, 97]--experimental-event-stream-output フラグ付きでテストバイナリをビルドするとテストバイナリがテスト中のイベントを named pipe に吐き出してくれるようになって,VS Code extension 側でその named pipe を監視するみたいな仕組みみたいですね.(他にも細々した工夫があるんでしょうけれど) https://github.com/swiftlang/vscode-swift/pull/775@Suite struct SomeTest { private static let dataSets: [(a: String, b: String, _ c: String)] = [ ("a", "b", "c"), ("d", "e", "f"), ] @Test(arguments: dataSets) // ← OK func doSomethingABC(_ a: String, _ b: String, c: String) { } @Test(arguments: dataSets.map { ($0.a, $0.b) }) // ← NG func doSomethingAB(_ a: String, _ b: String) { } @Test(arguments: dataSets.map { ($0.a, $0.c) }) // ← NG func doSomethingAC(_ a: String, _ c: String) { } } (edited)@Suite struct SomeTest { private static let dataSets: [(a: String, b: String, c: String)] = [ ("a", "b", "c"), ("d", "e", "f"), ] @Test(arguments: dataSets) func doSomethingABC(_ a: String, _ b: String, _ c: String) { } @Test(arguments: dataSets) func doSomethingAB(_ a: String, _ b: String, _: String) { } @Test(arguments: dataSets) func doSomethingAC(_ a: String, _: String, _ c: String) { } } (edited)scopeProviderというスコープを生成するファクトリもある?#expect(exitsWith:) で挟んだ領域を別プロセスで実行してステータスコードなどを見るらしい (edited)XCTAssertCrashってのを昔作ったことを思い出した。[https://github.com/norio-nomura/XCTAssertCrash]@Test func helloWorld() { let greeting = "Hello, world!" #expect(greeting == "Hello") // Expectation failed: (greeting → "Hello, world!") == "Hello" }@Test func helloWorld() { let greeting = "Hello, world!" #expect(greeting == "Hello") // Expectation failed: (greeting → "Hello, world!") == "Hello" } ◇ Test run started. ↳ Testing Library Version: 6.2-dev (a7b5435933149b6) ↳ Target Platform: x86_64-unknown-linux-gnu ◇ Test helloWorld() started. ✘ Test helloWorld() recorded an issue at <stdin>:5:3: Expectation failed: (greeting → "Hello, world!") == "Hello" ✘ Test helloWorld() failed after 0.001 seconds with 1 issue. ✘ Test run with 1 test failed after 0.002 seconds with 1 issue.swiftにJIT実行させるのではなく、swiftcを呼び出して実行可能ファイルを生成してから実行する仕組みになっています。import Testing やテストコードを呼び出す await Testing.__swiftPMEntryPoint() as Never などのコードは無ければ勝手に追加してビルドします。__swiftPMEntryPoint の中にあるってことですか?@Suite や @Test は実質的にリフレクションなのか@Foo がついてるものを実行時に全部集めるの、自分でもやりたいことがあるimport Testing @Test func helloWorld() { let greeting = "Hello, world!" #expect(greeting == "Hello, world!") } await Testing.__swiftPMEntryPoint() as Neverimport Testing @Test func helloWorld() { let greeting = "Hello, world!" #expect(greeting == "Hello, world!") } await Testing.__swiftPMEntryPoint() as Never swift testing -
<stdin>:8:15: warning: will never be executed 4 | #expect(greeting == "Hello, world!") 5 | } 6 | await Testing.__swiftPMEntryPoint() as Never | `- note: a call to a never-returning function 7 | 8 | await Testing.__swiftPMEntryPoint() as Never | `- warning: will never be executed 9 | ◇ Test run started. ↳ Testing Library Version: 6.2-dev (a7b5435933149b6) ↳ Target Platform: x86_64-unknown-linux-gnu ◇ Test helloWorld() started. ✔ Test helloWorld() passed after 0.001 seconds. ✔ Test run with 1 test passed after 0.001 seconds.import Testing @Test func helloWorld() { let greeting = "Hello, world!" #expect(greeting == "Hello, world!") } await Testing.__swiftPMEntryPoint() as Never swift - exit status: 69 with ◇ Test run started. ↳ Testing Library Version: 6.2-dev (a7b5435933149b6) ↳ Target Platform: x86_64-unknown-linux-gnu ✔ Test run with 0 tests passed after 0.001 seconds.mainを作るので見つけてくれる。 @swift-main swiftc import Testing @Test func helloWorld() { let greeting = "Hello, world!" #expect(greeting == "Hello, world!") } await Testing.__swiftPMEntryPoint() as Nevermainを作るので見つけてくれる。 @swift-main swiftc import Testing @Test func helloWorld() { let greeting = "Hello, world!" #expect(greeting == "Hello, world!") } await Testing.__swiftPMEntryPoint() as Never ◇ Test run started. ↳ Testing Library Version: 6.2-dev (a7b5435933149b6) ↳ Target Platform: x86_64-unknown-linux-gnu ◇ Test helloWorld() started. ✔ Test helloWorld() passed after 0.001 seconds. ✔ Test run with 1 test passed after 0.001 seconds.@Test func helloWorld() { let greeting = "Hello, world!" #expect(greeting == "Hello, world!") // Expectation failed: (greeting → "Hello, world!") == "Hello" }@Test func helloWorld() { let greeting = "Hello, world!" #expect(greeting == "Hello, world!") // Expectation failed: (greeting → "Hello, world!") == "Hello" } ◇ Test run started. ↳ Testing Library Version: 6.2-dev (cabf4d419cf9c8c) ↳ Target Platform: x86_64-unknown-linux-gnu ◇ Test helloWorld() started. ✔ Test helloWorld() passed after 0.074 seconds. ✔ Test run with 1 test passed after 0.078 seconds.swift replの起動時にはフレームワークサーチパス-Fが使われていないから、swift-testingがTesting.frameworkで提供されてるmacOSではswift replでimport Testingが動かないのね。$ swift repl -v Apple Swift version 6.1 (swiftlang-6.1.0.110.21 clang-1700.0.13.3) Target: arm64-apple-macosx15.0 /Applications/Xcode_16.3.app/Contents/Developer/usr/bin/lldb '--repl=-Xllvm -aarch64-use-tbi -enable-objc-interop -stack-check -sdk /Applications/Xcode_16.3.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.4.sdk -color-diagnostics -new-driver-path /Applications/Xcode_16.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/swift-driver -empty-abi-descriptor -resource-dir /Applications/Xcode_16.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift -disable-clang-spi -target-sdk-version 15.4 -target-sdk-name macosx15.4 -external-plugin-path /Applications/Xcode_16.3.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/lib/swift/host/plugins#/Applications/Xcode_16.3.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/bin/swift-plugin-server -external-plugin-path /Applications/Xcode_16.3.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/local/lib/swift/host/plugins#/Applications/Xcode_16.3.app/Contents/Developer/Platforms/MacOSX.platform/Developer/usr/bin/swift-plugin-server -in-process-plugin-server-path /Applications/Xcode_16.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/libSwiftInProcPluginServer.dylib -plugin-path /Applications/Xcode_16.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/swift/host/plugins -plugin-path /Applications/Xcode_16.3.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/local/lib/swift/host/plugins' Welcome to Apple Swift version 6.1 (swiftlang-6.1.0.110.21 clang-1700.0.13.3). Type :help for assistance. 1>swift replでimport Testingを使う(動かないけど)には-Fをつける $ swift repl -F $(xcrun --show-sdk-platform-path)/Developer/Library/Frameworks Welcome to Apple Swift version 6.1 (swiftlang-6.1.0.110.21 clang-1700.0.13.3). Type :help for assistance. 1> import Testing 2> @Test func helloWorld() { 3. let greeting = "Hello, world!" 4. #expect(greeting == "Hello") // Expectation failed: (greeting → "Hello, world!") == "Hello" 5. } 6> await Testing.__swiftPMEntryPoint() as CInt Test run started. Testing Library Version: 124 Target Platform: arm64e-apple-macos14.0 Test run with 0 tests passed after 0.001 seconds. REPL requires a running target process. error: REPL process is no longer alive, exiting REPL (edited)isRecursive を使う場合は、そのトレイトは SuiteTrait かつ TestTrait でないといけない、違反するとノーヒントでランタイムクラッシュする (edited)SuiteTrait かつ TestTrait のトレイトが @Test に付与されている場合、 scopeProvider メソッドは SuiteTrait としてと、 TestTrait としてで、2回呼ばれ、testCase 引数が nil かどうかで見分けられる (edited)isRecursive を使うと、そのトレイトが、子孫の @Suite と @Test にも付与されたかのように振る舞う、これは test: Test の .traits プロパティで見ると実際に付与されている (edited)@Test に対して、同じトレイトの scopeProvider が2回呼ばれること (B) test 引数にも isSuite プロパティがあり、 testCase 引数も nil かどうかでスイートかどうかを表すが、これが冗長な理由 つまり test.isSuite = false && testCase == nil が起こりうるのかどうか、もし起こるとしたら何を意味するか。 (edited)scopeProvider メソッドを使わない(デフォルト実装で)で、 provideScope メソッドだけ使う場合だと、isRecursive なら、 @Test の訪問時だけ、1回だけ呼ばれるようになります。 (edited)scopeProvider のデフォルト実装が (B) のケースをカットする(nilを返す)からです@Suite struct MyTestSuite { init() { print("foo") } @Test func aaa() { } @Test func iii() { } } これでswift testすると、"foo"が2回でるstruct MySuiteTrait: SuiteTrait & TestScoping { func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter suite scope") defer { print("leave suite scope") } try await function() } } (edited)struct MyTestTrait: TestTrait & TestScoping { func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter test") defer { print("leave test") } try await function() } } こうするとテストの開始・終了を挟める。struct MyRecursiveSuiteTrait: SuiteTrait & TestScoping { var isRecursive: Bool { true } func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter test") defer { print("leave test") } try await function() } } (edited)@Suite(.mySuite, .myRecursiveSuite) って両方指定しないといけないから// a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() {} // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() {} }func foo に注目していました。test: Test が違うから。testCase: Test.Case? はどちらも nil だから役に立たないけど。 (edited)import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestTrait & TestScoping { var isRecursive: Bool { true } func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter test") defer { print("leave test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } } (edited)import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestTrait & TestScoping { var isRecursive: Bool { true } func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter test") defer { print("leave test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } } (edited)enter test enter test bar leave test foo leave test◇ Test run started. ↳ Testing Library Version: 6.2 (3fdabe5392108d8) ↳ Target Platform: aarch64-unknown-linux-gnu ◇ Suite MyTest started. ◇ Test foo() started. ◇ Test bar() started. ✔ Test bar() passed after 0.001 seconds. ✔ Test foo() passed after 0.001 seconds. ✔ Suite MyTest passed after 0.001 seconds. ✔ Test run with 2 tests in 1 suite passed after 0.001 seconds. (edited)struct MyRecursiveSuiteTrait: SuiteTrait & TestTrait & TestScoping { ... でなければいけないです (edited)import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestTrait & TestScoping { var isRecursive: Bool { true } func scopeProvider(for test: Test, testCase: Test.Case?) -> MyRecursiveSuiteTrait? { print("scopeProvider called", test.name, testCase == nil ? "suite" : "test") return self } func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter provideScope", test.name, testCase == nil ? "suite" : "test") defer { print("leave provideScope", test.name, testCase == nil ? "suite" : "test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } } (edited)import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestTrait & TestScoping { var isRecursive: Bool { true } func scopeProvider(for test: Test, testCase: Test.Case?) -> MyRecursiveSuiteTrait? { print("scopeProvider called", test.name, testCase == nil ? "suite" : "test") return self } func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter provideScope", test.name, testCase == nil ? "suite" : "test") defer { print("leave provideScope", test.name, testCase == nil ? "suite" : "test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } } (edited)scopeProvider called MyTest suite enter provideScope MyTest suite scopeProvider called bar() suite enter provideScope bar() suite scopeProvider called foo() suite enter provideScope foo() suite scopeProvider called bar() test enter provideScope bar() test bar leave provideScope bar() test scopeProvider called foo() test enter provideScope foo() test leave provideScope bar() suite foo leave provideScope foo() test leave provideScope foo() suite leave provideScope MyTest suite◇ Test run started. ↳ Testing Library Version: 6.2 (3fdabe5392108d8) ↳ Target Platform: aarch64-unknown-linux-gnu ◇ Suite MyTest started. ◇ Test bar() started. ◇ Test foo() started. ✔ Test bar() passed after 0.001 seconds. ✔ Test foo() passed after 0.001 seconds. ✔ Suite MyTest passed after 0.001 seconds. ✔ Test run with 2 tests in 1 suite passed after 0.001 seconds. (edited)scopeProvider called が合計5回ある。import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestScoping { func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter provideScope", test.name, testCase == nil ? "suite" : "test") defer { print("leave provideScope", test.name, testCase == nil ? "suite" : "test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } }import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestScoping { func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter provideScope", test.name, testCase == nil ? "suite" : "test") defer { print("leave provideScope", test.name, testCase == nil ? "suite" : "test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } } enter provideScope MyTest suite bar foo leave provideScope MyTest suite◇ Test run started. ↳ Testing Library Version: 6.2 (3fdabe5392108d8) ↳ Target Platform: aarch64-unknown-linux-gnu ◇ Suite MyTest started. ◇ Test bar() started. ◇ Test foo() started. ✔ Test bar() passed after 0.001 seconds. ✔ Test foo() passed after 0.001 seconds. ✔ Suite MyTest passed after 0.001 seconds. ✔ Test run with 2 tests in 1 suite passed after 0.001 seconds.import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestTrait & TestScoping { func scopeProvider(for test: Test, testCase: Test.Case?) -> MyRecursiveSuiteTrait? { print("scopeProvider called", test.name, testCase == nil ? "suite" : "test") return self } func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter provideScope", test.name, testCase == nil ? "suite" : "test") defer { print("leave provideScope", test.name, testCase == nil ? "suite" : "test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } } (edited)import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestTrait & TestScoping { func scopeProvider(for test: Test, testCase: Test.Case?) -> MyRecursiveSuiteTrait? { print("scopeProvider called", test.name, testCase == nil ? "suite" : "test") return self } func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter provideScope", test.name, testCase == nil ? "suite" : "test") defer { print("leave provideScope", test.name, testCase == nil ? "suite" : "test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } } (edited)scopeProvider called MyTest suite enter provideScope MyTest suite foo bar leave provideScope MyTest suite◇ Test run started. ↳ Testing Library Version: 6.2 (3fdabe5392108d8) ↳ Target Platform: aarch64-unknown-linux-gnu ◇ Suite MyTest started. ◇ Test foo() started. ◇ Test bar() started. ✔ Test foo() passed after 0.001 seconds. ✔ Test bar() passed after 0.001 seconds. ✔ Suite MyTest passed after 0.001 seconds. ✔ Test run with 2 tests in 1 suite passed after 0.001 seconds. (edited)import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestTrait & TestScoping { var isRecursive: Bool { true } func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter provideScope", test.name, testCase == nil ? "suite" : "test") defer { print("leave provideScope", test.name, testCase == nil ? "suite" : "test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } }import Testing struct MyRecursiveSuiteTrait: SuiteTrait & TestTrait & TestScoping { var isRecursive: Bool { true } func provideScope( for test: Test, testCase: Test.Case?, performing function: () async throws -> Void ) async throws { print("enter provideScope", test.name, testCase == nil ? "suite" : "test") defer { print("leave provideScope", test.name, testCase == nil ? "suite" : "test") } try await function() } } extension Trait where Self == MyRecursiveSuiteTrait { static var mySuperSuite: Self { Self() } } // a. ここで Suite trait として1回呼ばれる @Suite(.mySuperSuite) struct MyTest { // b. ここで Suite trait として1回呼ばれる // c. ここで Test trait として1回呼ばれる @Test func foo() { print("foo") } // d. ここで Suite trait として1回呼ばれる // e. ここで Test trait として1回呼ばれる @Test func bar() { print("bar") } } enter provideScope foo() test foo leave provideScope foo() test enter provideScope bar() test bar leave provideScope bar() test◇ Test run started. ↳ Testing Library Version: 6.2 (3fdabe5392108d8) ↳ Target Platform: aarch64-unknown-linux-gnu ◇ Suite MyTest started. ◇ Test foo() started. ◇ Test bar() started. ✔ Test foo() passed after 0.001 seconds. ✔ Test bar() passed after 0.001 seconds. ✔ Suite MyTest passed after 0.001 seconds. ✔ Test run with 2 tests in 1 suite passed after 0.001 seconds.