自動化テストでテストが合格したかどうかをどのように判断しますか?Pytestテストフレームワークの断言の使い方を詳しく理解する


ソフトウェアテストの主な仕事目標は、実際の結果が予想された結果と一致していることを検証することであり、自動化ソフトウェアテストでは、断言によってこの目的を実現することである.Pytestでの断言はPythonオリジナルのassert文によって実現され、Pythonオリジナルのassert文を最適化し、断言に失敗した場合、エラー情報がより豊富になり、テスト時に問題の原因を迅速に特定することができる.
本文文字数5195
APIテスト、Webテスト、APPテストにおいても、テスト例が成功したかどうかは、実際の結果と予想結果が一致したかどうかを比較することによって判断されます.予想結果が実際の結果と一致すると、試験用例の実行が合格したことを示し、予想結果が実際の結果と一致しない場合、試験用例の実行が失敗したことを示す.予想される結果と実際の結果を比較するプロセスは,自動化ソフトウェアテストにおいて断言によって実現される.
優れたテストフレームワークは、TestNGのassertTrue、assertEquals、assertSameなど、断言の方法を提供しています.前にPytestの使い方「Pytestフレームワークに基づく自動化テスト開発実践(万字長文入門編)」を紹介しましたが、ここではPytestの断言を詳しく紹介します.TestNGに比べて簡単で、assert文は1つしかありませんが、機能が非常に強く、使いやすいです.
01—Python原生のassert
Pythonのassert文は通常、コードを必要とするチェックに使用され、ある状況が必ず発生するか、必ず発生しないかを決定します.
Pythonのassert文の構文は次のとおりです.
assert expression1 ["," expression2]

expression 1は往々にして条件式であり、条件式がTrueの場合、pass文を実行したことに相当し、何もしない.条件式がFalseの場合、例外AssertionErrorが放出され、特定のエラーメッセージexpression 2が返されます.実際の例を見てみましょう
# content of my_assertion.py
def assertion():
    assert [1, 2, 3] == [1, 2, 4], "left is [1,2,3], right is [1,2,4]"if __name__ == '__main__':
    assertion()

上のコードを実行して結果を見てみましょう.
$ python my_assertion.py 
Traceback (most recent call last):
  File "my_assertion.py", line 5, in <module>
    assertion()
  File "my_assertion.py", line 2, in assertion
    assert [1, 2, 3] == [1, 2, 4], "left is [1,2,3], right is [1,2,4]"
AssertionError: left is [1,2,3], right is [1,2,4]

これにより、assertの後の条件式はFalseであり、AssertionErrorが投げ出され、エラーメッセージleft is[1,2,3]、right is[1,2,4]が表示される.
しかし、ここには少し残念な点があります.開発者に条件判断に失敗した具体的な位置を明確に伝えていない.開発者が自分で比較してみると、==左の3番目の要素と右の3番目の要素が違います.
02-Pytestのassertの利点
ソフトウェアテストの仕事では、断言に失敗することがよくあります.失敗するたびに、エンジニアの目をテストして失敗の具体的な原因とエラーの位置を観察する必要がある場合は、非常に時間がかかります.強力なPytestも広範なテストエンジニアが直面している問題を考慮しているため、Pythonの元のassert文を最適化し、改善した.主に失敗を断言する時、誤った具体的な情報と位置を表示し、テストエンジニアに失敗の原因を一目で明らかにした.
それとも上記の例で、テスト例(test_の先頭の関数)に入れます.
# content of test_assertion.py
def test_assertion():
    assert [1, 2, 3] == [1, 2, 4], "left is [1,2,3], right is [1,2,4]"def test_assertion():
>       assert [1, 2, 3] == [1, 2, 4], "left is [1,2,3], right is [1,2,4]"
E       AssertionError: left is [1,2,3], right is [1,2,4]
E       assert [1, 2, 3] == [1, 2, 4]
E         At index 2 diff: 3 != 4
E         Full diff:
E         - [1, 2, 4]
E         ?        ^
E         + [1, 2, 3]
E         ?

爽やかな感じがしますか?pytestはエラーの位置がindexが2の要素程度が等しくないことを明確に示した.このわずかな改善により、テストに失敗したときにエラーの原因を特定する効率が大幅に向上しました.
テスト例でassert文を実行すると、Pytestがassert文を書き換えたため、上記の効果が得られます.非テスト例のassert文、例えばテスト項目のutils関数の中で、assertを使用するかPythonのオリジナルの効果を使用するか.
03—Pytest断言の使い方
自動化テストの例では、最も一般的な断言は等しい断言であり、断言の予想結果と実際の結果が一致することである.通常、予想される結果と実際の結果のデータ型は、文字列、メタグループ、辞書、リスト、およびオブジェクトであると断言します.Pytestはassertと==を通じてこれらのデータ型に対する等しい断言を完璧にサポートすることができる.一般的なデータ型の断言操作をいくつか紹介します.
4.1アサーション文字列
断言文字列は非常に簡単で、予想された文字列と実際の文字列をそれぞれ==の両側に書くだけで、断言に失敗した場合、最初の等しくない要素の下付き文字がリストされます.以下に、実際のテスト作業でよく使用されるいくつかの文字列の断言方法を示します.
# content of test_assertions.py
class TestAssertions(object):
    def test_string_1(self):
        assert "spam" == "eggs"def test_string_2(self):
        assert "foo 1 bar" == "foo 2 bar"def test_string_3(self):
        assert "foo
spam
bar"
== "foo
eggs
bar"
def test_string_4(self): def f(): return "streaming" assert f().startswith('S')

これらのテスト例を実行して、出力効果を見てください.コア部分は以下の通りです.
============================================================ FAILURES ============================================================
__________________________________________________ TestAssertions.test_string_1 __________________________________________________
​
self = <test_assertions.TestAssertions object at 0x10911a4d0>
​
    def test_string_1(self):
>       assert "spam" == "eggs"
E       AssertionError: assert 'spam' == 'eggs'
E         - eggs
E         + spam
​
tests/test_assertions.py:3: AssertionError
__________________________________________________ TestAssertions.test_string_2 __________________________________________________
​
self = <test_assertions.TestAssertions object at 0x10911a890>
​
    def test_string_2(self):
>       assert "foo 1 bar" == "foo 2 bar"
E       AssertionError: assert 'foo 1 bar' == 'foo 2 bar'
E         - foo 2 bar
E         ?     ^
E         + foo 1 bar
E         ?     ^
​
tests/test_assertions.py:6: AssertionError
__________________________________________________ TestAssertions.test_string_3 __________________________________________________
​
self = <test_assertions.TestAssertions object at 0x10911c2d0>
​
    def test_string_3(self):
>       assert "foo
spam
bar"
== "foo
eggs
bar"
E AssertionError: assert 'foo
spam
bar'
== 'foo
eggs
bar'
E foo E - eggs E + spam E bar ​ tests/test_assertions.py:9: AssertionError __________________________________________________ TestAssertions.test_string_4 __________________________________________________ ​ self = <test_assertions.TestAssertions object at 0x109106a90> ​ def test_string_4(self): def f(): return "streaming" > assert f().startswith('S') E AssertionError: assert False E + where False = <built-in method startswith of str object at 0x1090f7bb0>('S') E + where <built-in method startswith of str object at 0x1090f7bb0> = 'streaming'.startswith E + where 'streaming' = <function TestAssertions.test_string_4.<locals>.f at 0x10914b440>() ​ tests/test_assertions.py:15: AssertionError

テスト結果が一目瞭然だと改めて感じました.
4.2アサーション関数またはインタフェースの戻り値
関数の戻り値、インタフェースの戻り値の断言は、ソフトウェア自動化テストで最も一般的なシーンであるはずです.ここでは、関数が返す値の断言を例に、
def test_function():
    def f():
        return [1, 2, 3]assert f() == [1, 2, 4]

このテスト例を実行し、出力されたエラー情報を見てください.
============================================================ FAILURES ============================================================
_________________________________________________________ test_function __________________________________________________________
​
    def test_function():
        def f():
            return [1, 2, 3]
    
>       assert f() == [1, 2, 4]
E       assert [1, 2, 3] == [1, 2, 4]
E         At index 2 diff: 3 != 4
E         Full diff:
E         - [1, 2, 4]
E         ?        ^
E         + [1, 2, 3]
E         ?        ^
​
tests/test_assertions.py:22: AssertionError
​

出力情報には関数の戻り値が含まれており、戻り値が予想される結果と一致しない要素がindexが2の要素であることがわかります.
4.3アサーションセットタイプ
断言リスト、メタグループ、辞書、コレクションなどのタイプはテストでもよく見られ、ネストされたコレクションデータに対してpytestのassertは依然としてエラーの位置を正確に表示することができる.たとえば、次のテスト・インスタンス・コードです.
class TestCollections(object):
    def test_dict(self):
        assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0}def test_dict2(self):
        assert {"a": 0, "b": {"c": 0}} == {"a": 0, "b": {"c": 2}}def test_list(self):
        assert [0, 1, 2] == [0, 1, 3]def test_list2(self):
        assert [0, 1, 2] == [0, 1, [1, 2]]def test_set(self):
        assert {0, 10, 11, 12} == {0, 20, 21}

上記のテストコードを実行すると、コア出力は次のようになります.
============================================================ FAILURES ============================================================
___________________________________________________ TestCollections.test_dict ____________________________________________________
​
self = .TestCollections object at 0x10b0d2d10>
​
    def test_dict(self):
>       assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0}
E       AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0}
E         Omitting 1 identical items, use -vv to show
E         Differing items:
E         {'b': 1} != {'b': 2}
E         Left contains 1 more item:
E         {'c': 0}
E         Right contains 1 more item:
E         {'d': 0}...
E         
E         ...Full output truncated (6 lines hidden), use '-vv' to show
​
tests/test_assertions.py:27: AssertionError
___________________________________________________ TestCollections.test_dict2 ___________________________________________________
​
self = .TestCollections object at 0x10b0d2a90>
​
    def test_dict2(self):
>       assert {"a": 0, "b": {"c": 0}} == {"a": 0, "b": {"c": 2}}
E       AssertionError: assert {'a': 0, 'b': {'c': 0}} == {'a': 0, 'b': {'c': 2}}
E         Omitting 1 identical items, use -vv to show
E         Differing items:
E         {'b': {'c': 0}} != {'b': {'c': 2}}
E         Full diff:
E         - {'a': 0, 'b': {'c': 2}}
E         ?                     ^
E         + {'a': 0, 'b': {'c': 0}}...
E         
E         ...Full output truncated (2 lines hidden), use '-vv' to show
​
tests/test_assertions.py:30: AssertionError
___________________________________________________ TestCollections.test_list ____________________________________________________
​
self = .TestCollections object at 0x10b0c1190>
​
    def test_list(self):
>       assert [0, 1, 2] == [0, 1, 3]
E       assert [0, 1, 2] == [0, 1, 3]
E         At index 2 diff: 2 != 3
E         Full diff:
E         - [0, 1, 3]
E         ?        ^
E         + [0, 1, 2]
E         ?        ^
​
tests/test_assertions.py:33: AssertionError
___________________________________________________ TestCollections.test_list2 ___________________________________________________
​
self = .TestCollections object at 0x10b0d6c10>
​
    def test_list2(self):
>       assert [0, 1, 2] == [0, 1, [1, 2]]
E       assert [0, 1, 2] == [0, 1, [1, 2]]
E         At index 2 diff: 2 != [1, 2]
E         Full diff:
E         - [0, 1, [1, 2]]
E         ?        ----  -
E         + [0, 1, 2]
​
tests/test_assertions.py:36: AssertionError
____________________________________________________ TestCollections.test_set ____________________________________________________
​
self = .TestCollections object at 0x10b0c1a50>
​
    def test_set(self):
>       assert {0, 10, 11, 12} == {0, 20, 21}
E       AssertionError: assert {0, 10, 11, 12} == {0, 20, 21}
E         Extra items in the left set:
E         10
E         11
E         12
E         Extra items in the right set:
E         20
E         21...
E         
E         ...Full output truncated (4 lines hidden), use '-vv' to show
​
tests/test_assertions.py:39: AssertionError

ネストされた辞書やリストに対しても、不一致なデータの具体的な位置が表示されます.長すぎるデータの場合、デフォルトはtruncatedで、-vvですべての情報を表示できます.
等しい断言に加えて、大きい、小さい、等しくない、in/not inなどのタイプの断言を行うこともできる.
オブジェクトの断言に対しては,オブジェクトのタイプ断言,オブジェクト自体の断言を行うことができる.ここでは一例として挙げず、アサーションがassert文であることを覚えておけば、Python言語での使い方と完全に一致した使い方をすればよい.
より多くの断言の例は、Pytestの公式ドキュメントを参照してください.https://docs.pytest.org/en/latest/example/reportingdemo.html,ここには44の断言の例があり,非常に包括的で,ほぼすべての等しい断言のシーンをカバーしている.
04-PytestアサートExcepiton
コードが正常に動作した結果の断言をサポートするほか、PytestはExceptionとWarnningを断言し、ある条件で必ず何らかの異常や警告が発生すると断定することもできます.機能テストと統合テストでは、この2つの断言はあまり使われていません.ここで簡単に紹介します.
異常な断言に対してPytestの構文は:with pytest.raises(異常タイプ)は、次の例を見ることができます.
def test_zero_division():
    with pytest.raises(ZeroDivisionError):
        1 / 0

このテストケース断言演算式1を0で割るとZeroDivisionError異常が発生する.例外タイプに対するブレークスルーに加えて、次のような例外情報をブレークスルーできます.
def test_zero_division():
    with pytest.raises(ZeroDivisionError) as excinfo:
        1 / 0
    assert 'division by zero' in str(excinfo.value)

このテスト例はexcinfoと断言した.valueの内容にはdivision by zeroという文字列が含まれており,具体的な異常情報を断言する必要がある場合に非常に有用である.
Warnningの断言に対して、実はExceptionの断言の使い方とほぼ一致しています.ここでは紹介しませんが、より多くのExceptionとWarnningの断言についてはPytestの公式ドキュメントを参考にすることができます.https://docs.pytest.org/en/latest/assert.html#assertions-about-expected-exceptions
05-アサーションにカスタム機能を追加
前の紹介を通して、Pytestのassertは完璧で、簡単ではっきりしているような気がします.しかし、実際のテスト作業では、断言時に「自動」でログを追加し、テストコードに手動でログを追加しないようにするなど、実際の問題にも遭遇します.また、断言の情報は、Allureなどのテストレポートに「自動」を統合することが望ましい(Allureレポートについては、前の記事「Pytest+AllureできれいなHTMLグラフィックステストレポートを生成する」を参照).これにより、各テストスクリプトに重複するコードを手動で書くことを回避し、テスト例の作成に多くの時間と労力を費やすことができます.
このような考えがあったら、次にどのように実現するかを見てみましょう.
PytestにはHook関数pytest_が用意されていますassertrepr_compare、この関数はテストスクリプトのassert文の実行時に呼び出されます.したがって、この関数を実装し、関数に書き込みログを追加し、allureテストレポートコードを統合することができます.
完全なコードは次のとおりです.
# content of conftest.py
def pytest_assertrepr_compare(config, op, left, right):
    left_name, right_name = inspect.stack()[7].code_context[0].lstrip().lstrip('assert').rstrip('
'
).split(op) pytest_output = assertrepr_compare(config, op, left, right) logging.debug("{0} is
{1}"
.format(left_name, left)) logging.debug("{0} is
{1}"
.format(right_name, right)) with allure.step(" "): allure.attach(str(left), left_name) allure.attach(str(right), right_name) return pytest_output

呼び出しスタック情報をinspectで取得すると、テストスクリプトのassert文のopオペレータの両方の文字列名が得られ、ログとテストレポートで使用されます.次にassertrepr_を実行しますcompareは、断言に失敗したときの出力内容であるエラー詳細を出力します.pytest_assertrepr_compare関数は変更されていません.次にdebugログ出力とallureテストレポートの内容を追加し、最後にassertのエラー情報を呼び出し先に返します.
この関数を実装すると、テストスクリプトは変更する必要はなく、assertを直接使用して断言します.しかし、ログを自動的に記録し、allureテストレポートを生成することができます.
06—Pytestのassert特性を禁止する
Pytestのassertの効果ではなく、Pythonのオリジナルのassertの効果を維持したい場合は、テストを実行するときにオプションを指定するだけです.
--assert=plain

これにより、すべての試験例におけるassertがPythonオリジナルのassert効果となり、あるモジュールのみがPythonオリジナルのassert効果を維持したい場合は、対応するモジュールのdocstringにPYTEST_を追加するDONT_REWRITE文字列でいいです.つまりpyファイルの一番上に次のようなdocstring内容を追加します.
"""
Disable rewriting for a specific module by adding the string:
PYTEST_DONT_REWRITE
"""

しかし、Pytestのassertはもっと使いやすいので、誰もそうしないと思います.
07—まとめ
本稿では,Python原生のassertとPytestにおけるassertの違いを比較し,Pytestにおけるassertの使い方を詳細に紹介し,テスト作業の実際のニーズに基づいてpytest_を通過する方法を実証した.assertrepr_compareというHook関数は,断言時にログとレポート出力を増加させる.あなたに役に立つことを願っています.
参考資料
[1] https://morioh.com/tutorials
[2] https://docs.pytest.org/en/latest/example/reportingdemo