あるテストが別のテストのセットアップであるテストをどのように構成するのですか?


18

アイム統合のみ公開APIを使用して、システムをテストします。次のようなテストがあります。

def testAllTheThings():
  email = create_random_email()
  password = create_random_password()

  ok = account_signup(email, password)
  assert ok
  url = wait_for_confirmation_email()
  assert url
  ok = account_verify(url)
  assert ok

  token = get_auth_token(email, password)
  a = do_A(token)
  assert a
  b = do_B(token, a)
  assert b
  c = do_C(token, b)

  # ...and so on...

基本的に、単一のトランザクションの「フロー」全体をテストしようとしています。フローの各ステップは、成功する前のステップに依存します。私は自分自身を外部APIに制限しているため、データベースに値を突っ込むだけではいけません。

したがって、 `Aを実行する非常に長いテストメソッドが1つあります。主張する; B; 主張する; C; または...私はそれを別々のテストメソッドに分割します。各テストメソッドは、それを実行する前に前のテストの結果を必要とします:

def testAccountSignup():
  # etc.
  return email, password

def testAuthToken():
  email, password = testAccountSignup()
  token = get_auth_token(email, password)
  assert token
  return token

def testA():
  token = testAuthToken()
  a = do_A(token)
  # etc.

これは臭いだと思う。これらのテストを記述するより良い方法はありますか?

回答:


10

このテストが頻繁に実行されることを意図している場合、あなたの懸念はむしろ、これらの結果で動作することが期待される人々に便利な方法でテスト結果を提示する方法に集中するでしょう。

この観点から、testAllTheThings巨大な赤旗を掲げます。誰かがこのテストを1時間ごとまたはもっと頻繁に実行し(もちろん、バグのあるコードベースに対しては、そうでなければ再実行する意味はない)、FAILどのステージが失敗したかを明確に示すことなく毎回同じものを見ると想像してください。

再実行の結果(コードのバグ修正の着実な進捗を前提とする)は次のようになるため、個別のメソッドはより魅力的に見えます。

    FAIL FAIL FAIL FAIL
    PASS FAIL FAIL FAIL -- 1st stage fixed
    PASS FAIL FAIL FAIL
    PASS PASS FAIL FAIL -- 2nd stage fixed
    ....
    PASS PASS PASS PASS -- we're done

補足として、私の過去のプロジェクトの1つで、依存テストの再実行が非常に多かったため、ユーザーは、後の段階で失敗が「トリガー」され、後の段階で予想される失敗を繰り返したくないと不満を抱き始めました。彼らは、このゴミがテスト結果を分析するために彼らに難しく、それを作ると述べ、「我々は残りの部分は、テスト設計で失敗することをすでに知っている、繰り返し私たちを気にしないでください」

その結果、テスト開発者は最終的に追加のSKIPステータスでフレームワークを拡張し、依存テストの実行を中止するための機能をテストマネージャーコードに追加SKIPし、レポートからpedテスト結果を削除するオプションを追加する必要がありました。

    FAIL -- the rest is skipped
    PASS FAIL -- 1st stage fixed, abort after 2nd test
    PASS FAIL
    PASS PASS FAIL -- 2nd stage fixed, abort after 3rd test
    ....
    PASS PASS PASS PASS -- we're done

1
私がそれを読んでいると、実際にはtestAllTheThingsを書いたほうが良かったように聞こえますが、どこで失敗したかを明確に報告します。
ハビエル

2
@Javier それが失敗した場所の明確な報告理論的には、私の実際の音の素敵を、テストが頻繁に実行されるたびに、これらと連動することを強くダムPASS-FAILが見好むトークン
ブヨ

7

テストコードとセットアップコードを分離します。おそらく:

# Setup
def accountSignup():
    email = create_random_email()
    password = create_random_password()

    ok = account_signup(email, password)
    url = wait_for_confirmation_email()
    verified = account_verify(url)
    return email, password, ok, url, verified

def authToken():
    email, password = accountSignup()[:2]
    token = get_auth_token(email, password)
    return token

def getA():
    token = authToken()
    a = do_A()
    return a

def getB():
    a = getA()
    b = do_B()
    return b

# Testing
def testAccountSignup():
    ok, url, verified = accountSignup()[2:]
    assert ok
    assert url
    assert verified

def testAuthToken():
    token = authToken()
    assert token

def testA():
    a = getA()
    assert a

def testB():
    b = getB()
    assert b

生成されたすべてのランダムな情報は、失敗した場合にアサーションに含める必要あります。そうしないと、テストが再現できない可能性があります。使用したランダムシードを記録することもあります。また、ランダムなケースが失敗した場合は、その特定の入力をハードコードされたテストとして追加して、回帰を防ぎます。


1
+1 テストはコードであり、DRYは本番環境と同様にテストにも適用されます。
ダグ

2

それほど良くはありませんが、少なくともセットアップコードとアサートコードを分離できます。ストーリー全体を段階的に伝える別のメソッドを作成し、必要なステップ数を制御するパラメーターを取得します。次に、各テストのようなものを言うことができるsimulate 4か、simulate 10そしてそれがテストするものは何でもアサート。


1

さて、ここで「エアコーディング」によってPython構文を取得することはできないかもしれませんが、次のような一般的な関数を実装できるという考えをお持ちだと思います。

def asserted_call(create_random_email,*args):
    var result=create_random_email(*args)
    assert result
    return result

次のようにテストを書くことができます:

  asserted_call(account_signup, email, password)
  url = asserted_call(wait_for_confirmation_email)
  asserted_call(account_verify,url)
  token = asserted_call(get_auth_token,email, password)
  # ...

もちろん、このアプローチの読みやすさの損失がそれを使用する価値があるかどうかは議論の余地がありますが、定型コードを少し減らします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.