3

テストメソッドで for ループを実行しても (概念的に) 大丈夫ですか?

さまざまな入力が正しい値を返すかどうかを判断するために、さまざまなパラメーター値をコントローラーにテストしたいと思います。

  test "logged in user - add something - 0 qty" do
    @app = Factory.create(:app)

    (0..5).each do |t|
      @qty = t
      login(:user)
      get :add. :id => @app.id, :qty => @qty
      assert_nil(flash[:error])
      assert_response :redirect
      assert_redirect_to :controller => :apps, :action => :show, :id => @app.id
      if @qty = 0 
        assert_equal(Invite.all.count, @qty + 1)
      else 
        assert_something .........
    end
  end

そんな感じ。

4

8 に答える 8

7

私は通常、テストコード内のあらゆる種類の条件文やループを避けようとします。テストをできるだけ単純にする必要があります。テストにロジックを含め始める場合は、テストをテストて、設計どおりに機能することを確認する必要があります。ループを個別のテストケースに分割します。そうすれば、いずれか1つが失敗した場合に、失敗の原因となった入力を正確に特定するのが簡単になります。テストが失敗した場合、その原因がすぐに明らかになるはずです。それを理解するためにテストコードを分析する必要はありません。

アップデート:

テストケースでループが必要になる非常にまれなケースがいくつかあることを付け加えたいと思います。具体的な例の1つは、同時実行性の問題をテストする場合です。これは一般的な規則の例外であり、テストにあらゆる種類のロジックを含めるには、非常によく理解された理由が必要です。

于 2009-11-24T12:58:44.240 に答える
2

テストメソッドでforループを実行しても(概念的には)大丈夫ですか?

つまり、それは政治的に正しいのですか?

うまくいきますよね?私は異議がどうなるか想像しようとしています。

于 2009-11-24T12:58:14.037 に答える
2

各テストで複数のアサーションまたは検証を使用することは政治的に正しくありません。とは言え、みんなやってます。新しいスタイルの 1 つは、Cucumber のテスト シナリオで見られます。シナリオは依然として非常に読みやすい形式ですが、複数のデータ セットをテストできます。

しかし、それは Ruby です。もしあなたが他人の指示に忠実に従っているなら、あなたはそれを使用しないでしょう。正しい方法はありません。最も一般的な方法だけがあり、それは頻繁に変更されます。

私はかつて歯医者に、ブラッシング、フロス、すすぎの順番を尋ねたことがあります。彼は、私が実際に 3 つすべてを実行できたとしても構わないと言いました。ポイントは、多くの場合、標準以下の実装は、まったくないよりはましだということだと思います。ループによってテストがより楽しくなり、その結果可能性が高くなる場合は、テストから地獄をループする必要があります。

于 2009-11-24T16:16:08.740 に答える
2

また、テストは、ソフトウェアが何をするべきかを示す「生きたドキュメント」と見なす必要があるため、できるだけ明確に保ちます。

于 2009-11-24T13:26:14.963 に答える
1

この単一のテストが実際に複数のテスト(異なるパラメーターを使用)を実行していることをフレームワークに認識させることができれば、さらに良いでしょう。これにより、どの正確なパラメーターの組み合わせが失敗し、どれがテストレポートで成功するかを確認できます。

于 2009-11-24T13:00:43.393 に答える
1

ループが必要な場合がありますが、ループは必要ありません。テストが複雑になると、テストの操作が難しくなることを忘れないでください。アプリケーションが進化すると、テストも進化します。最初にそれらを複雑にしすぎると、いつの日か選択に直面する可能性があります。

  • 失敗するこの大きくて雑然としたテストをリファクタリングするのに 3 日を費やす必要がありますか?
  • テストを削除し、新しい、よりシンプルでエレガントな (3,5 日で) を書く必要がありますか?

これは難しい選択です。最初のオプションでは、プロジェクトを前進させない何かのために新しい機能を実装するために時間を無駄にしていますか? 時間はありますか?あなたのマネージャーは、あなたがこれを行う時間があると思いますか? このプロジェクトであなたの時間にお金を払っているクライアントは、あなたがこれに時間を割いていると思いますか?
2 番目のオプションは合理的ですが、新しいテストを作成するときに、すべてのケースを古いもの (および新しいもの) としてカバーしたことをどうやって知るのでしょうか? それはすべてドキュメントにありますか?それはテストドキュメントにありますか?それらのすべてを覚えていますか?それとも、テスト コードを調べてリファクタリングし、このコード BLOB 内に隠されているすべてのケースを明らかにするのでしょうか? これが第一候補になりませんか?

レガシー コードのようなテストを作成しないでください。誰も触れたくないコード、誰も本当に知らないコード、誰もがそれをできるだけ避けて無視しようとしています。テストは残りのアプリケーションとして設計する必要があります。コード設計に適用するときは、多くの設計原則を適用してください。シンプルにします。別途責任。それらを論理的にグループ化します。リファクタリングを容易にします。それらを拡張可能にします。考慮しなければならないことがたくさんあります。

あなたの場合は。コードが <0,100> のパラメーターに対して何かを行うユースケースがあると仮定しましょう (コードの 0..5 は互いに近く、広い範囲を使用すると例がより明確に見えます)。他の値では、いくつかの例外処理を行います。この場合、テスト ケースが必要です。

  • パラメータ = -1 の場合
  • パラメータ=0のとき
  • パラメータ=1の場合
  • パラメータ=99の場合
  • パラメータ=100の場合
  • パラメータ=101の場合

コードを適切にチェックしながら、リファクタリングが容易で読みやすい単純な個別のテスト ケース。
パラメーターが (10,70) にある場合の動作を確認するためにループを使用するテスト ケースを追加できますが、推奨されません。多くのテストと幅広いパラメータ範囲では、リソースの無駄です。アルゴリズムが決定論的である場合、一部の値のセットに対して同じ手順を実行すると、1 つの値で機能する場合はすべての値で機能します。
等価クラス、境界値、ペアワイズ テスト、パス カバレッジ、ステートメント カバレッジ、分岐カバレッジ、その他のテスト手法について読んで、テストを改善してください。

于 2009-11-24T21:23:21.957 に答える
1

私は政治的に正しくない人のリストに自分自身を追加するつもりです。値の範囲をテストする場合、ループはテストの可読性を高めることができます。さらに、DRY を支援し、リファクタリングを容易にします。新しいパラメーターをテストでメソッドが呼び出される 8 つの場所に追加しますか?それとも 1 つだけに追加しますか?

この手法を使用したテストを次に示します。自家製のテスト ライブラリを使用していますが、その手法は普遍的です。

  def test_swap_name
    test_cases = [
      [
        'Paul, Ron P.A.',
        'Ron Paul PA'
      ],
      [
        "PUBLIC, SR., JOHN Q",
        "JOHN Q PUBLIC SR"
      ],
      [
        "SMITH, JR., MARK A",
        "MARK A SMITH JR"
      ],
      [
        'James Brown',
        'James Brown'
      ],
      # (more test cases)
    ]
    for original, swapped in test_cases
      assertInfo("For original = #{original.inspect}") do
        assertEquals(original.swap_name, swapped)
      end
    end
  end

assertInfo は、例外メッセージの先頭に任意の文字列を追加します。これにより、テストが失敗したときに、どのデータがテストされていたかを知ることができます:

./StringUtil.test.rb
Method "test_swap_name" failed:
Assert::BlownAssert: For original = "Paul, Ron P.A.": Expected:
"Ron Paul PA"
but got
"Paul, Ron P.A."
./../../testlib/Assert.rb:125:in `fail_test'
./../../testlib/Assert.rb:43:in `assertEquals'
./StringUtil.test.rb:627:in `test_swap_name'
于 2010-01-11T19:37:53.137 に答える
0

テストでアサーションが 1 回しか発生しない限り、通常、テストでのループは問題ありません。つまり、これで問題ありません。

test "something" do
  for item in @collection
    assert_something item
  end
end

しかし、これはそうではありません:

test "something" do
  for item in @collection
    assert_something item
    assert_something_else item
  end
end

そして、これらはあなたが自分自身を憎むようになります:

test "something" do
  for item in @collection
    assert_something item
    if x == y
      assert_something_else item
    end
  end
end

test "something" do
  for item in @collection
    assert_something item
  end
  for item in @collection
    assert_something_else item
  end
end

この方法でテストを作成するのは、コレクション内のアイテムが互いに大幅に異なる場合のみですが、一般的に共有される動作を確認する必要がありました。たとえば、異なるオブジェクトの 10 個のインスタンスがあるとしますが、それらはすべて何らかのメッセージに応答することになっています。したがって、すべてのインスタンスがダック型メソッドが実行するはずのことを実行できることを確認します。しかし、 のインスタンスを常に含むコレクションがある場合はFoo、 についてアサーションを作成した方がよい場合がよくあります@collection.first。テストはより速く実行され、すべてのインスタンスでアサーションを繰り返しても実際にはあまり得られません。ほとんどの場合、itemコレクションの残りの部分から切り離してテストする方がはるかに優れています。特に偏執的であると感じている場合は、通常、これで問題ありません。

test "items are all Foo" do
  for item in @collection
    assert_kind_of Foo, item, "Everything in @collection must be Foo."
  end
end
test "something" do
  assert_something @collection.first
end

コレクションに非オブジェクトがある場合、「something」テストはとにかく失敗しますFooが、前のテストにより、実際の問題が何であるかが十分に明らかになります。

一言で言えば、それを避けてください。また、将来的に問題が発生した場合でも、問題の少ないものに簡単にリファクタリングできるように、テストは十分に単純である必要があります。

于 2009-11-24T21:55:34.133 に答える