著者: AKJ
Ruby に RubyGems があるように、Crystal にも Shards というパケージ管理の仕組みが存在しています。Shards は Crystal 標準のパッケージマネージャとして採用されており、Shards 関連の操作に使用する shards
コマンドも Crystal コンパイラに同梱されています。ですので、Shards を利用するための特別な準備は必要ありません。
Shards における個々のパッケージは shard (シャード)と呼ばれ、これは鉱物などの「欠片」や「破片」といった意味の単語です。Ruby や Python といった先行する言語と比べると充分ではないかもしれませんが、それでも GitHub などを利用して 2000 を超える shard が公開されています。
RubyGems と Shards の大きな違いは、パッケージのインストール先です。RubyGems は基本的にグローバルな Ruby の実行環境に対してパッケージをインストールする仕組みで、それをプロジェクト単位で個別管理するために別途 Bundler が登場しました。一方、Shards では導入パッケージをプロジェクト単位で管理することが前提となっています。
また、他言語のパッケージ管理手法と異なり、Shards には集中管理された公式のリポジトリが存在しません。ほとんどの shard は、git リポジトリとして(多くの場合 GitHub 上で)公開されています。このことは、shard を公開することへのハードルを大きく下げてくれていますが、一方で公開されている shard のクォリティ確保や、検索性の面では足かせにもなっています。
ただし、この原稿を執筆している2019年1月時点で Crystal がバージョン 1.0
に至っていないように、Shards の開発は現在も継続中です。現時点で課題となっている点も、今後のバージョンアップで改善されることを期待しましょう。
この章では、shard の使い方と作り方、および shard 作成において使用することになるテストの実行方法について簡単に説明します。
まずはインターネット上に公開されている shard を自分のプログラムから利用するための手順を見てみましょう。
前述の通り、Shards では導入パッケージの管理をプロジェクト単位でおこなうため、なにはともあれそのプロジェクトで実現したい機能とプロジェクトの名前を決めなければいけません。とはいえ、通常はやりたいことがあってプログラムを書き始めるはずですので、この辺りでつまずくこともそうないとは思います。
というわけで、実用的かどうかはさておき、ページタイトルと本文をパラメータとして受け取って最低限の HTML ソースを出力する、というだけのシンプルなコマンドラインツールを作ってみることにしましょう。
プロジェクト名は、shard として公開するのであればいくつかルール(後述)もありますが、そうでない場合にはそこまで深く考える必要はありません。今回はシンプル(simple)な HTML を返すアプリなので simple_html
というプロジェクト名にすることにします。
simple_html
コマンド利用イメージ$ simple_html "Page Title" "Page body."
<html><head><title>Page Title</title></head><body><h1>Page Title</h1><p>Page body.</p></body></html>
crystal
コマンドにはソースコードのコンパイル以外にもいくつか便利な機能が実装されており、その中の1つにプロジェクトひな形の生成機能(crystal init
コマンド)があります。
crystal init
コマンドを実行する際には、プロジェクトの種類(app
もしくは lib
)とプロジェクト名を指定します。例えば、simple_html
をアプリケーションとして実装する場合のひな形生成コマンドは crystal init app simple_html
です。このコマンドを実行するとカレントディレクトリの直下にプロジェクト名と同じ名前の simple_html ディレクトリができ、その中に各種ひな形が自動生成されます。
simple_html
アプリケーション用プロジェクトのひな形生成コマンド$ crystal init app simple_html
.editorconfig
-
文字コードや改行コード、インデントなどの設定を異なるエディタ間で共通利用するための設定ファイル。詳細を知りたければ EditorConfig で検索。
.git/
(ディレクトリ)-
このプロジェクト用の初期化済み git リポジトリ。
.gitignore
-
git 管理除外ファイルの指定用。
.travis.yml
-
Travis CI を使用してテストやビルドを自動化するための設定ファイル。使いこなせば強力なツール。ただ、最初のうちはあまり気にしなくても良い。
LICENSE
-
このプロジェクトのライセンスを明示するためのテキストファイル。自分で使うだけのアプリケーションなら気にする必要はない。標準では MIT ライセンス前提の内容になっているので、必要に応じて適宜変更する。
README.md
-
Markdown フォーマットで書かれたこのプロジェクトの README ファイル。GitHub などを通じて公開する予定ならちゃんと書いた方が良い。
shard.yml
-
プロジェクトに関連したメタデータ(名前やバージョン、作者など)やプロジェクトが利用する shard などの情報を記述するための設定ファイル。
src/
(ディレクトリ)-
プログラムのソースディレクトリ。ソースコードはすべてこの配下に置く。
src/[プロジェクト名].cr
-
プロジェクトのメインソースファイル。プログラムのエントリポイントはここ。
spec/
(ディレクトリ)-
テスト用ディレクトリ。テスト関連のファイルはここへ置く。
spec/spec_helper.cr
-
テスト関連ファイルその1。あまり触らない方。
spec/[プロジェクト名]_spec.cr
-
テスト関連ファイルその2(spec ファイル)。実際のテスト内容を記述する方。
simple_html
を実装するにあたり HTML をすべて手打ちすることもできますが、それはいささか面倒なので HTML を出力するのに便利な shard を探してみることにします。
Shards には公式のリポジトリがなく、shard の検索機能が提供されていません。そのため、標準ライブラリで提供されていない機能を使いたい場合に、その機能を提供する shard が存在するかどうかを調べるのが最初のハードルになります。
今のところ、この問題を解決する決定打は出てきていませんが、以下の2サイトは必要な shard を探す際の助けになってくれるはずです。
- Awesome Crystal
-
https://github.com/veelenga/awesome-crystal 一定の条件を満たした自薦他薦の shard をカテゴリ分類してリスト化。リストの更新も人の手で管理されているため、限定的ではあるもののある程度の基準をクリアした shard が見つかる。
- CrystalShards.xyz
-
http://crystalshards.xyz/ GitHub で公開されている shard のキーワード検索機能を提供。GitHub で公開されている shard を自動的に収集しているため玉石混こうではあるものの、Awesome Crystal より網羅的にマイナな shard も見つけられる。
試しに Awesome Crystal 内を「 html 」で検索してみると、HTML 構築専用の構文を提供してくれる html_builder
という shard が見つかりました。早速これを使ってみることにしましょう。
- crystal-lang/html_builder
プロジェクトディレクトリ内にある shard.yml
ファイルは、shard 関係の設定を記述するファイルです。
アプリケーションとして初期化された shard.yml
は以下のような内容になっています。
shard.yml
link:projects/simple_html/shard.yml[role=include]
ここへ html_builder
を指定するための設定を追加しましょう。大抵の場合 shard の README にはその shard を使用するために shard.yml
へ追加すべき内容が紹介されています。
README に従って dependencies
を追加した shard.yml
はこのようになりました。
link:projects/simple_html/shard.yml[role=include]
2018年9月時点の html_builder
はバージョン 0.2.2
が最新です。そのため、バージョン指定をせず shard をインストールした場合には最新のバージョン 0.2.2
がインストールされます。しかし、shard.yml
では、インストールする shard のバージョンを指定することもできます。例えば、shard.yml
での記述を以下のようにすると、旧バージョンの 0.2.1
をインストールできます。
shard
設定dependencies:
html_builder:
github: crystal-lang/html_builder
version: 0.2.1
ここで指定する version
には、特定のバージョンを直接記述する以外に、バージョンと >
/<
/>=
/<=
/~>
演算子を組み合わせて使用することもできます。
- 例
-
>= 0.2.0
でバージョン0.2.0以上。
これら演算子のうち、>
/<
/>=
/<=
辺りはどのバージョンが対象となるのか直感的に理解できると思いますが、~>
は以下のようなやや特殊な挙動をします。
-
~> 2.0.3
は、>= 2.0.3
かつ< 2.1
と同じ。 -
~> 2.1
は、>= 2.1
かつ< 3.0
と同じ。
html_builder
は GitHub で公開されていましたが、shard.yml
ファイルには、GitHub だけでなく、以下のような方法で shard を指定可能です。
github: user/repository
-
GitHub リポジトリを指定する。
gitlab: user/repository
-
GitLab リポジトリを指定する。
bitbucket: user/repository
-
Bitbucket リポジトリを指定する。
git: git://git.example.org/repository.git
-
任意の git リポジトリを指定する。
path: ../path/to/shard
-
ファイルシステム上のディレクトリパスで指定する。
shard.yml
で指定した shard をプロジェクト環境にインストールするコマンドは shards install
です。プロジェクトディレクトリの直下でこのコマンドを実行すると、プロジェクトディレクトリの下に lib ディレクトリが作られ、その中に必要なファイル一式が展開されます。
$ shards install
Fetching https://github.com/crystal-lang/html_builder.git
Installing html_builder (0.2.2)
これだけで shard のインストールは完了です。
また、インストール済みの shard は shards list
コマンドで確認できます。
$ shards list
Shards installed:
* html_builder (0.2.2)
ひな形生成直後のソースファイル src/simple_html.cr
には、プロジェクト名 simple_html
をキャメルケースに変換した SimpleHtml
モジュールと定義されています。この SimpleHtml
モジュールはこのプロジェクトの名前空間として使用するためのものです。ですので、特に理由がない限りプロジェクト固有の機能は SimpleHtml
モジュールか、もしくはその中で定義した型に対して実装することになります。ただし、この時点ではまだバージョンを表す VERSION
定数が定義されているだけの状態です。
link:projects/simple_html/src/simple_html_org.cr[role=include]
shard として公開されている機能をプログラム中で利用するためには require
文を使用してその旨を明示する必要があります。大抵の場合 shard の README にはその shard の使用法も記載されており requrie
文の書き方もそこで確認できます。
指定された require
文と、HTML を生成する SimpleHtml.build
メソッドを実装し、simple_html
コマンドのソースコードが完成しました。
link:projects/simple_html/src/simple_html.cr[role=include]
ビルド後に試しに実行してみると html_builder
の機能を使えていることが確認できました。
simple_html
コマンドのコパイルと実行結果$ crystal build src/simple_html.cr
$ ./simple_html "Page Title" "Page body."
<html><head><title>Page Title</title></head><body><h1>Page Title</h1><p>Page body.</p></body></html>
動作に問題がないようであれば、最適化フラグ --release
を付けた状態で再度ビルドしておきましょう。
$ crystal build --release src/simple_html.cr
以上がインターネット上で公開されている shard を利用する際のざっくりとした手順になります。
Note
|
説明をシンプルにするため、今回の simple_html コマンドにはあえて省いている機能が多くあります。例えば、現状ではコマンドラインパラメータが足りなかったり多すぎたりする状況が全く考慮されていません。コマンドラインパラメータが足りなければ実行時例外(IndexError )を吐いて異常終了してしまいますし、多すぎた場合には3つめ以降が単に無視されてしまいます。実際に使用するコマンドラインツールを作成する場合には、コマンドラインパラメータのバリデーションやエラー処理などが必要になります。
|
shard のインストールに使用した shards
コマンドには、インストール以外にも shard を管理するための各種機能が用意されています。
例えば、Crystal 本体のバージョンを上げたら shard が動かなくなった場合など、インストール済みの shard をアップデートしなければならない場面に遭遇することがあります。しかし、shard のインストール時に使用した shards install
コマンドは、すでにインストールされている shard については処理を行いません。そのため、shards install
を再度実行したとしてもインストール済みの shard はバージョンアップできません。これは、たとえ shard.yml
で新しいバージョンを明示的に指定したとしても同様です。
こうした場面では、替わりに shards update
コマンドを使用します。このコマンドはインストール済みの shard が shard.yml
で指定したバージョンと異なっていれば再取得してくれます。また、shard.yml
でバージョンが指定されていない場合は、該当の shard を最新の状態に更新します。
この他にも、インストール済みの shard を一覧表示してくれる shards list
などいくつかの機能が用意されており、使用可能なコマンドやオプションは shards help
で確認できます。
Crystal が標準で提供していない様々な機能を使えるようになる、というだけでも Shards が有用な仕組みだということはご理解いただけると思います。では、自作のライブラリを shard として公開するモチベーションとは何でしょう?
「世界中の Crystal 使いの人々に便利な機能を提供したい」という理由も当然アリです。ただ、そこまで肩肘を張らず「自分が便利だから」というだけでも、自作ライブラリを shard として公開する理由としては十分です。実際、複数のプロジェクトで共通使用する汎用部品は、仮に自分だけしか使わなさそうであっても shard として公開しておくことでデプロイ時の手間やその後のメンテナンスをかなり省力化できます。
例えば、ライブラリを開発環境とは別の本番環境で使用したい場合、プロジェクト全体を tar で固めて scp で転送、なんてことをしがちです。こうした作業はただでさえ手間がかかりますが、使用するライブラリのバージョンが異なる複数のプロジェクトを維持していこうと思うと気が遠くなります。しかし、もしそのライブラリが shard として公開されていれば、shard.yml
に2〜3行追加してから shards install
を実行するだけですみます。さらに、必要であれば個々のプロジェクトごとに過去のバージョンを指定してインストールすることもできます。
これだけでも、自作ライブラリを shard 化しておくメリットはあるのではないでしょうか。
「プログラミング言語の拡張ライブラリを公開する」というと敷居が高く感じられるかもしれませんが、Crystal の shard を公開する手順は比較的シンプルです。実際、crystal init
コマンドで作成したひな形をベースにコードを書いて、特に何も考えずにそのまま GitHub へ公開するだけでも、とりあえず shard として使えてしまったりします。
とはいえ、ある程度は shard の体裁というものもありますので、ここからは公開リポジトリで shard を公開する際に必要な最低限の手順について説明したいと思います。
Note
|
今回は作成した shard を GitHub 上で公開することを想定しています。GitHub や git の操作方法についてはすでにご存知だということを前提としていますので、これらの使い方については各種 Web サイトや書籍などをご参照ください。 |
アプリケーションを作成する場合と同様、何はともあれ shard として再利用したいライブラリに持たせる機能と、その名前を決める必要があります。機能については既にやりたいことがあるはずですが、一般に公開する shard の名前は意外と悩みどころです。
shard.yml
ファイルの記述方法を定義した「 shard.yml specification. 」には、shard の名前として以下のような命名規則が規定されています。
-
他の shard と重複しないこと
-
50 文字以下
-
英文字は小文字(
a
-z
)を使用すべき -
名前の一部に「 crystal 」を含むべきではない
-
数字(
0
-9
)を含んでも良いが、先頭に置いてはならない -
アンダースコア(
_
)やダッシュ(-
)を含んでも良いが、先頭や末尾に置いてはならない -
アンダースコアやダッシュが連続してはならない
- shard.yml specification.
-
https://github.com/crystal-lang/shards/blob/master/docs/shard.yml.adoc
基本的にこれらの条件に従う必要がありますが、shard 名の重複についてはそこまで厳密に考えなくても問題になることは少ないでしょう。単機能の shard 名は割とカブりがちですし、その名前を世界中の誰も使っていないことを確認することは現実的には不可能です。とはいえ、最低でも標準添付のライブラリとカブるような名前は避ける必要があります。また、余裕があれば CrystalShards.xyz で使いたい名前を検索して、ヒットするかどうか確認くらいはしてみても良いかもしれません。
また、既存 shard の例をいくつかみてみると、shard の命名スタイルには大きく分けて2つのパターンがあるようです。
-
html_builder
やmysql
など、機能をそのまま表した名前。単機能とは言わないまでも何か1つの対象に焦点を絞った shard に多い。 -
kemal
やtopaz
など、機能とは関係なくプロダクトイメージなどからつけられた独自の名前。フレームワークなどある程度の規模を持ち、複数の機能から構成されている shard に多い。
これらを参考にわかり易い、またはカッコいい shard 名を考えてみてください。
ここでは整数 n
に対して n
番目のフィボナッチ数を返してくれる shard を作ってみることにしましょう。単機能のシンプルな shard ですので、shard 名はそのまま fibonacci
としました。
命名規則上はダッシュ記号も使えることになっていますが、shard 名には基本的には単語の区切りにアンダースコアを使用するスネークケースで命名することをお勧めします。何故かというと、shard 名がソースコードのファイル名にも使用される場合が多いことがその理由です。Crystal 公式ドキュメントの「コーディングスタイル」では、ソースファイルのパスを名前空間に合致させ、ファイル名として型名をスネークケースに変換したものを使用するよう推奨されています。
- 例
-
HTTP::WebSocket
→ http/web_socket.cr
- Crystal公式ドキュメント「コーディングスタイル」
ちなみに、shard 名をスネークケースで命名してプロジェクト名にも使用すれば、プロジェクトひな形で生成されたソースファイル名をそのまま使用できます。
また、同コーディングスタイルでは、型名(クラス名やモジュール名)として複数の単語を先頭文字のみ大文字にしてそのまま連結したアッパーキャメルケースを使用することが推奨されています。例えば、crystal init
コマンドによるプロジェクトひな形の自動生成では、プロジェクト名をアッパーキャメルケースへ変換した名前で名前空間用のモジュールが定義されます。この点からも shard 名はスネークケースで付けておくと便利です。
shard を作成する際も、それ用のプロジェクトを立ち上げることになります。プロジェクト名は特にこだわりがなければ shard 名と同じで構いません。というよりむしろ、ひな形で提供される各種ファイルを有効利用するには、プロジェクト名と shard 名をそろえておく方が良いでしょう。
独立したアプリケーションではなく、再利用可能なライブラリを作る場合のプロジェクトひな形生成コマンドは crystal init lib プロジェクト名
です。
$ crystal init lib fibonacci
なお、ライブラリを作る場合もアプリケーションを作る場合も、crystal init
コマンドで生成されるファイルやディレクトリの構成に違いはありません。
shard を使用する際にも使用した shard.yml
ですが、本来このファイルはプロジェクトを shard として公開する際のメタデータを記述するためのものです。公開 shard にはメタデータとして最低限以下のような情報が必要です。
name
-
shard の名前。プロジェクト名がそのまま使用される。
version
-
shard のバージョン情報。ひな形生成時は
0.1.0
。 authors
-
shard 作成者のリスト(配列)。git の設定で
user.name
とuser.mail
が設定されていればそれらが使用される。 crystal
-
shard が対応する Crystal のバージョン。ひな形を生成した
crystal
コマンドのバージョンが使用される。 license
-
shard 公開時に採用するライセンスの種類。デフォルトでは
MIT
ライセンスが設定されている。
上のリストにもある通りこれらの情報はひな形生成時に自動生成されますので、ある程度そのままで利用可能な状態になっています。複数人で開発している場合に authors
を追加したり、MIT
以外のライセンスを license
に指定したりするなど、必要に応じてこれらの値を変更してください。また、作りたい shard に別の shard の機能が必要であれば、アプリケーションを作成した際と同様に dependencies
の設定を追加します。
shard.yml
link:projects/fibonacci/shard.yml[role=include]
fibonacci
では他の shard を使用することもなく、特に変更すべき内容もありませんのでこのまま進めることにします。
拡張子が .yml
となっていることからもわかる通り、shard.yml
ファイルは YAML で書かれています。
「 shard.yml specification.」には、shard.yml
自体のルールも以下のように定義されています。shard.yml
を修正した場合は、これらの条件から外れないように注意しましょう。このほか、「 shard.yml specification.」ではひな形に登場しない項目がいくつも説明されています。状況によっては便利な項目もありますので、一度詳しく目を通しておくことをお勧めします。
-
YAML ドキュメントとして構文的に正しいこと
-
文字コードは UTF-8 を使用すること
-
空白2文字でインデントすること
-
文字列、配列、ハッシュ以外の YAML の要素を使用しないこと
最近ではテスト駆動開発(test-driven development)や振る舞い駆動開発(behavior driven development)が流行りのようです。テスト実行機能がコンパイラに標準で用意されていたりすることから、Crystal としてはこうした開発手法が想定されているようです。これらの方式では、まず想定されるパブリックメソッドの挙動(テストケース)を定義し、その後にテストケースを満足させるようにコードを実装する、という流れで開発を進めます。
プロジェクトひな形にはあらかじめテスト用の構成が含まれていますので、ソースファイルにコードを書き始める前に、まずは fibonacci
のテストケースを書いてみましょう。
require "fibonacci"
Fibonacci.number(3) #=> 2_big_i
今回は shard 名(プロジェクト名)が fibonacci
ですので、名前空間用のモジュールは Fibonacci
です。提供するメソッドは n 番目のフィボナッチ数を返す Fibonacci.number(n)
のみ。引数 n
は整数(Int
型のいずれか)でさえあれば良いでしょう。ただし、結果として受け取るフィボナッチ数は n
が大きくなると急激に大きくなります。n
が200を超える頃には128ビット整数でも表現しきれないサイズになりますので、こちらは可変長整数(BigInt
)型を使用することにします。また、あまり一般的ではありませんが、フィボナッチ数は n
が負の場合も定義されています。せっかくですので、n
が負のフィボナッチ数にも対応させることにしましょう。
プロジェクトひな形にはテストケースを定義するための spec/fibonacci_spec.cr
が含まれています。ただし、ここで定義されている内容は「 fales
は true
と等しいはず」という内容で、当然ながらこの状態でテストを実行しても必ず不合格になります。
fibonacci_spec.cr
require "./spec_helper"
describe Fibonacci do
# TODO: Write tests
it "works" do
false.should eq(true)
end
end
fibonacci
の挙動に合わせて必要なテストケースを定義すると以下のようになりました。
fibonacci_spec.cr
link:projects/fibonacci/spec/fibonacci_spec.cr[role=include]
テストケースの書き方はこの章の最後で詳しく紹介しますので、ひとまずは「そういうもの」として先へ進んでください。
必要なテストケースが準備できたら、次はそのテストケースを満足させる機能を実装していきます。テストはパブリックなインタフェース(メソッド)の入出力を定義しているだけですので、メソッドがどう実装されるかは気にしません。
fibonacci
の実装方法も何パターンか考えられますが、とりあえず、一度計算した内容のキャッシュくらいは持てるように実装してみたのが以下のコードです。
fibonacci.cr
のソースコードlink:projects/fibonacci/src/fibonacci.cr[role=include]
テストの実行コマンドは crystal spec
です。プロジェクトディレクトリの直下でこのコマンドを実行すると用意したテストケースが順に評価され、実際の挙動が指定された内容通りかどうかチェックされます。
$ crystal spec
...
Finished in 133 microseconds
3 examples, 0 failures, 0 errors, 0 pending
テスト結果の先頭行には、テストケース(it
ブロック)ごとの実行結果がそれぞれ1文字で表示されます。表示されたのが .
であれば、そのテストケースには合格できたことを表しています。もしここに .
以外の文字が表示されているようであれば、対応した it
ブロック内でテストした機能の実装に問題があることになります。先頭行がすべて .
になるまで、ソースコードの修正とテストを繰り返しましょう。
今回の spec ファイルには it
ブロックが3つあり、上の例では .
が3つ並んでいます。ということは、実装したソースコードは当初想定した通りの挙動をしているようです。
このように、テストが成功した時点で shard の機能面は完成です。実際、この状態でも GitHub へ公開すれば、shard として使用できてしまったりします。しかし、曲がりなりにも一般公開する shard には、それなりの体裁といったものもありますので、もうひと手間ふた手間かけてみることにしましょう。
ひな形生成時に用意されている README.md
ファイルは、以下の内容をカバーするように構成されています。
README.md
ひな形の構成# shard 名
-
README のタイトルに当たるパート。shard の概要を記載する。
## Instration
-
shard をインストールするための手順を記載するパート。
shard.yml
へ追加するdependencies
の内容は用意されている。shard 以外の外部ライブラリやコマンドに依存している場合はここで説明しておいた方が良い。 ## Usage
-
ソースファイル内での shard の使い方を記載するパート。shard の
require
方法は用意されている。最低限のサンプルコードくらいは書いておくべき。 ## Development
-
開発者向け情報を記載するパート。shard の開発に参加したり、自身で改造したりしたい人向けの情報を記載する。積極的に開発者を募るつもりがなければ項目ごとオミットしても良いかも。
## Contributing
-
shard の開発に参加してくれる人向けの参加手順を記載するパート。積極的に開発者を募るつもりがなければ項目ごとオミットしても良いかも。
## Contributors
-
shard の作成に携わった人のリストを記載するパート。
また、このひな形は GitHub での公開を前提としています。そのため、GitHub で shard を 公開する場合には、以下の2点を修正するだけで標準的な README として利用できるようになっています。
-
何箇所かある
[your-github-name]
を自分の GitHub アカウントに置き換える -
TODO:
の部分を埋めていく
なお、ひな形の中で登場する TODO:
は以下の3箇所です。
TODO: Write a description here
-
その shard の概要。最低限、なにをする shard なのかくらいは書いておいた方が良い。
TODO: Write usage instructions here
-
その shard の使い方。ある程度サンプルコードで代用可能。
TODO: Write development instructions here
-
その shard の開発に関わろうとする人向けの情報。
fibonacci
はメソッドが1つだけしかなく機能もシンプルなので、Usage
は最低限のサンプルコードで済ませることにします。また、共同開発者を積極的に募るほどのモノでもありませんので、最低限の項目を記載した README.md
は以下のようになりました。
README.md
link:projects/fibonacci/README.md[role=include]
プロジェクトのひな形では、shard を公開する際のライセンスとして、MIT
ライセンスが選択されています。
shard.yml
の license
には MIT
が指定されており、プロジェクトディレクトリ内の LICENSE
ファイルも MIT
ライセンスの条項が記載されています。MIT
ライセンスとは、ざっくりいうと「著作権表記とライセンス条項さえ含まれていれば有償無償問わず自由に使って良い、ただし無保証」というものです。ソフトウェアの利用者としてはかなり自由に利用でき、開発者としては免責が明言されているため、比較的使いやすいライセンスの1つです。大した量ではありませんので、詳細については一度 LICENSE
ファイルの内容を読んでみてください。
もし MIT
以外のライセンスを採用したければ、LICENSE
ファイルの内容を使用したいライセンス条件に書き換えて shard.yml
の license
を変更することになります。このとき、shard.yml
の license
には OSI(Open Source Initiative)で定義されたライセンス名か、ライセンスの参照先 URL を指定可能です。
- OSI ライセンス名
今回は特にライセンスへのこだわりもありませんので、MIT
ライセンスのままで行こうと思います。
ここまでの準備が完了したら、プロジェクトを GitHub へ公開しましょう。この辺りの手順は GitHub のドキュメントなどを参照してください。
プロジェクトが GitHub へ公開されると、別のプロジェクトが shard.yml
に以下の記述を追加することで fibonacci
をインストール可能になります。
dependencies:
fibonacci:
github: github_name/fibonacci
ここまでで shard を作って公開するまでの手順は完了です。
ただし、この状態では shards
コマンドが shard のバージョンを認識できません。fibonacci
を使おうとするプロジェクトで shard.yml
に特定のバージョンを指定していても、常に最新状態(リポジトリの HEAD)の shard がインストールされてしまいます。
shard を作る手順の最後に、shard のバージョン管理方法をご紹介しましょう。
毎度おなじみ「 shard.yml specification.」には、shard のバージョンとして以下のようなルールが示されています。
-
セマンティックバージョニングに従うことが望ましい
-
数字が含まれていること
-
.
や-
を使用しても良いが、連続しないこと- 例
-
0.0.1
、1.2.3
、2.0.0-rc1
など
強制はされないものの、セマンティックバージョニングのような合理的なバージョン付けを強く推奨。
後方互換が損なわれる大きな変更でも、内部の子細なバグフィックスでもメジャーバージョンが上がるようでは、使う側としてバージョンの変化からバージョンアップのインパクトを測りかねてしまいます。
セマンティックバージョニングではメジャー/マイナ/パッチという3つの数字でバージョンを構成し、パッチよりマイナ、マイナよりメジャーバージョンが上がることのインパクトが大きくなるよう定義されています。shard.yml
内でバージョンを指定する際の ~>
演算子は、セマンティックバージョニングのような、先頭に近い数字の変更がインパクトが大きい際に有効に働きます。
- セマンティックバージョニング
fibonacci
プロジェクトでは、プロジェクトファイルの2箇所に shard のバージョンが登場します。1つ目は shard.yml
ファイル2行目の version
、もう1つがソースファイルで定義されている Fibonacci::VERSION
定数です。この2つの値は、ひな形生成時にはどちらも 0.1.0
となっており、これがプロジェクト(shard)のバージョンになります。
前述の通り、shard.yml
とソースファイルにバージョンが書かれていても、shards
コマンドはそのバージョンを認識できません。
このとき使用されるのが第三のバージョン情報が、git リポジトリのバージョンタグです。
shard.yml
の dependencies
内で、ある shard のバージョンとして 1.0.0
が指定されていたとしましょう。このとき、shards
コマンドは、その shard のリポジトリから v1.0.0
とタグ付けされたコミットの状態をインストールしようとします。
つまり、先ほどプッシュした fibonacci
の GitHub リポジトリに v0.1.0
というタグを付けることで、現状の fibonacci
がバージョン 0.1.0
だと明示できます。なお、GitHub ではリリース管理機能を使って新しいリリースを追加する際に、Web UI からバージョンタグを設定できて便利です。
もし fibonacci
の機能に修正を加えてバージョンを 0.2.0
へ上げる場合には以下のような手順で行うことになるでしょう。こうしておくと、複数のプロジェクトからそれぞれ異なるバージョンの fibonacci
を利用可能になります。
-
fibonacci
の機能を修正 -
ソースファイル上の
Fibonacci::VERSION
定数を0.2.0
に変更 -
shard.yml
2行目のversion
を0.2.0
に変更 -
変更をコミットし、GitHub 上の master ブランチへマージ
-
GitHub 上で変更がマージされた master ブランチにバージョンタグ
v0.2.0
を追加
というわけで、以上が shard を作って公開し、さらに他のプロジェクトから使用してもらうために必要な作業の流れになります。
是非みなさんも自作のライブラリを shard として公開してみてください。世界中には自分と同じことで困っていて、自分だけしか使わないだろうと思っていた機能を便利に使ってくれる人が意外といたりしますよ。
この章の最後に、shard を作る際にも使用したテストについて簡単にご紹介しましょう。
ここでいうテストはソフトウェアに対するユニットテストの一種で、プログラムが想定した通りの挙動を取るかどうかを調べる機能試験に相当します。これは、あらかじめ「このメソッドにこういった引数を与えると、こんな結果になるはず」というプログラムの挙動(テストケース)を列挙しておき、実際にそうなるかどうかを個々に評価するような仕組みです。
Crystal には、RSpec を参考にしたテスト機能を提供するモジュール Spec
がコンパイラ自身の標準添付ライブラリとして用意されています。また、標準のプロジェクトひな形にテスト用のファイル一式が含まれているなど、言語自体がテストを強く意識した作りになっています。
細かい説明は後にして、まずはテストの使い方をざっくり見てみましょう。
まず、テストにはテストケースを定義した spec ファイルが必要です。spec ファイルは、"spec"
とテスト対象となるコードを require
した Crystal のソースファイルで、テストケース自体も Crystal のコードとして定義します。
さて、ここでは簡単な例として "がおー"
と吠える Bear
クラスを実装する場合を考えてみましょう。Bear
クラスが持つインスタンスメソッドは吠え声を文字列で返す #bark
のみ、ソースファイル名は bear.cr
です。spec ファイルの名前はテスト対象とするファイルのベースネームに続けて _spec.cr
を加えたものが使用されることが一般的です。今回もその例に倣い、熊が正しく "がおー"
と吠えるかどうかテストする bear_spec.cr
を、ソースファイルと同じディレクトリに作ってみました。
bear_spec.cr
link:examples/bear_spec.cr[role=include]
最初の2行は Spec
モジュールとソースコードの require
文で、4行目以降がテストケースの定義部分になります。
まず登場するのが describe Bear
で宣言されたブロックです。これは、その内部で行われるテストが Bear
型を対象としていることを明示しています。describe Bear
ブロックの中には、もう1つ describe "#bart"
ブロックが置かれています。こちらのブロックはその内部で Bear
型の中でも #bart
メソッドに関するテストを行うことを宣言しています。
続いて登場するのがテストケースに相当する it
ブロックです。it
ブロックでは、describe
で宣言された対象となるメソッドの、ある1つの振る舞いに関するテストを行います。また、it
ブロックには引数としてその内部で行うテスト内容の説明文を与えることができます。この例では、it
(Bear#bart
メソッド)が、正しく "がおー"
という文字列を返すかどうかについてテストすることが説明されています。
そして、it
ブロックの内で実行される Bear.new.bark.should eq "がおー"
が実際に評価されるテスト内容の記述です。Bear.new.bark
が "がおー"
と等しく(eq
)あるべき(should
)といったように、実際に評価している内容が(英語として)比較的自然に読める構文になっています。
さて、spec ファイルが用意できたので次に Bear
クラスそのものを bear.cr
へ実装していくのですが、ついうっかり熊に "わん"
と吠えさせてしまいました。
bear.cr
link:examples/bear0.cr[role=include]
この状態でテストを実行してみるとどうなるでしょうか。なお、テストの実行コマンドは crystal spec
です。特定の spec ファイルを対象としたい場合には、その spec ファイル名をパラメータとして与えてください。
link:examples/spec_error.txt[role=include]
テスト結果の先頭行には、テストケース(it
ブロック)ごとの実行結果サマリが、それぞれ1文字で出力されます。今回はテストケースが1つしかありませんので、表示されているのは F
が1文字だけです。テスト結果 F
は、実際の挙動がテストケースで指定された条件とは異なる結果を示したため、テストが不合格になった場合に表示されるものです。
このように何らかの理由でテストが不合格になると、Failures:
に続いて具体的な問題点が出力されます。ここでは、期待された値(Expected:
)が "がおー"
なのに対して、実際の結果(got:
)が "わん"
になっていたことが確認できます。このように、テストを行うと、実装されたコードが想定外の挙動を示した際に、どの部分がどのように異なっていたのかを具体的に知ることができます。
テスト結果から問題の箇所は明らかですので、bear.cr
の実装を修正しました。
bear.cr
link:examples/bear.cr[role=include]
もう一度テストを実行してみると、今度は F
ではなく .
が表示されましたので、実装したコードが当初想定した通りの機能を実現できている様子が確認できました。
link:examples/spec_success.txt[role=include]
このように、まず実装しようとする機能の挙動をテストケースとして定義し、テストケースを満足するように実装を進める開発手法を、テスト駆動開発や振る舞い駆動開発などと呼びます。この手法のメリットとしては、実装コードの品質が担保されるという点はもちろんありますが、テストケース自体がある意味で機能仕様やサンプルコードとして利用できるという点も見逃せません。チームで開発している場合や他の人が書いたコードを引き継ぐ場合などでも、適切なテストケースが用意されていれば最低限の情報はそこから入手可能です。
では次に、spec ファイルで使用する各構文についてみてみましょう。
グルーピング用ブロックは、1つ以上のテストケースを何らかの基準でまとめるために使用されます。グルーピング用ブロックの内側には次に紹介するテストケース用のブロックを置くだけでなく、別のグルーピング用ブロックをネストさせることも可能です。
先の例で登場した describe
もグルーピング用ブロックの1種です。describe
ブロックはテスト対象を元にしたグルーピングを行うもので、テスト対象として型(クラス、モジュールなど)やメソッド名(文字列で指定)を引数として与えることができます。この引数はテスト不合格時の出力メッセージでも利用されるため、テスト対象となるメソッド名に準じた構成をとることが強く推奨されています。例えば、外側の describe
では型を指定し、内側の describe
にメソッド名を渡すような形です。またこのときのメソッド名に、クラスメソッドであれば .
を、インスタンスメソッドであれば #
を先頭に付けておくと、Class #method
のように標準的なメソッド表記に近い形を再現できます。
Crystal の spec ファイルでは、グルーピング用のブロックとして describe
以外にもう1つ context
が使用可能です。describe
ブロックがテスト対象をもとにしたグルーピングだとすると、context
ブロックはその時の状況(条件)によるグルーピングです。例えば、配列が要素を含んでいるか否かで挙動が異なる場合に、それぞれの状況を明示できます。
describe
と context
link:examples/spec_example_00.cr[role=include]
こちらの例としては it
がすでに登場しています。it
はあるメソッドがもつ特定の挙動1つについてのテストケースを定義するためのブロックです。ですので、複数の挙動に対するテスト内容を1つの it
ブロック内に記述すべきではありません。例えば、オブジェクトの状態にや引数の値によって異なる挙動を示すような場合は、同じメソッドであってもそれぞれに独立した it
ブロックを設けてテストケースを定義すべきです。
テストケースを書くためのブロックには it
ともう1つ、pending
があります。pending
ブロックは文字通り、一時的にそのテストケースの実行を保留する場合に使用します。例えば、機能仕様としてのテストケースは用意されたものの対象となるメソッドがまだ実装されていないような場合に、it
の代わりに pending
ブロックを使用すると良いでしょう。ただし、あくまで 一時的に保留しておく だけですので、最終的には pending
ブロックを it
ブロックに置き換えてテストに合格できるようにしなければいけません。
it
と pending
link:examples/bear1_spec.cr[role=include]
require "spec"
が実行されると、全てのオブジェクトに対して #should
と #should_not
という2つのインスタンスメソッドが追加されます。#should
は、レシーバの状態に対する評価条件(eq "がおー"
など)を引数に取り、実際の値がその条件を満たしていないとテストが不合格になります。一方、#should_not
は逆に評価条件を満たしてしまうとテストが不合格になります。
オブジェクトの状態に対する評価条件には以下のような種類があります。一部の評価条件は特定のインスタンスメソッドを持たないオブジェクトに対しては使用できません。例えば、should be
を使用するには #same?
が実装されている必要があります。
actual.should eq expected
-
actual
がexpected
と等しいかどうか。 条件:actual == expected
actual.should be expected
-
actual
がexpected
と同値とみなせるかどうか。 条件:actual.same?(expected)
actual.should be_a expected
-
actual
がexpected
型の値かどうか。 条件:actual.is_a?(expected)
actual.should be_nil
-
actual
がnil
かどうか。 条件:actual.nil?
actual.should be_true
-
acrual
がtrue
かどうか。 条件:actual == true
actual.should be_false
-
acrual
がfalse
かどうか。 条件:actual == false
actual.should be_truthy
-
acrual
がif
文で真とみなされるかどうか。 条件:acrual
がfalse
、nil
、Pointer.null
のいずれでもない actual.should be_falsey
-
acrual
がif
文で偽とみなされるかどうか。 条件:acrual
がfalse
、nil
、Pointer.null
のいずれか actual.should be < expected
-
actual
がexpected
より小さいかどうか。 条件:actual < expected
actual.should be ⇐ expected
-
actual
がexpected
以下かどうか。 条件:actual ⇐ expected
actual.should be > expected
-
actual
がexpected
より大きいかどうか。 条件:actual > expected
actual.should be >= expected
-
actual
がexpected
以上かどうか。 条件:actual >= expected
actual.should be_close(expected, delta)
-
actual
とexpexted
の差がdelta
以下かどうか。 条件:(actual - expected).abs ⇐ delta
actual.should contain expected
-
actual
がexpexted
を含むかどうか。 条件:actual.includes?(expected)
actual.should match expected
-
actual
がexpexted
にマッチするかどうか。 条件:actual =~ expected
テストケース内で expext_raises
ブロックに引数として例外の型を指定してすると、ブロック内で指定した型の例外が発生することを確認できます。
link:examples/spec_example_01.cr[role=include]
このとき、引数に文字列もしくは正規表現オブジェクトを追加することで、エラーメッセージに対する条件を指定することも可能です。
expect_raises(SomeError, "message") { … }
-
エラーメッセージに
"message"
が含まれるSomeError
型の例外が発生しなければ不合格。 expect_raises(SomeError, /pattern/) { … }
-
エラーメッセージが
/pattern/
にマッチするSomeError
型の例外が発生しなければ不合格。
なお、発生した例外の状態を他の評価条件でテストしたい場合、expect_raises
の返り値として例外インスタンスを取得できます。
テストの実行結果は大きく分けると以下のパートから構成されます。
-
テストケースの実行結果サマリ
-
不合格となったテストケースの詳細情報
-
テストの実行に要した時間
-
テスト結果の統計情報
-
不合格となったテストケースの一覧
このうち、2. および 5. については、全てのテストケースを問題なく通過できた場合には表示されません。
ソース修正前の bear_spec.cr
の実行結果を例に、それぞれの内容を詳しくみてみましょう。
link:examples/spec_error.txt[role=include]
テストケースの実行結果には合格(.
)、不合格(F
)、エラー(E
)、ペンディング(*
)の4種類があります。テストの実行結果では、先頭行にテストケースの数だけ実行結果に対応した文字が並んで表示されます。もしテストを実行したのがカラー表示対応のターミナルであれば .
は緑、F
と E
が赤、*
が黄色で表示されているかもしれません。
ここで表示される実行結果が全て .
になっていれば、テスト全体に合格できたことになります。
- 合格(
.
) -
テストケース内の評価条件を全てパスし、想定外の例外やエラーも発生しなかった場合。
- 不合格(
F
) -
テストケース内の評価条件を満たさない項目が存在したり、想定される例外が発生しなかった場合。
- エラー(
E
) -
テストケース内で
expect_raises
で補足されない想定外の例外が発生した場合 - ペンディング(
*
) -
テストケースが
pending
ブロックとして定義されており、テストの実行を保留している場合。
Failures:
に続いて、不合格となったテストケースの具体的な問題点が出力されます。
-
不合格テストケースの通し番号とテストケースの説明(5行目)
-
不合格の原因となったチェック項目(6行目)
-
想定された状態(
Expected:
)と実際の状態(got:
)(8〜9行目) -
該当チェック項目があるファイル名と行番号(11行目)
特に 3. で表示される想定値と実際の値との比較は、問題解決の大きな助けになってくれることでしょう。
ここには examples
、failures
、errors
、pending
と4種類の数値が出力されます。
examples
が spec ファイルに定義されたテストケースの数、後ろ3つは、実行結果が不合格/エラー/ペンディングとなったテストケースの数です。
ここで examples
以外の値がゼロになっていれば、テスト全体に合格したことになります。
テストを実行する crystal spec
コマンドには、spec ファイル名以外のパラメータを指定できます。適切なパラメータを与えることで、ディレクトリ単位や spec ファイル単位、さらには特定のテストケース単位でのテストを実行可能です。
crystal spec
-
パラメータを省略した場合、
spec/**/*_spec.cr
にマッチする全ての spec ファイルを対象とする。 crystal spec dir/
-
ディレクトリを指定した場合。
dir/**/*_spec.cr
マッチする全ての spec ファイルを対象とする。 crystal spec spec_file.cr
-
spec ファイルを指定した場合。
spec_file.cr
単体を対象とする。 crystal spec file.cr:10
-
spec ファイルと行数を指定した場合。
spec_file.cr
ファイルの10行目から始まるブロックだけを対象とする。
大規模なソースコードのテストを実施する際には、これらのパラメータを有効活用することでテスト効率を向上できるかもしれません。
なお、crystal init
コマンドで生成されるプロジェクトひな形ではテスト関連のファイルは spec
ディレクトリに収められています。そのため、spec ファイルを指定しなくても crystal spec
とだけ打てばテストが実行可能になっています。
プロジェクトひな形の spec
ディレクトリには、spec ファイル以外に spec ヘルパファイル spec_helper.cr
が用意されています。デフォルトでは、"spec"
とプロジェクトのソースファイルを require
するだけの内容になっており、spec ファイルから読み込まれています。
spec ヘルパは、spec ファイルが1つだけの場合それほどありがたみを感じないかもしれません。しかし、プロジェクトが大規模になり、いくつもの spec ファイルを駆使するようになると spec ヘルパファイルが効果を発揮するようになります。例えば、テスト用のオブジェクト生成やテスト用の特別な環境設定など、複数の spec ファイルで共通的に使用するメソッドを spec ヘルパファイルに書いておけばコードの重複を防ぐことが可能です。
テストはコードのクォリティを確保する上で重要な工程ですが、慣れないうちは面倒臭く感じることも多いと思います。spec ヘルパのような仕組みをうまく活用して、効率よくテストする方法を模索してみてください。
-
shard を利用するプロジェクトを作成する
-
shard.yml
に使用したい shard の記述を追加する -
shard をプロジェクトディレクトリにインストールする
-
ソースコードの先頭で shard を
require
する -
ソースコード内で shard が提供する機能を使用する
-
shard 用のプロジェクトを作成する
-
shard.yml
にメタデータを記述する -
テストのテストケースを書く
-
テストケースを満足させるようにソースファイルに機能を実装する
-
テストを実行、問題がなくなるまでソースの修正とテストを繰り返す
-
README を書く
-
ライセンスを選ぶ
-
GiuHub などのリポジトリへ公開する
-
git リポジトリにバージョンタグを付ける
describe
ブロック-
テスト対象でテストケースをグルーピングする。
context
ブロック-
条件や状況でテストケースをグルーピングする。
it
ブロック-
ある振る舞いに対するテストケースを記述する。
pending
ブロック-
一時的にテストを保留するテストケースに使用する。
Object#should
メソッド-
レシーバがある条件を満たさなければテストに不合格とする。
Object#should_not
メソッド-
レシーバがある条件を満たした場合にテストに不合格とする。
expect_raises
ブロック-
ブロック内で指定された例外が発生しなかったらテストに不合格とする。