読者です 読者をやめる 読者になる 読者になる

5.1さらうどん

@giginetの技術ブログ。ゲーム開発、iOS開発、その他いろいろ

SwiftでJSONのテストを良い感じにするJSONMatcher作った

この度、JSONMatcherというSwift向けのテストライブラリを開発しました。

github.com

これは何?

SwiftJSONのオブジェクトや文字列を検査するマッチャーです。

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に大変影響を受けてます。

github.com

Swiftでライブラリを作る

今回のモチベーションの一つとして、Swiftのライブラリを最新のエコシステムを用いてしっかり作ってみたいというものがありました。

Swiftのライブラリ作成の知見は枯れておらず、有名OSSを眺めてみてもあまり統一されていなかったので、今回作成した知見をまとめてみました。

f:id:gigi-net:20160521182333p:plain

しっかりテストして、こういう風にバッヂを貼りまくると、ちゃんとしたライブラリっぽくなります。

ライブラリ作成にあたって、3月に行われたtry! Swiftの「Creating a Swift Library」というセッションが大変参考になってオススメです。

以下の書き起こしやリポジトリを参考にしてみてください。

try! Swift ライブラリの開発 #tryswiftconf Day2-8 - niwatakoのはてなブログ

github.com

ディレクトリ構造

まずSwiftのライブラリを作るにあたって、ディレクトリ構造からして悩むのですが、結論から言えば、最小構成で以下のような感じにすれば良さそうです。

実装はSources/LibraryName以下にソースコードTests/LibraryName以下にテストコードを配置するのがベストのようです。

├── Cartfile
├── Carthage
│   └── Checkouts
├── JSONMatcher.xcodeproj
├── Sources
│   └── JSONMatcher
│       ├── *.swift
│       └── Info.plist
└── Tests
       └── JSONMatcher
           ├── *.swift
           └── Info.plist

テスト

f:id:gigi-net:20160521185739p:plain

テストは必ず記述しましょう。XCTestをそのまま使っても良いですが、今回は前述のNimbleを用いて記述しています。

今回は使用していませんが、さらにRspecっぽく書きたい方はQuickを併せて使っても良いでしょう。

テストの実行には簡単なShellスクリプトを用意して、xcodebuild testを実行してます。

マルチプラットフォーム

Swift向けのライブラリは、iOSOSX、tvOS、watchOS、Linuxの5つの環境向けに作ることができます。

今回はiOSOSX、tvOS向けに対応させてみました。

watchOSではそもそもXCTestが使えないため、今回は対応できず、Linux対応は面倒そうなのでやっていません。

マルチプラットフォーム対応は簡単で、以下のようにプラットフォーム別にターゲットとスキーマを作成しましょう。

f:id:gigi-net:20160521191145p:plain

最初はiOS向けのみに実装し、テストがある程度揃ってきたら、テストを実行しつつ、マルチプラットフォーム化をするのが楽でした。

CI

CI as a Serviceとして、Travis CIやCircle CIが使えます。好きなものを使いましょう。

CIでは以下のような事を行っています。

  • 各プラットフォーム向けのテストの実行
  • カバレッジのレポート
  • swiftlintの実行
  • CocoaPodsの検証

また、先日、Travis CIでのPublicリポジトリ向けのキャッシュの提供が開始されました。Swiftのライブラリはビルド時間が長くなりがちなので、依存ライブラリのビルドに時間がかかる場合は利用すると良いでしょう。

カバレッジ

ついでにテストカバレッジを取得しましょう。Xcode7からはテストスキーマのチェックを入れるだけで、簡単にカバレッジを計測できるようになりました。

f:id:gigi-net:20160521191244p:plain

計測したカバレッジは、CIからcodecov.ioというサービスに送っています。codecovはSwift対応が楽で大変便利です。

f:id:gigi-net:20160521191005p:plain

Coverallsでの表示も可能ですが、公式でサポートされていないようでした。

パッケージ管理ツール

現在、Swiftのパッケージ管理ツールとして、CarthageCocoaPodsSwift 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上で回しています。

github.com

SwiftLintはデフォルトの設定だと厳しすぎるので、.swiftlint.ymlという設定ファイルで挙動をカスタマイズすることができます。(参考

どの警告を抑止すべきか、開発元のRealmでの利用例を踏襲しておけば問題ないと思ったので、主にここの設定を参考にしています。

まとめ

ざっくりと開発で培った知見をまとめてみました。参考になれば幸いです。

また、テストケースでJSONを扱う需要がある方は是非JSONMatcherをご利用ください。Pull Requestも歓迎です。

github.com

pecoでrbenvとかpyenvを簡単に切り替える奴作った

peco-anyenv

https://raw.githubusercontent.com/giginet/peco-anyenv/master/images/peco-anyenv.gif

pyenvrbenvのようないわゆるanyenv系のツールが大変便利で常用しているのですが、インタプリタの数が増えてくると切り替えるのが面倒になってくる。

$ rbenv global 2.3.0
rbenv: version `2.3.0' not installed

このようにいちいちバージョンを打たないといけないし、インストールされてると思ったらされてないみたいな事があった。

そこでpecoで現在のインタプリタを簡単に切り替えれる奴を作ってみた。

github.com

使い方は冒頭のアニメーションの通りで、特に説明することはなさそう。

prbenvなどのように、お使いのenvにpプレフィックスをつけて実行するだけ。デフォルトでは現在のディレクトリのみインタプリタが切り替わる(local)が、--globalオプションをつけることで全体に適応することもできる。

特にPythonの場合はvirtualenvを使うことで、かなり頻繁にインタプリタを切り替えることになると思うのでさらに便利だと思う。

現在はPython, Ruby, Perl, Nodeに対応しているけれど、他の言語でも使いたいという方は簡単に増やせるのでPRをお待ちしています。

作ってみて。

元々、半年前にQiitaに記事を書いて個人的に使っていたのですが、便利だったのと、rbenvとかでも使いたくなったので、この機会に汎化してみた。

qiita.com

あまりにもシェルスクリプト力が低すぎて、そらで書くことができないし、シェルスクリプトをもっと学ばねばならないと思った。

Djangoでgitのリビジョンを表示するプラグインを作った

Django python

django-debug-toobar-vcs-info

Djangoにはdjango-debug-toolbarというプラグインがあります。

github.com

このプラグインを導入すると、プロファイリングを行ったり、利用されているライブラリのバージョンを表示したりといった、デバッグに必要な情報を簡単にサービス上に表示することができます。

f:id:gigi-net:20151227182311p:plain

現在、Djangoで運用しているサービスがあるのですが、本番で現在のGitリビジョンを確認したいという需要があったので、このdjango-debug-toolbarの拡張として、以下のような物を作りました。

github.com

django-debug-toolbar-vcs-infodjango-debug-toolbarにGitなどのバージョン管理システムの現在の状態を表示するパネルを追加するプラグインです。

このプラグインを導入すると、以下のように現在のHEADのハッシュやコミットログを確認することができます。これを本番環境に導入しておくと結構便利です。

f:id:gigi-net:20151227182421p:plain

非常に需要の少ないモノですが、簡単に導入できますし、入れておいて損はないので、Djangoでサービスを運用している方は是非使ってみてください。

vcs-infoという名前ですが、現状、gitにしか対応していないので、Mercurial対応などのPRお待ちしています。

作ってみて

そもそも、作ってみた動機として「PyPIにしっかりテストしたパッケージをホストしたい」という気概がありました。

PyPIにパッケージを登録するのは、3~4年前に一度挑戦したことがあったのですが、全くテストがされていなくて、使いやすい物になっていなかったので、今回はガッチリとテストをしてみました。

f:id:gigi-net:20151227183355p:plain

toxを利用し、Django1.7から1.9に対応させたり、Python2, 3に両対応させたり、カバレッジを計測したり、しっかりモックテストを書いたりと言った知見が溜まりましたし、そういう意味で小さなプラグインを作ってみるのは良いと思います。

今回のような複数Python, Djangoのバージョンに対応させたテスト手法について、Python Advent Calendar 2015の記事として投稿していますので、ご興味のある方はこちらも併せてご覧ください。

複数バージョンのPython向けにCI環境を構築してテストする - Qiita

Thanks to WPZOOM about nice icon sets.