状況
通常、ExUnit のような単体テストは、入力、関数呼び出し、および必要な出力を備えた自己完結型である必要があります。これにより、テストを任意のシステムで実行でき、環境に関係なく常に正しくテストできます。
一方、アプリケーションが ElixirSystem.cmd/3
や Erlangなどで syscall を実行し:os.cmd/1
、その結果を処理する場合、異なる/更新されたバイナリ、変更された環境、異なるオペレーティング システムなどの理由により、テストで異なる結果が得られる可能性があります。
もちろん、これらのケースでテストが失敗するのは良いことです。これにより、実際の状況のカバレッジが向上します。ただし、開発するときは、まず関数が正しいことを実行できるようにしてから、正しいことを実行する必要があります。外の世界が変化した場合、常に予測どおりにテストを実行することは困難であるか、不可能ですらあります。
さらに、めったにまたはほとんど起こらない条件をテストしたい場合がありますが、実際に発生することは非常にまれであるため、システム コールはその情報を提供しません。何らかの方法で syscall の出力をモックし、それをプログラムの内部ロジックから分離する必要があります。
例
シンプルにするために (より複雑な状況でも同じ原則が適用されます)、システムの起動時間を読み取り、クリーンアップされた結果に応じて応答することを検討してください。
def what_time do
time =
:os.cmd('who -b | cut -d\' \' -f14') # Returns something like '13:50\n'
|> to_string
|> String.trim("\n")
|> String.split(":")
|> List.to_tuple
case time do
{"12", "00"} -> {:ok, "It's High Noon!"}
_ -> {:error, "meh"}
end
end
この機能は、特定の時間にシステムを再起動した場合にのみ正しくテストできますが、これはもちろん不合理です。ただし、出力の形式は大まかにわかっているため、次のようなテスト値のリストを作成し['16:04', '23:59', '12:00', "12:00", 2, "xyz", '1.0"]
、syscall なしで解析部分をテストしてから、通常どおり期待される結果と比較することができます。
素朴なアプローチ
しかし、これはどのように行われますか?syscall は関数の最初のものなので、それを別の関数に取り出すと、syscall をテストできますが、syscall 自体が問題であるため、あまり役に立ちません。
def what_time do
time = get_time
|> to_string
[...]
end
def get_time do
:os.cmd('who -b | cut -d\' \' -f14') # Returns something like '13:50\n'
end
少し良い...
文字列/文字リストを解析するだけの別のヘルパー メソッドを追加すると、syscall 自体を非公開にしながら、目的を達成できます。
def what_time do
what_time_helper(get_time())
end
def what_time_helper(time) do
time =
time
|> to_string
[...]
end
end
defp get_time do
:os.cmd('who -b | cut -d\' \' -f14') # Returns something like '13:50\n'
end
これで、ExUnit ケースでヘルパー テスト関数を呼び出すことができ、通常のプログラムで通常の関数を呼び出すことができます。
…でもダメ?
この最後のアイデアは実際には機能しますが、あまり洗練されていないと思います。次の欠点が見られます。
- 各関数は、プライベート システムコール、パブリック ヘルパー、およびパブリック 通常メソッドに分割する必要があり、関数の量が 3 倍になります。結果として得られるコードは、不必要なパーティショニングのために長くなり、読みにくくなります。
- ヘルパー メソッドは、テストするために公開する必要がありますが、公開しないでください。その結果、追加のドキュメントを作成する必要があり、API リファレンスが長くなり、安全な操作を保証するためにメソッドがより多くのチェックを行う必要があります (以前は、syscall 自体によって生成された値のみが発生する可能性がありました)。
- 小さなメイン関数は、定義済みのセットを使用して他の関数を呼び出すだけですが、テスト カバレッジに含めることはできません。この不満は少々的外れですが、コード行または関数数でテスト カバレッジを表示する自動テスト ツールを使用すると、問題が生じると思います。
質問
だから、私の質問は次のようになります:
- ExUnitなどのテストでそのようなケースを正しく処理するにはどうすればよいですか?
- システムコールを内部ロジックから分離し、ボイラープレート関数の量を減らす方法は?
- これが関数型プログラミングで通常行われるツールや一般的な方法はありますか?