Swift Testingで実装コードとテストコードを同じファイルに記述する方法

2025/03/01に公開

概要

ChatGPT等が強い現在ですが、テストコードと実装コードは同じファイルであるとコンテキストがファイル内に収まっていてとても良いのではと思いました。そこで、この記事ではSwiftにおいて実装コードとテストコードを同じファイルに記述する方法を見つけたので紹介します。

結論

func fizzBuzz(for n: Int) -> String {
    switch n {
    case let n where n % 15 == 0: "FizzBuzz"
    case let n where n % 3 == 0: "Fizz"
    case let n where n % 5 == 0: "Buzz"
    case let n: String(n)
    }
}

#if DEBUG
import Testing

@Test("3の倍数でFizzを返す", arguments: [3, 6, 9, 12, 18, 21, 24, 27, 33])
func test3(n: Int) async throws {
    #expect(fizzBuzz(for: n) == "Fizz")
}

@Test("5の倍数でBuzzを返す", arguments: [5, 10, 20, 25, 35])
func test5(n: Int) async throws {
    #expect(fizzBuzz(for: n) == "Buzz")
}

@Test("15の倍数でFizzBuzzを返す", arguments: [
    15, 30, 45
])
func test15(n: Int) async throws {
    #expect(fizzBuzz(for: n) == "FizzBuzz")
}

@Test("3の倍数でも5の倍数でもないのであればその数字を返す", arguments: [
    1, 2, 4, 7, 8, 11, 13, 14, 16, 17, 19
])
func testN(n: Int) async throws {
    #expect(fizzBuzz(for: n) == "\(n)")
}
#endif

Xcodeでも基本的に問題なく使用できています。チェックマークの反映が⌘Uのときとテスト横を押した時で異なっていたり、⌘Uでテスト全実行しないとテストの横のチェックマークが出なかったりなどしましたが、いつもの通りの壊れ具合だと認識しています。

問題なくXcodeで使用できている画像

swift testコマンドでも大丈夫でした

% swift test
Building for debugging...
[13/13] Linking TestSameFilePackageTests
Build complete! (3.59s)
Test Suite 'All tests' started at 2025-03-01 19:15:18.862.
Test Suite 'All tests' passed at 2025-03-01 19:15:18.864.
	 Executed 0 tests, with 0 failures (0 unexpected) in 0.000 (0.002) seconds
􀟈  Test run started.
􀄵  Testing Library Version: 102 (arm64e-apple-macos13.0)
􀟈  Test example() started.
􀟈  Test "3の倍数でも5の倍数でもないのであればその数字を返す" started.
􀟈  Test "3の倍数でFizzを返す" started.
􀟈  Test "15の倍数でFizzBuzzを返す" started.
􀟈  Test "5の倍数でBuzzを返す" started.
􀟈  Passing 1 argument n → 12 to "3の倍数でFizzを返す"
􀟈  Passing 1 argument n → 4 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 21 to "3の倍数でFizzを返す"
􀟈  Passing 1 argument n → 1 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 2 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 3 to "3の倍数でFizzを返す"
􀟈  Passing 1 argument n → 6 to "3の倍数でFizzを返す"
􀟈  Passing 1 argument n → 9 to "3の倍数でFizzを返す"
􀟈  Passing 1 argument n → 24 to "3の倍数でFizzを返す"
􀟈  Passing 1 argument n → 18 to "3の倍数でFizzを返す"
􀟈  Passing 1 argument n → 7 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 8 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 33 to "3の倍数でFizzを返す"
􀟈  Passing 1 argument n → 11 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 27 to "3の倍数でFizzを返す"
􀟈  Passing 1 argument n → 14 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 16 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 15 to "15の倍数でFizzBuzzを返す"
􀟈  Passing 1 argument n → 19 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 45 to "15の倍数でFizzBuzzを返す"
􀟈  Passing 1 argument n → 13 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 5 to "5の倍数でBuzzを返す"
􀟈  Passing 1 argument n → 10 to "5の倍数でBuzzを返す"
􀟈  Passing 1 argument n → 17 to "3の倍数でも5の倍数でもないのであればその数字を返す"
􀟈  Passing 1 argument n → 25 to "5の倍数でBuzzを返す"
􁁛  Test example() passed after 0.001 seconds.
􀟈  Passing 1 argument n → 30 to "15の倍数でFizzBuzzを返す"
􀟈  Passing 1 argument n → 35 to "5の倍数でBuzzを返す"
􀟈  Passing 1 argument n → 20 to "5の倍数でBuzzを返す"
􁁛  Test "15の倍数でFizzBuzzを返す" passed after 0.001 seconds.
􁁛  Test "5の倍数でBuzzを返す" passed after 0.001 seconds.
􁁛  Test "3の倍数でも5の倍数でもないのであればその数字を返す" passed after 0.001 seconds.
􁁛  Test "3の倍数でFizzを返す" passed after 0.001 seconds.
􁁛  Test run with 5 tests passed after 0.001 seconds.

Discussion