Common Test と EUnitの使い分け
Erlangには単体テスト用のEUnit (eunit) と結合テスト用のCommon Test (ct) が提供されている.
ただ, これらの使い分けは非常に曖昧で書く人によるところが大きい.
OTPはctで統一されていたり, 大抵の人はeunitで済ましてしまうことが多いだろう. Elixirにはctがないという話を見たような記憶もある.
自分なりに確立した使い分けについてここで紹介する.
ctの使いどころ
時間がかかるテストの切り分け
eunitのtimeoutはデフォルトで1テストあたり5sであるのに対し, ctはinfinityがデフォルトである.
ということもあり, スローテストはctに纏めるようにしている.
ファイルの書き込み/読み込みを行うテスト
eunitはファイルパスが実行パス依存 (カレントディレクトリ依存) になるが, ctではテストの度に生成されるpriv_dir
とSUITE
毎に用意できるdata_dir
が存在する.
その為, ファイルの入出力を行う場合はctを使うようにしている.
入力ファイルはdata_dir
から取れば良いし, 出力ファイルはpriv_dir
に書けばテストの度にディレクトリ自体が変わるので, テスト中で削除しなくても良い.
(良い訳ではないが, 大した容量じゃない時は消すコードを自分は書かない)
余談だが, rebarとrebar3では実行パス周りが変わっている為, eunitにファイル入出力があると移行した際に全部書き直す必要があったはずである.
なお, priv_dir
とdata_dir
は以下のコードで取得することができる.
-include_lib("common_test/include/ct.hrl").
PrivDir = ?config(priv_dir, Config),
DataDir = ?config(data_dir, Config).
同じテストを引数/モジュールを変えて実行したい場合
テストジェネレータを使えばeunitでも同様のことができるが, 遥かにctの方が書きやすく結果が見やすい.
例えば, bbmustacheでは連想配列, mapそしてkeyの種類としてstring, binary, atomをサポートしている.
これらを全て同じテストにかける為に使っている.
他にも, behaviourのテストに使える.
そのbehaviourに必要な関数の仕様をctにしておき, モジュールが増える度に, ct内で定義した実行対象モジュールのリストに追加してあげれば終わりだ.
eunitの使いどころ
関数単位のテスト
単体テストの名の通り, 依存関係のないテストは圧倒的にeunitの方が書きやすい.
何よりテストジェネレータの恩恵が大きい. (例)
この例はdescriptionがないので, まさかりが飛んでくるコードだが...
趣味コードだとテストジェネレータでdescriptionなしテストを量産してしまう.
細かい挙動をテストしたい場合
○○の機能を付けたgen_serverを作ったとする. 色々なところに使うとなると, かなり細かくテストを書きたくなる.
こういう時はやはりeunitを使う.
moyoのeunit.hrl拡張にあるようなマクロを使い, プロセスのダウン時の挙動などをテストする.
挙動を変える為にmeckも使う.
internal functionのテスト
eunit/ctに限らずできるが, rebar3のprofilesを使って, testの場合のみexport_allするのは, 自分はよくやる.
rebar時代は
% test用にexport
というコメントを書いていた物だが, 大分楽になった.
まとめ
実行のオーバーヘッドが小さいeunitとオーバーヘッドが大きい代わりに高機能(?)なct.
基本的にはeunitで良いと思うが, ctが重宝するケースは確実にあるので, 使ったことがない人がいれば是非使って欲しい.