Goで外部プログラムをexecするpackageのテストをどうするか

songmuさんに教えてもらったことを参考に考えてやってみた方法を紹介します。あくまでも自分が考えた方法です。もっといい方法などあればぜひ教えてください。

まずテストの実行前に常に実行する処理を書きたいです。これは*testing.Mを使えばできます。

こう書くことでテストの実行前に setup 関数、全てのテスト終了時に clean 関数を実行できます。tmpのディレクトリ名を渡しているのはあとで解説します。それでは setup 関数と clean 関数はどうすればいいのかという話になりますが、その前にどういうテストを実行すべきかを考えてみます。

今回は外部プログラムの代わりに違うプログラムを実行したいのでそのプログラムを setup 関数で準備します。準備するプログラムは以下のようにしました。

起動したプロセスが突然死んだり、SIGTERMを送ってもなかなか死なないケースのパターンをテストしたかったので以下のようなオプションに対応したプログラムを起動するようにしました。

  • -survival-timeで起動してから死ぬまでの時間
  • -wait-timeSIGTERM/SIGINTを送られてから死ぬまでの時間
  • -exit-codeでExit時のステータスコード

このプログラムを保存するファイルの拡張子を .go にしてしまうとmainパッケージでないディレクトリの中にmainパッケージのmain関数が存在することになり、色々とおかしなことになります。なので別の拡張子にしてテスト内でファイルを読み込むようにするか、文字列で定義します。今回は文字列で定義するようにします。

先程紹介したプログラムを文字列で持ちつつ、tmpのディレクトリの中にファイルとして保存します。その後に go build することで今回のバイナリを用意することができます。バイナリの位置を programBinary に代入しているので、各テストで適切なオプションを与えながら実行することで今回のお目当てのpackageのテストができるようになります。テストが終わったら最後に clean 関数で作成したファイルを削除します。

おまけ

ソースコードを読んでいて気付いたのですが、 exec.CommandContextcontext 経由で cancel を呼ぶとプロセスに(問答無用でいきなり) SIGKILL が送られます。またまたご冗談を、って感じですが本当です。

SIGKILLを送られて大丈夫なケースってあまり思いつかないので、使えるケースはほぼないのではないでしょうか。とりあえず安易に使わないようにしましょう。