LoginSignup
9
4

More than 3 years have passed since last update.

[Swift 5.1] Swift Package ManagerのテストをLinux上でも実行する

Posted at

Tl;Dr

Swift 5.1 から追加された--enable-test-discoveryオプションを渡せばよい。

$ swift test --enable-test-discovery

ただし、正しく検出できないバグも残っているらしいので、心配ならば Swift 4.1 から追加された--generate-linuxmainを利用してテスト実行用のソースを自動生成できる。

$ swift test --generate-linuxmain # 自動生成
$ swift test # 実行

つまり、Swift 4.1 以降であればテスト一覧を自分で列挙する必要はない

Linux環境で実行すべきテストを列挙する(Swift 5.1未満)

Swift 5.1 においてswift package initでプロジェクトを初期化すると、Testsディレクトリ配下が以下のように生成される(生成時のルートディレクトリはSwiftPackageExample)。

$ tree Tests 
Tests
├── LinuxMain.swift
└── SwiftPackageExampleTests
    ├── SwiftPackageExampleTests.swift
    └── XCTestManifests.swift

このうちLinuxMain.swiftXCTestManifests.swiftは、Linux などの macOS 以外の OS でテストを実行するためのファイルであり、前者が『エントリポイント』、後者が『実行すべきテストの一覧を列挙』するためのものになっている。

これらが必要なのはmacOS以外の環境では実行すべきテストの一覧を自動的に検出できなかったことにある。

それぞれのソースを簡単に見てみる。

LinuxMain.swift

LinuxMain.swiftでは、後者のXCTestManifests.swiftに定義されたメソッドを呼び出すことで、実行すべきテスト一覧を取得し、XCTMain()関数でテストを実行している。

LinuxMain.swift
import XCTest

import SwiftPackageExampleTests

var tests = [XCTestCaseEntry]()
tests += SwiftPackageExampleTests.__allTests() // XCTestManifests.swiftに定義されたメソッドを呼び出し

XCTMain(tests)

XCTestManifests.swift

XCTestManifests.swiftでは、①extensionを利用して各テストクラスの『テスト名』と『テストメソッドの参照』のタプルの一覧を返すように定義され、②さらに前述のLinuxMain.swiftから呼び出される__allTests()関数においてtestCase()関数でXCTestCaseEntryに変換して実行すべきテストの一覧を返すようになっている。

XCTestManifests.swift
#if !canImport(ObjectiveC)
import XCTest

// ①
extension SwiftPackageExampleTests {
    // DO NOT MODIFY: This is autogenerated, use:
    //   `swift test --generate-linuxmain`
    // to regenerate.
    static let __allTests__SwiftPackageExampleTests = [
        ("testExample", testExample),
    ]
}

// ②
public func __allTests() -> [XCTestCaseEntry] {
    return [
        testCase(SwiftPackageExampleTests.__allTests__SwiftPackageExampleTests),
    ]
}
#endif

--generate-linuxmainで自動生成する(Swift 4.1+)

Swift 4.1未満ではXCTestManifests.swiftを自分でメンテする必要があり、テストメソッドを新たに追加するごとにテスト一覧の定義を更新する必要があった。

それが Swift 4.1 において--generate-linuxmainというオプションが追加され、完全に自動生成することが可能になった。

$ swift test --help
...
  --generate-linuxmain    Generate LinuxMain.swift entries for the package

以下のコマンドでLinuxMain.swiftXCTManifests.swiftが自動生成される。

$ swift test --generate-linuxmain

前述のXCTestManifests.swiftのコメントをみるとDO NOT MODIFY: This is autogeneratedと記述されており、自動生成されていることがコメントからも読み取れる。

// DO NOT MODIFY: This is autogenerated, use:
//   `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__SwiftPackageExampleTests = [

--enable-test-discoveryで自動検出する(Swift 5.1+)

Swift 5.1 では--enable-test-discoveryという macOS 以外において、テストの一覧を自動的に検出してテストを実行するオプションが追加された。

$ swift test --help
...
  --enable-test-discovery
                          Enable test discovery on platforms without Objective-C runtime

これを利用すれば前述したLinuxMain.swiftXCTestManifests.swiftは不要となり、Linux環境でも以下のコマンドで実行できるようになる。

$ swift test --enable-test-discovery

しかし、このオプションにはまだバグが残っているらしい(執筆時点)。

自身のプロダクトで正しく検出できるのかは、--list-testsオプションでテスト一覧を列挙し、それらの件数が一致するかを確認するとよい。

# on mac
$ swift test --list-tests 
SwiftPackageExampleTests.SwiftPackageExampleTests/testExample

# on Linux
$ swift test --enable-test-discovery --list-tests
SwiftPackageExampleTests.SwiftPackageExampleTests/testExample

もっとも、これはこの時点において正しく検出できるかの確認に過ぎないので、自身のプロダクトにおいてLinux環境でのテストが重要なものであれば、このオプションがデフォルトになる(つまり指定が不要になる)まで見送ったほうがよいかもしれない。

Dockerで実行できるようにする

mac のローカル環境でもDockerで実行できるようにしておくと何かと便利である。

以下はSwift 5.1のコンテナで実行するMakefileの例である。

Makefile
linux-test:
    docker run --rm \
        --volume "$(CURDIR):/src" \
        --workdir "/src" \
        swift:5.1 \
        swift test --enable-test-discovery

これは次のように実行できる。

$ make linux-test

ついでにコンテナにログインするターゲットを用意しておいてもよいかもしれない。

Makefile
linux:
    docker run --rm -it \
        --volume "$(CURDIR):/src" \
        --workdir "/src" \
        swift:5.1

--volumeでマウントしているので、ソースの変更などはすべてmac側で行える。

$ make linux
docker run --rm -it \
                --volume "/Users/hosonumayuusuke/go/src/github.com/YusukeHosonuma/SwiftPrettyPrint:/src" \
                --workdir "/src" \
                swift:5.1
root@ff97680b040b:/src# 

GitHub Actionsで実行できるようにする

以下のようにruns-onubuntu-latestcontainerswift:5.1を指定してジョブを定義すればよい。

name: Test

on:
  push:
    branches:
      - master
  pull_request:

jobs:
  unit-test-in-linux:
    runs-on: ubuntu-latest
    container: swift:5.1

    steps:
    - uses: actions/checkout@v2

    - name: Run Unit Test
      run: swift test --enable-test-discovery

参考PR

自身のOSSプロダクトについて、以下のPRで対応したので参考までに。
https://github.com/YusukeHosonuma/SwiftParamTest/pull/34

LinuxMain.swiftを残してしまっているが、これは本来であれば不要なのでご留意を。

参考記事

終わりに

Swift Package Manager は Swift と共に進化してきたこともあり、古いバージョンについて言及された記事も多数残っているため、参考情報として利用する際には注意が必要なように思う。

かくいう私も Swift 4.1 の時点ですでに追加されていた自動生成用の--generate-linuxmainの存在を知らず、 30個以上もあるテストの定義を手書きしてしまうところであった (というより、この記事に書いたLinux環境でのテストについて知ったのも最近のことだ)。

今であれば公式のパーサである swift-syntax もあるので、自動生成用のツールを書いてしまうかと思い、ふとすでに存在しないかとググってみたら参考記事に貼ったような情報を得られたという次第であった。

日本語圏においては Swift Package Manager の情報は英語圏に比べてさらに少なく、私自身も『LinuxMain.swiftとは』でググっても殆ど出てこない状況に遭遇したことを思い出し、筆を執ることにした。

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4