アルファベット A からアルファベット B に可能な限り厳密に音訳しようとするアプリケーションを作成するとします。
言語 B は非常に複雑なため、これが常に成功するとは限りません。しかし、おおよその音訳は得られます。
この場合、20 ~ 30% が失敗すると予想されることを考慮して、単体テストをどのように作成しますか?
アルファベット A からアルファベット B に可能な限り厳密に音訳しようとするアプリケーションを作成するとします。
言語 B は非常に複雑なため、これが常に成功するとは限りません。しかし、おおよその音訳は得られます。
この場合、20 ~ 30% が失敗すると予想されることを考慮して、単体テストをどのように作成しますか?
単体テストが成功することが常に目標でなければなりません。使用方法によっては、ソフトウェアの重大なエラーと、予想される翻訳のエラーを区別できません。
両方を分離することをお勧めします。
単体テストは決定論的でなければなりません。テストの失敗は、「意図したとおりに機能する」ものではなく、ソフトウェアの障害を示す必要があります。あなたの場合、変換が成功するか失敗するかにかかわらず、結果を確実に確認できる方法でデータを準備し、それらをテストします(失敗が予想される場合、失敗のテストは常にオプションです-重要なのは、いついつを制御できるかですテストの成功/失敗)。
失敗することが予想される単体テストは、単体テストではありません。フィルターとして機能し、「十分に近い」かどうかを判断し、合格/不合格を決定する評価関数を使用して、成功の定義を変更する必要があります。翻訳者が上達するにつれて、フィルターの基準を上げることができます。
この状況で私が学んだ手法は、機能する (または決定論的な) コードの部分のみをテストすることです。もちろん、決定論的な部分と非決定的な部分を区別するのは難しい部分です。これを簡単に言うと、「関数に縮小 [またはリファクタリング]」します。これは、決定論的なコードの部分を分離し、それらの部分をテストすることを意味します。
シナリオベースのコンテキストについては、このブログ記事を読んで、レガシ コードに関するテストを取得するとき (およびApprovalTestsという名前のオープン ソースの単体テスト ライブラリを使用するとき) に、この手法を適用する方法を説明してください。
ここで興味深いかもしれないさらに別の手法は、「理論に基づく」テストです。詳細については、このブログ投稿をご覧ください。
単体テストが失敗する可能性がある唯一の理由は、タイミングが関係している場合です(主に電子機器がテストされている場合)。ただし、そのような場合でも、可能であればタイムアウトの延長/切り替えやその他のタイミングの問題によって、ユニットテストからタイミングの問題を取り除くことが目標である必要があります。それが不可能な場合は、十分に文書化する必要があります。
タイミングの問題を取り除き、テストを決定論的にする別の方法は、いくつかのインジェクションメソッドを使用して、すべての外部インターフェイスのスタブを作成することです。つまり、外部インターフェイスメソッドが返す値を設定できます。このように単体テストを設定することで、文字通りすべてをテストし、すべてのエラー状態をテストすることができます。
(ストーリー:いくつかの単体テストがときどき失敗する会社で働いていました。それらが重大なエラーなのかタイミングの問題なのかを分析できたのはごくわずかでした。そもそも優れた単体テストを作成するために多くの時間を節約できます) 。