回答:
これは私が考えることができるどんな方法でも誤解だと思います。
production-codeをテストするtest-codeはまったく似ていません。私はPythonでデモンストレーションします:
def multiply(a, b):
"""Multiply ``a`` by ``b``"""
return a*b
次に、簡単なテストは次のようになります。
def test_multiply():
assert multiply(4, 5) == 20
両方の関数の定義は似ていますが、どちらも非常に異なることを行います。ここに重複コードはありません。;-)
また、テスト機能ごとに1つのアサーションを持つ本質的に重複したテストを書くこともあります。これは狂気であり、私はこれをしている人々を見ました。これは悪い習慣です。
def test_multiply_1_and_3():
"""Assert that a multiplication of 1 and 3 is 3."""
assert multiply(1, 3) == 3
def test_multiply_1_and_7():
"""Assert that a multiplication of 1 and 7 is 7."""
assert multiply(1, 7) == 7
def test_multiply_3_and_4():
"""Assert that a multiplication of 3 and 4 is 12."""
assert multiply(3, 4) == 12
1000行以上の有効なコード行に対してこれを行うことを想像してください。代わりに、「機能」ごとにテストします。
def test_multiply_positive():
"""Assert that positive numbers can be multiplied."""
assert multiply(1, 3) == 3
assert multiply(1, 7) == 7
assert multiply(3, 4) == 12
def test_multiply_negative():
"""Assert that negative numbers can be multiplied."""
assert multiply(1, -3) == -3
assert multiply(-1, -7) == 7
assert multiply(-3, 4) == -12
これで、機能が追加/削除されたときに、1つのテスト関数の追加/削除を検討するだけで済みます。
for
ループを適用していないことに気づいたかもしれません。これは、いくつかのことを繰り返すのが良いからです。ループを適用すると、コードはずっと短くなります。しかし、アサーションが失敗すると、あいまいなメッセージを表示する出力を難読化する可能性があります。これが発生すると、テストの有用性が低下し、どこで問題が発生したかを検査するデバッガが必要になります。
assert multiply(1,3)
失敗しますが、失敗したテストレポートも取得しませんassert multiply(3,4)
。
def test_shuffle
ます。
assert multiply(*, *) == *
したがって、assert_multiply
関数を定義できます。現在のシナリオでは、行数と読みやすさは関係ありませんが、より長いテストでは、複雑なアサーション、フィクスチャ、フィクスチャ生成コードなどを再利用できます。これがベストプラクティスであるかどうかはわかりませんが、通常はこの。
DRYの最終的なターゲットには、すべてのテストコードの削除が含まれませんか?
いいえ、DRYの最終的な目標は、実際にはすべての製品コードを排除することです。
テストがシステムに必要なものの完全な仕様である場合、対応する製品コード(またはバイナリ)を自動的に生成し、製品コード自体を事実上削除する必要があります。
これは実際に、モデル駆動型アーキテクチャのようなアプローチが達成すると主張するものです。人間が設計した単一の真実のソースであり、そこからすべてが計算によって導き出されます。
私は逆(すべてのテストを取り除く)が望ましいとは思わない:
単体テストは、意図しない変更をより困難にすることを目的としているため、意図的な変更をより困難にすることもあります。この事実は確かに乾燥した原則に関連しています。
たとえばMyFunction
、本番コードで1か所だけで呼び出される関数があり、20個の単体テストを記述する場合、その関数が呼び出されるコード内に21か所を簡単に作成できます。MyFunction
(一部の要件が変更されるため)の署名、セマンティクス、またはその両方を変更する必要がある場合、1つだけではなく21の場所を変更する必要があります。そして、その理由は確かにDRY原則の違反ですMyFunction
。同じ関数呼び出しを(少なくとも)21回繰り返しました。
このような場合の正しいアプローチは、テストコードにもDRY原則を適用することです。20個の単体テストを記述するとき、単体テストでの呼び出しをMyFunction
いくつかのヘルパー関数(理想的には1つだけ)にカプセル化します。 20ユニットテスト。理想的には、コード呼び出しのたった2つの場所で終わることになりますMyFunction
。1つは実動コードから、もう1つは単体テストからです。したがって、MyFunction
後の署名を変更する必要がある場合、テストで変更する場所はわずかしかありません。
「少数の場所」はまだ「1つの場所」(単体テストなしで得られるもの)を超えていますが、単体テストを行うことの利点は、変更するコードが少ないという利点を大きく上回るはずです(そうでなければ、単体テストを完全に実行します)違う)。
ソフトウェア構築の最大の課題の1つは、要件を把握することです。それは「このソフトウェアは何をすべきか」という質問に答えることです。 システムに必要なことを正確に定義するには、ソフトウェアに正確な要件が必要ですが、ソフトウェアシステムおよびプロジェクトのニーズを定義する人には、ソフトウェアまたは正式な(数学)背景を持っていない人も含まれます。要件の定義が厳密でないため、ソフトウェア開発はソフトウェアを要件に合わせて検証する方法を見つけることを余儀なくされました。
開発チームは、プロジェクトの口語的な説明をより厳格な要件に翻訳していることに気付きました。テスト分野はソフトウェア開発のチェックポイントとして合体し、顧客が望むと言うことと、ソフトウェアが望むと理解することの間のギャップを埋めます。ソフトウェア開発者と品質/テストチームの両方が(非公式)仕様の理解を形成し、それぞれが(独立して)ソフトウェアまたはテストを作成して、理解が一致することを確認します。(不正確な)要件を理解するために別の人を追加すると、要件の精度をさらに高めるための質問と異なる視点が追加されました。
受け入れテストは常に行われてきたため、テストの役割を拡張して自動化された単体テストを作成するのは自然でした。問題は、テストを行うためにプログラマーを雇うことを意味していたため、品質保証からテストを行うプログラマーに視野を狭めました。
それはすべて、テストが実際のプログラムとほとんど変わらない場合、おそらくテストを間違って行っているということです。Msdyの提案は、テストではなく、テスト方法に重点を置くことです。
皮肉なことに、業界は口語の説明から要件の正式な仕様を取得するのではなく、テストを自動化するコードとしてポイントテストを実装することを選択しています。答えるためにソフトウェアを構築できる正式な要件を作成するのではなく、採用されたアプローチは、正式なロジックを使用してソフトウェアを構築するアプローチではなく、いくつかのポイントをテストすることでした。これは妥協案ですが、かなり効果的で比較的成功しています。
既に述べたように、単体テストにはテスト対象のコードの重複を含めないでください。
ただし、セットアップはテスト間で類似している(ただし同一ではない)傾向があるため、ユニットテストは通常、「本番」コードほどDRYではありません。特に、かなりの数の依存関係がある場合/フェイク。
もちろん、この種のことを一般的なセットアップ方法(またはセットアップ方法のセット)にリファクタリングすることは可能ですが、これらのセットアップ方法は長いパラメーターリストを持ち、かなり壊れやすい傾向があることがわかりました。
だから実用的である。保守性を損なうことなくセットアップコードを統合できる場合は、必ず実行してください。しかし、代替手段が複雑で脆弱なセットアップ方法のセットである場合、テスト方法を少し繰り返しても問題ありません。
地元のTDD / BDDエバンジェリストは次のように言います。
「本番コードはDRYである必要があります。しかし、テストを「湿らせる」ことは問題ありません。」
テストは基本的にコードと同じことを表しているため、コードの(概念ではなく、実装で)重複しているようです。
これは真実ではありません。テストはユースケースを記述し、コードはユースケースを渡すアルゴリズムを記述するため、より一般的です。TDDでは、ユースケース(おそらくユーザーストーリーに基づく)の作成から始め、その後、これらのユースケースを渡すために必要なコードを実装します。そのため、小さなテスト、小さなコードチャンクを作成し、その後、必要に応じてリファクタリングを繰り返してリファクタリングを行います。それが仕組みです。
テストによっても同様に繰り返すことができます。たとえば、フィクスチャ、コードを生成するフィクスチャ、複雑なアサーションなどを再利用できます。通常、テストのバグを防ぐためにこれを行いますが、通常、テストが実際に失敗するかどうかを最初にテストするのを忘れて、 、コードのバグを30分探していて、テストが間違っている場合... xD