web-dev-qa-db-ja.com

テストコードは通常のコードとして扱われるべきですか?

他の誰かの投稿を読んで、テストコードは通常のコードとしてではなく小さなプログラムとして扱う必要があることを覚えています。他のエンジニアの同僚と話し合うときに参照したいが、満場一致で共有されていない。

特に、プライベート関数などを使用して、テストを通常のコードとしてリファクタリングする必要があるかどうかを議論するときに、私はそれを参照していました。一部の同僚は、開発者に次のようにテストを変換するように勧めていました。

public test1() {
    variable1 = 'value1'
    variable2 = 'value2'
    variable3 = 'value2'
    assert_something1()
    assert_something2()
    assert_something3()
}

public test2() {
    variable1 = 'value1'
    variable2 = 'value2'
    different_variable3 = 'different_value3'
    assert_something1()
    assert_something2()
    assert_something_different3()
}

これに:

public test1() {
    set_common_variables()
    do_common_assertions()
}

public test2() {
    set_common_variables()
    different_variable3 = 'different_value3'
    do_common_assertions()
    assert_something_different3()
}

private set_common_variables()
{
    variable1 = 'value1'
    variable2 = 'value2'
}

private do_common_assertions()
{
    assert_something1()
    assert_something2()
}

彼らは繰り返しを避けることによりコードの可読性を向上させるための基本的なリファクタリングルールを奨励しているだけだと主張しますが、私は個人的にこれらはこれらの理由のためここでは適用されないと思います:

  • テストは別々に、そして短いものを読むことを意味するべきであるので、前提とアサーションは一緒にあるべきだと思います。

  • 上記の例は、リファクタリングの必要性を示しますが、テスト自体ではなくコード内にあります。テストをリファクタリングすることにより、何が行われているのかを隠してしまう可能性があります。

これに関するあなたの経験は何ですか?テストは通常​​、通常のコードとして扱われるべきかどうかを考えますか?

4
namelivia

私は他の誰かの投稿を読んだことを覚えています。テストコードは通常のコードとしてではなく小さなプログラムとして扱う必要があります。これは私が本当に気に入っているアイデアであり、他のエンジニアの同僚と話し合うときに参照したいと思いますが、満場一致で共有されるわけではありません。

ユニットテストで効果的に作業する 、Jay Fields著、このトピックをカバーし、あなたの立場をサポートする有用な議論を提供するかもしれません。

DAMP not DRY を確認することもできます。

テスト駆動開発のようなコンテキストでは、自動チェックが開発者のサポートを提供することが期待されているため、詳細な設計ドキュメントから得ることができるので、テストを読むことは仕様を読むことと同じような経験を呼び起こすはずです。私自身、特に多くのナビゲーションが必要な仕様を読むのは好きではありません。

同じ考えの別のスペル:テスト自体には専用のテストがないため、明らかに間違いのない設計が必要です。

これは、チェックに共有コードがないことを意味するわけではありませんが、共有コードは通常、行の重複を回避するのではなく、共有意図を中心としています。たとえば、テストに重要なデータと付随するデータを簡単に見分けられるようにスペルを選択した、テストデータを返すルーチンがあるとします。

// Verify that the payroll code does the right thing
// in a state with no income tax
employee = AnyEmployee.withHomeState("TX")

...
8
VoiceOfUnreason

決定的に、価値のあるテストコードは、次の理由により、製品コードと同じくらい注意深く扱う必要があります。

  • テストコードは重要なドキュメントリソースです。テストコードは私たちが取り組んでいるシステムについて話します。それはビジネスやシステムのドメインについての物語を伝えます。コンポーネントの動作、コンポーネントが相互に作用するときのシステムの動作。許容できるものと許容できないもの。この情報はテストを見ると簡単に取得できるはずですが、テストするコードが読みにくい、またはコードが推論するのが難しい場合は難しいと思います。読みやすさはコードに関係なく重要です。

  • システムの適切な動作に対する信頼は、このコードに基づいています。信頼できるスパゲッティコードはなく、長い間、私たちの信頼に値します。

  • 保守が難しいコードをテストすると、信頼性の高い新しい製品コードを記述したり、既存のコードを進化させたりする能力に影響します。それは最終的には生産性に影響を与え、それは今度は市場の変化する要求にコードを適応させる私たちの能力に影響を与えます。特定のドメイン言語をテストすることで、そうでないシステムよりも簡単に(そして安価に)システムを進化させることができます。企業にとって、生産性と市場投入までの時間の問題。

  • 維持が難しいコードのテストは、開発者がより多くのテストを記述したり、既存のテストを維持したりすることを思いとどまらせます。これによる影響は、徐々に減衰し始める量産コードの影響を受けます。高い確実性と自信を持って製品コードを進化させることができない場合、システム全体が死に始めます。

テストコードは、システム寿命全体を通じて維持されることを覚えておいてください。おそらく、テストコードから本番コードの作成を開始します。この時点で、テストコードを適切に構造化して再利用できるようにしたいと考えています。

なぜ私はこれを言うのですか?のため Test should be meant to be read separately and short, so I think premises and assertions should be together, even if this means repeating code.

public test1() {
    set_common_variables()
    do_common_assertions()
}

public test2() {
    set_common_variables()
    different_variable3 = 'different_value3'
    do_common_assertions()
    assert_something_different3()
}

上記のスニペットはかなり短く、読みやすいです1

さらに、カプセル化do_common_assertionsは何から私たちを抽象化するのに役立ちますdo_common_assertions 手段。 do_common_assertions他のテストがそれを利用できるように、これらの検証を1か所に置く価値がある、変更される傾向があるルールを検証します。それ以外の場合は、テストコードを予期しない誤検知にさらします。これは、開発者が新しいルールでテストを更新し忘れたために発生します。

最後に、私は冗長性がネストされたìf/elseまたはtry/catchブロック。 90%が同じように見える数行の後、読みを停止し、フォーカスを失います。


1:意味のあるメソッド名があり、ドメインのユビキタス言語を呼び起こすと仮定

6
Laiv

私はあなたの同僚に同意します。 DRYは、テストと本番用コードに適用する必要があります。テストフレームワークの作成者は、各テストメソッドが実行される前に共通のセットアップメソッドを実行するメカニズム(注釈、関数デコレータなど)を提供することにも同意しています。

2
Matthew

それはあなたが何を達成したいかによると思います。退行を防ぐための唯一のセーフティネットの場合、特に外部からの場合のみ(つまり、「壊れているのか?」と答える閉じたブラックボックスとして扱われる場合)、要件は製品コードとほとんど同じです。実際、デバイスをブラックボックスでテストする製品コードがあります。これが事実である場合、テストコードはおそらくそのように扱われるべきです。目的がたまたま実行されるドキュメントになることである場合、私はOPに同意する傾向があります。

bool test_foo_completes_for_negatives(){
  foo(-1);
  foo(-2);
  foo(-102);

  return true;
}

何をテストするかという点で、はるかに読みやすくなります

template <typename I>
bool test_foo_completes_for_negatives(){
  std::vector<I> negatives = get_negative_list();
  for (auto& test_val : negatives) {
    foo(test_val);
  }

  return true;
}

後者の方がより柔軟で、テストの目的の点で読みやすくなります。それは常にトレードオフであるので、それは良くも悪くもなりません。

注目に値するかもしれませんが、テストコードが処理されることはほとんどありません正確にプロダクションコードと同じです。たとえば、テストコードが独自の(メタ)テストを持つことはほとんどありません。

2
ANone