Swift

パッケージとテスト

Swift Package Managerでファイルを分割し、Swift Testingで結果を自動確認します。

対象:Swift 6系。基礎はコンソールで学びます。iOSアプリの開発にはmacOSとXcodeが必要です。

01

新しいパッケージを作る

Swift 6系とターミナルを使います。入門で使ったフォルダーとは別に、新しい空のフォルダーを作ります。SwiftPMはSwiftに付属するビルド・依存関係管理ツールです。

パッケージを作成COMMAND
mkdir StudyKit
cd StudyKit
swift package init --type library --name StudyKit

Package.swift、Sources、Testsが生成されます。次の例では生成された同名ファイルを全体ごと置き換えます。別のテストコードを残すと、同名の型や関数が重複することがあります。

02

Package.swiftで構成を確認

再現できる構成に揃えるため、Package.swiftを次の内容にします。最初の行は必要なツールのバージョンです。targetはビルドする単位、testTargetはテスト用の単位で、dependenciesに検査対象を書きます。

Package.swiftSWIFT
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
    name: "StudyKit",
    products: [.library(name: "StudyKit", targets: ["StudyKit"])],
    targets: [
        .target(name: "StudyKit"),
        .testTarget(name: "StudyKitTests", dependencies: ["StudyKit"])
    ]
)
03

処理とテストを別ファイルへ

公開する関数にpublicを付けます。この例では個数が負の場合はエラーにするルールを定めます。

Sources/StudyKit/StudyKit.swiftSWIFT
public enum TotalError: Error { case negativeCount }
public func total(price: Int, count: Int) throws -> Int {
    guard count >= 0 else { throw TotalError.negativeCount }
    return price * count
}

Swift Testingの @Test はテストを登録し、#expect は期待する条件を検査します。throwsを持つテストではtryの失敗もテスト失敗として扱われます。

Tests/StudyKitTests/StudyKitTests.swiftSWIFT
import Testing
@testable import StudyKit

@Test func normalTotal() throws {
    #expect(try total(price: 120, count: 3) == 360)
}
@Test func zeroCount() throws {
    #expect(try total(price: 120, count: 0) == 0)
}
@Test func negativeCount() {
    #expect(throws: TotalError.self) {
        try total(price: 120, count: -1)
    }
}
04

ビルドしてテストする

Package.swiftのあるフォルダーで実行します。ライブラリには画面やmainがないため、swift runではなくswift testで動作を確かめます。

ビルドとテストCOMMAND
swift build
swift test
確認する結果(ログの文面や順番は環境によって異なります)OUTPUT
ビルドが成功する
normalTotal、zeroCount、negativeCountの3テストが成功する

試しに最初の期待値360を361へ変えるとnormalTotalが失敗します。360へ戻して再実行し、再び全件成功することを確認しましょう。テストを追加するときは通常の入力だけでなく、0や範囲外も検査します。

05

依存関係を管理する

この例は外部パッケージを使いません。追加が必要になったらPackage.swiftのdependenciesへパッケージを、targetのdependenciesへ利用する製品を指定します。下のコマンドは宣言済みの依存関係を解決し、状態を表示します。

依存関係の確認COMMAND
swift package resolve
swift package show-dependencies
確認する結果OUTPUT
この例では外部依存はない
依存を追加した場合は解決されたパッケージが表示される

外部依存を使うアプリではPackage.resolvedも管理すると、使ったバージョンを共有できます。.buildは生成物なので通常Gitの管理対象にしません。ネットワークエラー時はURLと接続を確認します。

公式ドキュメントで確認する:Swift Package Managerの入門

06

練習:価格も検証する

価格が負の場合もTotalErrorを投げるように変更し、負の価格のテストを加えてください。以下は既存のソースとテストをそれぞれ置き換える解答例です。

解答例を確認する
Sources/StudyKit/StudyKit.swiftSWIFT
public enum TotalError: Error { case negativeValue }
public func total(price: Int, count: Int) throws -> Int {
    guard price >= 0, count >= 0 else { throw TotalError.negativeValue }
    return price * count
}
Tests/StudyKitTests/StudyKitTests.swiftSWIFT
import Testing
@testable import StudyKit
@Test func normalTotal() throws {
    #expect(try total(price: 120, count: 3) == 360)
}
@Test func zeroCount() throws {
    #expect(try total(price: 120, count: 0) == 0)
}
@Test func negativeCount() {
    #expect(throws: TotalError.self) { try total(price: 120, count: -1) }
}
@Test func negativePrice() {
    #expect(throws: TotalError.self) { try total(price: -1, count: 3) }
}
確認する結果OUTPUT
swift testで4テストが成功する