Loading
BLOG 開発者ブログ

2024年4月1日

自作したiOS向けOSSライブラリのテスト設定をする

この記事は『SwiftUIを使ってOSSライブラリを作成してみた』の続きになります。

せっかく作ったライブラリなら品質を保ちながら運用していきたいところですよね?

今回はテストを導入するための準備と簡単なテストの設定をしてみようと思います。

はじめに

こんにちは。クラウドソリューショングループのtakinami.sです。

OSSライブラリは作っておしまいではなく、そこから運用を行って品質の維持・向上をしていくことが重要になります。

そんな運用を支えるテストコードに関して理解を深めていきましょう。

この記事を執筆するにあたって、前回の記事を前提に進めているところがあります。

そちらを先に読んでいただくと、この記事をスムーズに読み進められるかと思います。

デフォルトのテスト設定

前回の記事では取り上げませんでしたが、OSSライブラリのプロジェクトを作成した際に、すでに簡単なテストコードが記載されたファイルがプロジェクト内に含まれています。

テストコード用ファイル

プロジェクト直下に『Tests』というフォルダが作成されており、さらにその中に『プロジェクト名 + Tests』という名前のフォルダが作られています。

今回は『HasuoProgressSpinner』というプロジェクトでライブラリを作成したので、

Tests/HasuoProgressSpinnerTests

という階層になっていました。

最初は以下のようにテストコードが書かれています。

import XCTest
@testable import HasuoProgressSpinner

final class HasuoProgressSpinnerTests: XCTestCase {
    func testExample() throws {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct
        // results.
        XCTAssertEqual(HasuoProgressSpinner().text, "Hello, World!")
    }
}

シンプルで簡単そうにそうに見えますね。

1行目でimportしている『XCTest』はAppleから提供されているユニットテスト(単体テスト)やUIテストを行うためのフレームワークです。

2行目ではライブラリ用のクラスをimportしています。

テストの際には当然テストコード用のファイル以外の関数や変数を動かしたり見たりする必要があるわけですが、それら関数などにアクセスできるように@testableという属性をつけています。

しかし、アクセスできるようにするといっても、元のコード側でアクセスレベルを同じファイル内からしか見られないようなprivateの設定にされていると対象外となるので注意です。

/* 例えば以下のような変数にはテストであってもアクセスすることはできません */
private var text = "Hello, World"

テストの内容はtestExample()の中で書いていきます。

どんな内容が書かれているかは、後ほど触れます。

テスト用ファイルをプロジェクトに設定する

テスト用ファイルはプロジェクト直下の『Package.swift』ファイルの中で、プロジェクト設定として入っています。

以下のコードの.testTarget内がそれにあたります。

import PackageDescription

let package = Package(
    name: "HasuoProgressSpinner",
    products: [
        // Products define the executables and libraries a package produces, and make them visible to other packages.
        .library(
            name: "HasuoProgressSpinner",
            targets: ["HasuoProgressSpinner"]),
    ],
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages this package depends on.
        .target(
            name: "HasuoProgressSpinner",
            dependencies: []),
        .testTarget(
            name: "HasuoProgressSpinnerTests",
            dependencies: ["HasuoProgressSpinner"]),
    ]
)

ざっくり読み解くと、『HasuoProgressSpinnerTests』という名前のテストは『HasuoProgressSpinner』と依存関係がありますよ、という状態ですね。

テストをカスタマイズしてみよう

それでは、デフォルトで設定されているテスト内容を見てみましょう。

XCTAssertEqual(HasuoProgressSpinner().text, "Hello, World!")

XCTAssertEqualというAssertを使って、『HasuoProgressSpinner』というstructにある変数textが「Hello, World!」という文字列とイコールになるかをテストしています。

しかし、今回テスト対象となるコードでは「Hello, World!」というテキストは設定していないので、毎回テスト失敗となってしまいます。

しかも、struct名も変更してしまいました。

これではテストとして機能しませんね。

きちんとテストが行えるように少し内容を変えてみましょう。

XCTAssertNoThrow(HsProgress(showType: "succeed", text: "成功しました"))
XCTAssertNoThrow(HsProgress(showType: "succeed", text: nil))

XCTAssertNoThrowというAssertを使用し、実行時に例外が起きないかをテストすることにしました。

『HsProgress』というstructではshowTypetextを引数として渡して実行することができます。

基本的にそれら引数はString型で渡されることを想定していますが、null許可も入れています。

今回はtextにString型を入れて渡すテストとnullを入れて渡すテストを行い、どちらも成功するかどうかを確認しようと思います。

下の画像のようにテスト用コードの行番号のところにマークが出ていますので、そこを押してあげればテストが実行されます。

テスト用コード

テストに成功するとこのように表示されます。

iOSテスト成功

最後に

簡単に触れる程度でしたが、テストコードを見ていきました。

まだシンプルな実装しかしていないライブラリを対象にしたのであっさりした内容だけ記述しました。

実際に運用していくとなると、準備用のコードを書いたり、よりAssertを組み合わせたりする必要があると思います。

XCTestの詳細や各Assertについてなどは、下記のAppleの公式ドキュメントに目を通しておくと良いでしょう。

XCTestのドキュメント

ライブラリへのコントリビュートもお待ちしております。

HasuoProgressSpinner

では、よきOSSライフを!

takinamisのブログ