EUnitでapplicationを起動したテストを書く
EUnitでapplicationを起動したテストを書こうとした場合, いくつか嵌まりどころがあったので纏めておく.
もちろん, mockライブラリの「meck」を使用するという方法もあるが, ここではそれについては触れない.
on_loadで起動する方法 (悪い例)
-module(hoge_tests).
-include_lib("eunit/include/eunit.hrl").
-on_load(init/0).
hoge_test_() ->
%% 省略
init() ->
_ = application:ensure_all_started(app).
ok.
元々はNIF用に用意されているのだと思うのだが, moduleがloadされた際に実行する関数を指定することができる.
これを使って, applicationの起動が必要なテストはここで起動処理を書いたりしていた.
ただ, 本来の用途でないだけあって困ったことがいくつかある.
テスト終了後の挙動
on_loadで起動したアプリケーションは, test終了後exit(Pid, kill)で終了される.これによる副作用がある.
applicationにはloadに関するステータスとして
- loaded
- loading
そして, startに関するステータスとして
- started
- running
が存在するが(ちなみに, これらはapplication:info/0を呼ぶことで取得することができる), exitされるとrunningからは消えるがstartedに残るという現象が発生する.
そして, application:ensure_all_started/1はstartedに存在するアプリケーションについては実行しない.
結果, 他のテストで同じアプリケーションを使用した際にstartできなくなる.
(ちなみに, application:stop/1を呼ぶとstartできるようになる)
on_loadの処理を失敗した場合
そして, もう1つはon_loadを失敗した場合だ.
ついつい調子に乗って
init() ->
?assertMatch({ok, _}, application:ensure_all_started(hoge)),
ok.
とか書くと痛い目に合う.
on_loadはokが返らなかった場合, そのモジュールのテストが実行されないからだ.
(多分ロード自体がされないと思うが調べていない)
testが実行されなければ失敗もしないので (つまりtest件数が減るだけ), 何が何でもokを返さないといけない.
setupを用いてネストする方法 (正しい例)
このように困ったことがある訳だが, 単にこれは正しい書き方ではないからこうなっているだけであって, 正しい書き方をすれば良いだけの話だ.
%% 省略
hoge_test_() ->
{setup,
fun() -> {ok, Started} = application:ensure_all_started(hoge), Started end,
fun(Started) -> [application:stop(App) || App <- Started] end,
[
hoge_test_main(),
hoge_test_main2()
]}.
hoge_test_main() ->
[
{"hoge", ?_assertEqual(...)},
{"fugo", ?_assertEqual(...)}
].
hoge_test_main2() ->
[
%% 省略
].
多少面倒にはなるが, なんら問題はない.
数が多くなってくると多少面倒ではあるが...
まとめ
つまるところ今までon_loadを使っていた訳だが, applicationが正常に終了されない為によるテスト間依存が発生していた.
Common Test (CT) を書くというのも1つの手ではあるが, 手軽さのコストが違うので, これで書けるところは書いていきたい.
とはいえ, Erlangを書き始めて半年なので正直なところ, これが本当に正しいかは分からない.
もし, こちらの方が良いというものがあればご教授願いたい.