この度、JSONMatcherというSwift向けのテストライブラリを開発しました。
これは何?
SwiftでJSONのオブジェクトや文字列を検査するマッチャーです。
JSONオブジェクトのテストがこんな感じで簡単に書けます。
import XCTest import Nimble import JSONMatcher class ExampleTestCase: XCTestCase { func testComplexExample() { expect([ "name" : "Snorlax", "no" : 143, "species" : "Sleeping", "type" : ["normal"], "stats" : [ "hp" : 160, "attack" : 110, "defense" : 65, "special_attack" : 65, "special_defense" : 65, "speed" : 30 ], "moves" : [ ["name" : "Tackle", "type" : "normal", "level" : 1], ["name" : "Hyper Beam", "type" : "normal", "level" : NSNull()], ] ]).to(beJSONAs([ "name" : "Snorlax", "no" : Type.Number, // value type matching "species" : try! NSRegularExpression(pattern: "[A-Z][a-z]+", options: []), // regular expression matching "type" : ["[a-z]+".regex], // shorthands for NSRegularExpression "stats" : [ "hp" : 160, "attack" : 110, "defense" : 65, "special_attack" : 65, "special_defense" : 65, "speed" : 30 ], "moves" : [ ["name" : "Tackle", "type" : "[a-z]+".regex, "level" : Type.Number], // nested collection ["name" : "Hyper Beam", "type" : "normal", "level" : NSNull()], ] ])) } }
JSONMatcherは、Swift製のマッチャーライブラリ、Nimbleの拡張として動作し、与えられたJSON文字列、オブジェクトを簡単に比較できます。
上記のように、複雑なJSONであっても直感的にマッチャーを書くことができます。
マッチャーとして
- 与えられたオブジェクトや文字列がJSONであることを判定する
beJSON
- 特定のオブジェクトを含むかを判定する
beJSONIncluding
- 2つのオブジェクトが一致することを判定する
beJSONAs
の3種類を提供しています。
expect("{\"name\": \"Pikachu\"}").to(beJSON()) expect(["name" : "Pikachu", "no" : 25]).to(beJSONIncluding(["name" : "Pikachu"])) expect(["name" : "Pikachu", "no" : 25]).to(beJSONAs(["name": "Pikachu", "no" : 25]))
また、上記の例にあるとおり
- 正規表現(
NSRegularExpression
) - 型マッチングのようなもの
にも対応していて、かなり柔軟に比較することができます。
このライブラリは、rspec-json_matcherに大変影響を受けてます。
Swiftでライブラリを作る
今回のモチベーションの一つとして、Swiftのライブラリを最新のエコシステムを用いてしっかり作ってみたいというものがありました。
Swiftのライブラリ作成の知見は枯れておらず、有名OSSを眺めてみてもあまり統一されていなかったので、今回作成した知見をまとめてみました。
しっかりテストして、こういう風にバッヂを貼りまくると、ちゃんとしたライブラリっぽくなります。
ライブラリ作成にあたって、3月に行われたtry! Swiftの「Creating a Swift Library」というセッションが大変参考になってオススメです。
以下の書き起こしやリポジトリを参考にしてみてください。
try! Swift ライブラリの開発 #tryswiftconf Day2-8 - niwatakoのはてなブログ
ディレクトリ構造
まずSwiftのライブラリを作るにあたって、ディレクトリ構造からして悩むのですが、結論から言えば、最小構成で以下のような感じにすれば良さそうです。
実装はSources/LibraryName
以下にソースコード、Tests/LibraryName
以下にテストコードを配置するのがベストのようです。
├── Cartfile ├── Carthage │ └── Checkouts ├── JSONMatcher.xcodeproj ├── Sources │ └── JSONMatcher │ ├── *.swift │ └── Info.plist └── Tests └── JSONMatcher ├── *.swift └── Info.plist
テスト
テストは必ず記述しましょう。XCTestをそのまま使っても良いですが、今回は前述のNimbleを用いて記述しています。
今回は使用していませんが、さらにRspecっぽく書きたい方はQuickを併せて使っても良いでしょう。
テストの実行には簡単なShellスクリプトを用意して、xcodebuild test
を実行してます。
マルチプラットフォーム化
Swift向けのライブラリは、iOS、OSX、tvOS、watchOS、Linuxの5つの環境向けに作ることができます。
watchOSではそもそもXCTestが使えないため、今回は対応できず、Linux対応は面倒そうなのでやっていません。
マルチプラットフォーム対応は簡単で、以下のようにプラットフォーム別にターゲットとスキーマを作成しましょう。
最初はiOS向けのみに実装し、テストがある程度揃ってきたら、テストを実行しつつ、マルチプラットフォーム化をするのが楽でした。
CI
CI as a Serviceとして、Travis CIやCircle CIが使えます。好きなものを使いましょう。
CIでは以下のような事を行っています。
- 各プラットフォーム向けのテストの実行
- カバレッジのレポート
- swiftlintの実行
- CocoaPodsの検証
また、先日、Travis CIでのPublicリポジトリ向けのキャッシュの提供が開始されました。Swiftのライブラリはビルド時間が長くなりがちなので、依存ライブラリのビルドに時間がかかる場合は利用すると良いでしょう。
カバレッジ
ついでにテストカバレッジを取得しましょう。Xcode7からはテストスキーマのチェックを入れるだけで、簡単にカバレッジを計測できるようになりました。
計測したカバレッジは、CIからcodecov.ioというサービスに送っています。codecovはSwift対応が楽で大変便利です。
Coverallsでの表示も可能ですが、公式でサポートされていないようでした。
パッケージ管理ツール
現在、Swiftのパッケージ管理ツールとして、Carthage、CocoaPods、Swift Package Manager(SPM)の3種類がよく知られています。
対応するための作業はそれほど多くないので、とりあえず全てに対応させてます。
Carthage
基本的に上記の通り、プラットフォーム別にスキーマを作ってあげると、良い感じで対応できます。
今回のように依存関係があるライブラリの場合はCartfile
に依存環境を記述しましょう。
Carthageの設計思想は、Xcode標準のスキーマを使ったシンプルなビルドシステムであり、Carthageに対応して作ると良い感じの構造になるので、意識して設計すると良さそう。
CocoaPods
pod spec create
などを実行すると、podspec
のひな形ができるので良い感じで記述していきます。
また、CIでpod lib lint
などを回すようにしておくと、podspec
の検証ができます。
完成したPodは公式のリポジトリに登録すると良いでしょう。
SPM
Carthage対応ができていればPackage.swift
というファイルを置くだけでなんとなく対応できます。
ほとんどユーザーはいないと思うのでちゃんと動くかどうかはあんまり確認してません。
静的解析
静的解析ツールとして、Realmが提供しているSwiftLintを使って、Travis CI上で回しています。
SwiftLintはデフォルトの設定だと厳しすぎるので、.swiftlint.yml
という設定ファイルで挙動をカスタマイズすることができます。(参考)
どの警告を抑止すべきか、開発元のRealmでの利用例を踏襲しておけば問題ないと思ったので、主にここの設定を参考にしています。
まとめ
ざっくりと開発で培った知見をまとめてみました。参考になれば幸いです。
また、テストケースでJSONを扱う需要がある方は是非JSONMatcherをご利用ください。Pull Requestも歓迎です。