Yandexが機胜テストにPyTestおよびその他のフレヌムワヌクを䜿甚する方法

みなさんこんにちは 私の名前はセルゲむです。Yandexでは、収益化サヌビスの自動化テストチヌムに所属しおいたす。 テストの自動化のタスクを扱う各チヌムの前に、「どの[フレヌムワヌク|ツヌル]テストを曞くべきですか」ずいう質問が発生したす。この投皿では、回答に圹立おたいず思いたす。 具䜓的には、Pythonのテストツヌルに぀いお説明したすが、倚くのアむデアず結論は他のプログラミング蚀語にも拡匵できたす。アプロヌチは倚くの堎合、特定のテクノロゞヌに䟝存しないためです。



Pythonにはテストを䜜成するための倚くのツヌルがあり、それらの遞択は明らかではありたせん。 PyTestの興味深い䜿甚法を説明し、その[長所|短所|暗黙の機胜]に぀いお説明したす。 蚘事では、自動テストの簡単で理解しやすいレポヌトを䜜成するのに圹立぀Allureの䜿甚の詳现な䟋を芋぀けるでしょう。 たた、䟋ではマッチャヌを蚘述するためのフレヌムワヌク-Python甹Hamcrestが䜿甚されたす。 最終的に、珟圚テストツヌルを探しおいる人が、䞊蚘の䟋に基づいお、環境で機胜テストを迅速に実装できるこずを願っおいたす。 既に䜕らかのツヌルを䜿甚しおいる人は、新しいアプロヌチ、ナヌスケヌス、抂念を孊ぶこずができたす。

歎史的に、私たちのプロゞェクトには、盞互䜜甚の耇雑なパタヌンを持぀テクノロゞヌ動物園党䜓がありたす。 同時に、APIず機胜は成長しおいるだけなので、統合テストを実装する必芁がありたす。

自動化゚ンゞニアずしお、最適なテストプロセスを確立するには、テストを䜜成するための最も䟿利で柔軟なツヌルが必芁です。 Pythonが遞ばれたのは、習埗が簡単で、そのコヌドが通垞読みやすく、最も重芁なこずずしお、豊富な暙準ラむブラリず倚くの拡匵パッケヌゞを備えおいるためです。

機胜テスト甚のツヌルのリストを芋た埌、あなたは無意識のうちにst迷に陥り、どのツヌルを遞択するかを考え、テストをすばやく蚘述し、サポヌトに問題がないようにし、新しい埓業員を簡単に蚓緎しおそれを䜿甚するようにしたす。

か぀お実隓する機䌚があり、その遞択は有望なPyTestフレヌムワヌクにかかっおいたした。 それからそれはただそれほど普及しおおらず、少数の人々がそれを䜿甚したした。 APIを䜿甚せずに、フィクスチャを䜿甚し、通垞のPythonモゞュヌルの圢匏でテストを蚘述するずいうコンセプトが気に入りたした。 その結果、PyTestが起動し、次のような倚くの機胜を備えた非垞に柔軟な゜リュヌションができたした。


ここで、PyTestでのフィクスチャ、パラメヌタ化、マヌキングの仕組みに぀いお詳しく説明したす。 PyHamcrestフレヌムワヌクを䜿甚しおマッチャヌを蚘述し、Allureを䜿甚しお結果レポヌトを䜜成する方法。

テストを曞く


備品


垞識では、 フィクスチャはテストが実行されるスタンドの固定状態です。 これは、システムを特定の状態にするアクションにも適甚されたす。

pytestフィクスチャでは、 @pytest.fixture.デコレヌタでラップされた関数@pytest.fixture. 関数自䜓は、必芁な瞬間テストクラス、モゞュヌル、たたは関数の前に実行され、返された倀がテスト自䜓で利甚できるようになりたす。 同時にフィクスチャヌは他のフィクスチャヌを䜿甚できたす。さらに、珟圚のセッション、モゞュヌル、クラス、たたは関数内の特定のフィクスチャヌの有効期間を決定できたす。 これらは、テストをモゞュヌル化するのに圹立ちたす。 たた、統合をテストするずきは、隣接するテストラむブラリからそれらを再利甚したす。 柔軟性ず䜿いやすさは、 pytestを遞択するための䞻芁な基準の䞀郚でした。 フィクスチャを䜿甚するには、その名前をテストのパラメヌタヌずしお指定する必芁がありたす。

必芁なずきに備品が助けになりたす

テストサヌバヌ


次の䟋では、 FlaskでWebサヌバヌの機胜をテストし、ポヌト8081で接続を埅機し、 GET芁求を受信するテストに぀いお説明したす。 サヌバヌはtextパラメヌタヌから1行を取埗し、応答で各単語をその反転単語に倉曎したす。 クラむアントがそれを受け入れる方法を知っおいる堎合、Jsonが返されたす。

 import json from flask import Flask, request, make_response as response app = Flask(__name__) @app.route("/") def index(): text = request.args.get('text') json_type = 'application/json' json_accepted = json_type in request.headers.get('Accept', '') if text: words = text.split() reversed_words = [word[::-1] for word in words] if json_accepted: res = response(json.dumps({'text': reversed_words}), 200) else: res = response(' '.join(reversed_words), 200) else: res = response('text not found', 501) res.headers['Content-Type'] = json_type if json_accepted else 'text/plain' return res if __name__ == "__main__": app.run(host='0.0.0.0', port=8081) 


テスト察象のサヌバヌのテストを䜜成し、指定されたポヌトでの存圚を確認したす。 サヌバヌが存圚するこずを確認しおください。 これを行うには、゜ケットモゞュヌルを䜿甚したす。 テスト埌に゜ケットを準備しお閉じるフィクスチャを䜜成したす。

 import pytest import socket as s @pytest.fixture def socket(request): _socket = s.socket(s.AF_INET, s.SOCK_STREAM) def socket_teardown(): _socket.close() request.addfinalizer(socket_teardown) return _socket def test_server_connect(socket): socket.connect(('localhost', 8081)) assert socket 


ただし、 setUP/tearDownを実装しおオブゞェクトを返すコンテキストマネヌゞャずしおフィクスチャを衚す新しいyield_fixtureデコレヌタを䜿甚するこずをおsetUP/tearDownしたす。

 @pytest.yield_fixture def socket(): _socket = s.socket(s.AF_INET, s.SOCK_STREAM) yield _socket _socket.close() 


yield_fixtureを䜿甚するず、より簡朔でyield_fixture芋えたす。 フィクスチャのデフォルトの有効期間はscope=functionこずに泚意しおください。 これは、各テストをそのパラメヌタヌで実行するず、フィクスチャヌの新しいむンスタンスが発生するこずを意味したす。

テスト甚のServerフィクスチャを䜜成し、テストするWebサヌバヌの堎所を蚘述したす。 静的情報を保存するオブゞェクトを返すので、毎回生成する必芁はないため、 scope=module蚭定しscope=module 。 このフィクスチャが生成する結果はキャッシュされ、珟圚のモゞュヌルが起動するたびに存圚したす

 @pytest.fixture(scope='module') def Server(): class Dummy: host_port = 'localhost', 8081 uri = 'http://%s:%s/' % host_port return Dummy def test_server_connect(socket, Server): socket.connect(Server.host_port) assert socket 


scope=sessionおよびscope=class class-フィクスチャの有効期間もありたす。 たた、 scope=倀の䜎いフィクスチャレベルの高いフィクスチャを䜿甚するこずはできたせん。

autouse䜿甚autouseチャの䜿甚に泚意しおautouse 。 それらはデヌタを感知できないほど倉曎する可胜性があるため、危険です。 それらを柔軟に䜿甚するには、呌び出されたテストに必芁なフィクスチャを確認できたす。

 @pytest.yield_fixture(scope='function', autouse=True) def collect_logs(request): if 'Server' in request.fixturenames: with some_logfile_collector(SERVER_LOCATION): yield else: yield 


ずりわけ、フィクスチャはテストクラスを指すこずができたす。 次の䟋には、テストベンチでテストが時間を倉曎するクラスがありたす。 たずえば、各テストの埌に、時間が珟圚に曎新される必芁がありたす。 次の䟋では、 Serviceフィクスチャはテスト䞭のサヌビスのオブゞェクトを返し、日付ず時刻を倉曎できるset_timeメ゜ッドを持っおいたす。

 @pytest.yield_fixture def reset_shifted_time(Service): yield Service.set_time(datetime.datetime.now()) @pytest.mark.usefixtures("reset_shifted_time") class TestWithShiftedTime(): def test_shift_milesecond(self, Service): Service.set_time() assert ... def test_shift_time_far_far_away(self, Service): Service.set_time() assert ... 


通垞、状況に固有の小さな噚具はテストモゞュヌル内に蚘述されたす。 しかし、フィクスチャが倚くのテストスむヌトで䞀般的になるず、通垞はpytestの特別なファむルconftest.pyに移動されたす。 このファむルにフィクスチャが蚘述されるず、すべおのテストでフィクスチャが衚瀺されるようになり、 importする必芁はありたせん。

仲人


明らかに、チェックなしのテストは誰にも必芁ありたせん。 pytestを䜿甚するずき、 assertなどのマッチャヌを䜿甚しお、最も簡単な方法でチェックを行うこずができたす。 Assertは、蚘述されおいるステヌトメントを怜蚌する暙準のPythonステヌトメントです。 「1぀のテストでassert぀のassert 」ずいうルヌルを順守したす。 デヌタの準備やサヌビスを目的の状態にする手順に圱響を䞎えるこずなく、特定の機胜をテストできたす。 テストで゚ラヌを匕き起こす可胜性のあるデヌタを準備する手順を䜿甚する堎合、別のテストを䜜成するこずをお勧めしたす。 この構造を䜿甚しお、システムの予想される動䜜を説明したす。

゚ラヌが怜出された堎合、テストで人間が読める起動レポヌトが必芁です。 そしお最近では、 pytestは非垞に有益assertをサポヌトし始めたした。 もっず耇雑なものが必芁になるたで、それらを䜿甚するこずをお勧めしたす。

たずえば、次のテスト
 def test_dict(): assert dict(foo='bar', baz=None).items() == list({'foo': 'bar'}.iteritems()) 

゚ラヌの堎所に関する詳现な回答を返したす。
 E assert [('foo', 'bar...('baz', None)] == [('foo', 'bar')] E Left contains more items, first extra item: ('baz', None) 


Flaskでテストサヌバヌをテストするテストでは、 test_server_connectメ゜ッド内のテストを曞き換えお、特定のexception予期されおいないこずをより正確に刀断しexception 。 これを行うには、 PyHamcrestフレヌムワヌクを䜿甚したす 。
 from hamcrest import * SOCKET_ERROR = s.error def test_server_connect(socket, Server): assert_that(calling(socket.connect).with_args(Server.host_port), is_not(raises(SOCKET_ERROR))) 


PyHamcrestでは、組み蟌みのマッチャヌを組み合わせるこずができたす。 has_propertyずcontains_string,をこのようcontains_string,組み合わせるhas_property contains_string,䜿いやすい単玔なマッチャヌが埗られたす。

 def has_content(item): return has_property('text', item if isinstance(item, BaseMatcher) else contains_string(item)) def has_status(status): return has_property('status_code', equal_to(status)) 


次に、チェックされた倀を倉曎し、次に指定された䞀臎に枡すマッチャヌを䜜成する必芁がありたす。 これを行うために、クラス属性に基づいおそのようなマッチャヌを圢成するBaseModifyMatcherクラスをdescriptionたす descriptionマッチの説明、 BaseModifyMatcherチェックされた倀のfunction-modifier、 instance修食子に期埅されるクラスのタむプ

 from hamcrest.core.base_matcher import BaseMatcher class BaseModifyMatcher(BaseMatcher): def __init__(self, item_matcher): self.item_matcher = item_matcher def _matches(self, item): if isinstance(item, self.instance) and item: self.new_item = self.modify(item) return self.item_matcher.matches(self.new_item) else: return False def describe_mismatch(self, item, mismatch_description): if isinstance(item, self.instance) and item: self.item_matcher.describe_mismatch(self.new_item, mismatch_description) else: mismatch_description.append_text('not %s, was: ' % self.instance) \ .append_text(repr(item)) def describe_to(self, description): description.append_text(self.description) \ .append_text(' ') \ .append_description_of(self.item_matcher) 


テスト察象のサヌバヌは、 textパラメヌタヌで枡された反転ワヌドから応答を生成するこずがわかっおいたす。 BaseModifyMatcherを䜿甚しお、通垞の単語のリストを受け取り、それに応じお反転した単語の行を期埅するゲヌマヌを䜜成したす。
 rom hamcrest.core.helpers.wrap_matcher import wrap_matcher reverse_words = lambda words: [word[::-1] for word in words] def contains_reversed_words(item_match): """ Example: >>> from hamcrest import * >>> contains_reversed_words(contains_inanyorder('oof', 'rab')).matches("foo bar") True """ class IsStringOfReversedWords(BaseModifyMatcher): description = 'string of reversed words' modify = lambda _, item: reverse_words(item.split()) instance = basestring return IsStringOfReversedWords(wrap_matcher(item_match)) 


BaseModifyMatcherを䜿甚する次のプレヌダヌは、json行をチェックしたす。

 import json as j def is_json(item_match): """ Example: >>> from hamcrest import * >>> is_json(has_entries('foo', contains('bar'))).matches('{"foo": ["bar"]}') True """ class AsJson(BaseModifyMatcher): description = 'json with' modify = lambda _, item: j.loads(item) instance = basestring return AsJson(wrap_matcher(item_match)) 


テスト䞭のFlask サヌバヌをテストするテストに、さらに2぀のテストを远加したす。これらのテストは、さたざたなリク゚ストに応じおサヌバヌが生成するものをチェックしたす。 これを行うには、䞊蚘のhas_status 、 has_contentおよびcontains_reversed_words has_contentを䜿甚したす。

 def test_server_response(Server): assert_that(requests.get(Server.uri), all_of(has_content('text not found'), has_status(501))) def test_server_request(Server): text = 'Hello word!' assert_that(requests.get(Server.uri, params={'text': text}), all_of( has_content(contains_reversed_words(text.split())), has_status(200) )) 

Hamcrestに぀いおは、 Habréで読むこずができたす。 should-dslにただ泚意を払う䟡倀がありたす。

パラメヌタ化



倚くの異なるパラメヌタヌを䜿甚しお同じテストを実行する堎合、テストデヌタのパラメヌタヌ化が圹立ちたす。 パラメヌタヌ化の助けを借りお、テストで繰り返しコヌドが䜿甚されたす。 オプションのリストを芖芚的に匷調するず、読みやすさが向䞊し、サポヌトが匷化されたす。 フィクスチャは、システムの説明、準備、たたは目的の状態ぞの移行を行いたす。 パラメヌタ化は、テストケヌスを蚘述するさたざたなテストパラメヌタのセットを圢成するために䜿甚されたす。

PyTestでは、特別な@pytest.mark.parametrizeデコレヌタヌを䜿甚しおテストをパラメヌタヌ化する必芁がありたす。 1぀のパラメヌタヌ化で耇数のパラメヌタヌを指定できたす。 パラメヌタヌが耇数のパラメヌタヌ化に分割されおいる堎合、それらは乗算されたす。

テスト内に静的デヌタを保持するこずは良い習慣ではありたせん。 Flaskでテストサヌバヌをチェックするサンプルテストでは、 test_server_requestメ゜ッドをパラメヌタヌ化しお、 textパラメヌタヌのオプションを説明する䟡倀がありたす。

 @pytest.mark.parametrize('text', ['Hello word!', ' 440 005 ', 'one_word']) def test_server_request(text, Server): assert_that(requests.get(Server.uri, params={'text': text}), all_of( has_content(contains_reversed_words(text.split())), has_status(200) )) 

クラむアントがサポヌトしおいる堎合、 json応答を確認するのを忘れおいたした。 通垞のパラメヌタヌの代わりにオブゞェクトを䜿甚しおテストを曞き換えたす。 䞀臎は、応答のタむプによっお異なりたす。 詊合にはもっずわかりやすい名前を付けるこずをお勧めしたす。

 class DefaultCase: def __init__(self, text): self.req = dict( params={'text': text}, headers={}, ) self.match_string_of_reversed_words = all_of( has_content(contains_reversed_words(text.split())), has_status(200), ) class JSONCase(DefaultCase): def __init__(self, text): DefaultCase.__init__(self, text) self.req['headers'].update({'Accept': 'application/json'}) self.match_string_of_reversed_words = all_of( has_content(is_json(has_entries('text', contains(*reverse_words(text.split()))))), has_status(200), ) @pytest.mark.parametrize('case', [testclazz(text) for text in 'Hello word!', ' 440 005 ', 'one_word' for testclazz in JSONCase, DefaultCase]) def test_server_request(case, Server): assert_that(requests.get(Server.uri, **case.req), case.match_string_of_reversed_words) 


py.test -v test_server.pyを䜿甚しおこのようなパラメヌタヌ化されたテストを実行するず、レポヌトが取埗されたす。

 $ py.test -v test_server.py ============================= test session starts ============================= platform linux2 -- Python 2.7.3 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python plugins: timeout, allure-adaptor collected 8 items test_server.py:26: test_server_connect PASSED test_server.py:89: test_server_response PASSED test_server.py:109: test_server_request[case0] PASSED test_server.py:109: test_server_request[case1] PASSED test_server.py:109: test_server_request[case2] PASSED test_server.py:109: test_server_request[case3] PASSED test_server.py:109: test_server_request[case4] PASSED test_server.py:109: test_server_request[case5] PASSED ========================== 8 passed in 0.11 seconds =========================== 


このようなレポヌトでは、特定の起動でどのパラメヌタヌが䜿甚されたかは完党に理解できたせん。

出力をより明確にするために、 Caseクラスに__repr__メ゜ッドを実装し、 idparametrize補助デコレヌタを蚘述する必芁がありたす。このCase 、 idparametrizeデコレヌタのids=远加パラメヌタを䜿甚したす。

 def idparametrize(name, values, fixture=False): return pytest.mark.parametrize(name, values, ids=map(repr, values), indirect=fixture) class DefaultCase: def __init__(self, text): self.text = text self.req = dict( params={'text': self.text}, headers={}, ) self.match_string_of_reversed_words = all_of( has_content(contains_reversed_words(self.text.split())), has_status(200), ) def __repr__(self): return 'text="{text}", {cls}'.format(cls=self.__class__.__name__, text=self.text) class JSONCase(DefaultCase): def __init__(self, text): DefaultCase.__init__(self, text) self.req['headers'].update({'Accept': 'application/json'}) self.match_string_of_reversed_words = all_of( has_content(is_json(has_entries('text', contains(*reverse_words(text.split()))))), has_status(200), ) @idparametrize('case', [testclazz(text) for text in 'Hello word!', ' 440 005 ', 'one_word' for testclazz in JSONCase, DefaultCase]) def test_server_request(case, Server): assert_that(requests.get(Server.uri, **case.req), case.match_string_of_reversed_words) 


 $ py.test -v test_server.py ============================= test session starts ============================= platform linux2 -- Python 2.7.3 -- py-1.4.20 -- pytest-2.5.2 -- /usr/bin/python plugins: ordering, timeout, allure-adaptor, qabs-yadt collected 8 items test_server.py:26: test_server_connect PASSED test_server.py:89: test_server_response PASSED test_server.py:117: test_server_request[text="Hello word!", JSONCase] PASSED test_server.py:117: test_server_request[text="Hello word!", DefaultCase] PASSED test_server.py:117: test_server_request[text=" 440 005 ", JSONCase] PASSED test_server.py:117: test_server_request[text=" 440 005 ", DefaultCase] PASSED test_server.py:117: test_server_request[text="one_word", JSONCase] PASSED test_server.py:117: test_server_request[text="one_word", DefaultCase] PASSED ========================== 8 passed in 0.12 seconds =========================== 


idparametrizeデコレヌタidparametrizeを芋お、 fixtureパラメヌタに泚意を払うず、 fixtureパラメヌタ化できるこずがわかりたす。 次の䟋では、実IPずロヌカルIPの䞡方でサヌバヌが正しく応答するこずを確認したす。 これを行うには、 Serverフィクスチャを少し調敎しお、パラメヌタヌを取埗できるようにする必芁がありたす。

 from collections import namedtuple Srv = namedtuple('Server', 'host port') REAL_IP = s.gethostbyname(s.gethostname()) @pytest.fixture def Server(request): class Dummy: def __init__(self, srv): self.srv = srv @property def uri(self): return 'http://{host}:{port}/'.format(**self.srv._asdict()) return Dummy(request.param) @idparametrize('Server', [Srv('localhost', 8081), Srv(REAL_IP, 8081)], fixture=True) @idparametrize('case', [Case('Hello word!'), Case('Hello word!', json=True)]) def test_server_request(case, Server): assert_that(requests.get(Server.uri, **case.req), case.match_string_of_reversed_words) 


マヌキング



マヌキングを䜿甚するず、゚ラヌの原因ずしおテストをマヌクしたり、テストをスキップしたり、 ナヌザヌ定矩のラベルを远加したりできたす 。 これはすべお、必芁なテスト、ケヌス、たたはパラメヌタヌをグルヌプ化たたはマヌクするためのメタデヌタです。 グルヌプ化の堎合、倚かれ少なかれ重芁なテストがあるため、この機胜を䜿甚しおテストずクラスに重倧床を瀺したす。

pytestでは 、特別な@pytest.mark.MARK_NAMEデコレヌタヌを䜿甚しお、テストずテストパラメヌタヌをマヌクできたす。 たずえば、各テストパックには数分以䞊かかる堎合がありたす。 したがっお、最初に重芁なテストを実行しおから、残りを実行したいず思いたす。

 @pytest.mark.acceptance def test_server_connect(socket, Server): assert_that(calling(socket.connect).with_args(Server.host_port), is_not(raises(SOCKET_ERROR))) @pytest.mark.acceptance def test_server_response(Server): assert_that(requests.get(Server.uri), all_of(has_content('text not found'), has_status(501))) @pytest.mark.P1 def test_server_404(Server): assert_that(requests.get(Server.uri + 'not_found'), has_status(404)) @pytest.mark.P2 def test_server_simple_request(Server, SlowConnection): with SlowConnection(drop_packets=0.3): assert_that(requests.get(Server.uri + '?text=asdf'), has_content('fdsa')) 


このようなマヌキングのあるテストは、CIで䜿甚できたす。 たずえば、Jenkinsでは、 multi-configuration project䜜成できたす。 このタスクの[ Configuration Matrixセクションで、 User-defined Axisを['acceptance', 'P1', 'P2', 'other']を含むTESTPACKずしお定矩しUser-defined Axis 。 このタスクはテストを順番に実行し、 acceptanceテストが最初に実行され、それらの成功した実行が他のテストを実行するための条件になりたす。

 #!/bin/bash PYTEST="py.test $WORKSPACE/tests/functional/ $TEST_PARAMS --junitxml=report.xml --alluredir=reports" if [ "$TESTPACK" = "other" ] then $PYTEST -m "not acceptance and not P1 and not P2" || true else $PYTEST -m $TESTPACK || true fi 

別のタむプのラベル付けは、テストをxfailずしおマヌクするこずxfail 。 テスト党䜓をマヌクするこずに加えお、テストパラメヌタヌをマヌクできたす。 そのため、次の䟋では、ipv6にアドレスhost='::1',指定するず、サヌバヌは応答したせん。 この問題を解決するには、サヌバヌコヌドで0.0.0.0代わりに::を䜿甚したす。 テストがこの状況にどのように反応するかを確認するために、ただ修正したせん。 さらに、オプションのreasonパラメヌタヌで理由を説明できたす。 このテキストは、起動レポヌトに衚瀺されたす。

 @pytest.yield_fixture def Server(request): class Dummy: def __init__(self, srv): self.srv = srv self.conn = None @property def uri(self): return 'http://{host}:{port}/'.format(**self.srv._asdict()) def connect(self): self.conn = s.create_connection((self.srv.host, self.srv.port)) self.conn.sendall('HEAD /404 HTTP/1.0\r\n\r\n') self.conn.recv(1024) def close(self): if self.conn: self.conn.close() res = Dummy(request.param) yield res res.close() SERVER_CASES = [ pytest.mark.xfail(Srv('::1', 8081), reason='ipv6 desn`t work, use `::` instead of `0.0.0.0`'), Srv(REAL_IP, 8081), ] @idparametrize('Server', SERVER_CASES, fixture=True) def test_server(Server): assert_that(calling(Server.connect), is_not(raises(SOCKET_ERROR))) 

テストずパラメヌタヌは、タグpytest.mark.skipif()でマヌクできたす。 特定の条件を䜿甚しおこれらのテストをスキップできたす。

実行ずデバッグ


打ち䞊げ


テストを実行するには倚くの方法がありたす。 py.testコマンドを䜿甚するか、 python -m pytestずしおpython -m pytest 。

pytestを開始するずき

1぀の堎所でテストを実行するためのすべおのパラメヌタヌを保存できる特別なtoxテストランナヌに぀いお蚀及したいず思いたす。 これを行うには、テストずずもにルヌトフォルダヌにtox.iniを曞き蟌みたす。

 [tox] envlist=py27 [testenv] deps= builders pytest pytest-allure-adaptor PyHamcrest commands= py.test tests/functional/ \ --junitxml=report.xml \ --alluredir=reports \ --verbose \ {posargs} 


そしお、1぀のコマンドでテストがtox  tox 。 圌は.toxフォルダヌにvirtualenvを䜜成し、 virtualenvにテストを実行するために必芁な䟝存関係を.tox 、最終的にconfig ファむルで指定されたパラメヌタヌでpytestを実行したす 。

あるいは、テストをpython甚のモゞュヌルずしおフォヌマットする堎合、 python setup.py test実行できたす。 これを行うには、 ドキュメントに埓っおsetup.pyを調敎する必芁がありたす 。

アリュヌルに関する䞊蚘の䟋のように、docstringを指定するこずにより、 pytestを䜿甚しおドキュメントを確認できたす。 幞い、 pytestにはdoctest、pep8、 unittest 、noseのpy.test --pep8 --doctest-modules -v --junit-xml report.xml self_tests/ ft_lib/たす py.test --pep8 --doctest-modules -v --junit-xml report.xml self_tests/ ft_lib/

さらに、 pytestはUnitTestおよび錻テストを実行できるこずに泚意しおください。

デバッグ


pudb

通垞のコヌドず同様に、テストにはデバッグが必芁です。 通垞、テストがERRORステヌタスで倱敗した理由がスタックトレヌスでただ䞍明な堎合に䜿甚されたす。 pytestにはこれに察するいく぀かのアプロヌチがありたす。


テストのデバッグに圹立぀Pytestオプション


結果分析


結果のレポヌトは、成功したテスト、倱敗したテスト、倱敗したテストに関するデヌタのセットです。 萜ちたテストは、システムの状態、システムをそのような結果に導くステップ、テストが萜ちたパラメヌタヌ、およびこれらのパラメヌタヌを䜿甚するずきにシステムに期埅されるこずを蚘述する必芁がありたす。 : , , , , : smoke functional , . , .

pytest JUnit , Jenkins-CI . , Allure , .

pytest :


Allure PyTest , Allure PyTest . , :


Step' , , . , . -.

Attachment' — , . , -, . Step'. type . attachemnt' : txt , html , xml , png , jpg , json .

error_if_wat , , ERROR_CONDITION . Server . allure.step . socket . requests . allure.attach . docstring , , .

 import allure ERROR_CONDITION = None @pytest.fixture def error_if_wat(request): assert request.getfuncargvalue('Server').srv != ERROR_CONDITION SERVER_CASES = [ pytest.mark.xfail(Srv('::1', 8081), reason='ipv6 desn`t work, use `::` instead of `0.0.0.0`'), Srv('127.0.0.1', 8081), Srv('localhost', 80), ERROR_CONDITION, ] @idparametrize('Server', SERVER_CASES, fixture=True) def test_server(Server, error_if_wat): assert_that(calling(Server.connect), is_not(raises(SOCKET_ERROR))) """ Step 1: Try connect to host, port, and check for not raises SOCKET_ERROR. Step 2: Check for server response 'text not found' message. Response status should be equal to 501. """ with allure.step('Try connect'): assert_that(calling(Server.connect), is_not(raises(SOCKET_ERROR))) with allure.step('Check response'): response = requests.get(Server.uri) allure.attach('response_body', response.text, type='html') allure.attach('response_headers', j.dumps(dict(response.headers), indent=4), type='json') allure.attach('response_status', str(response.status_code)) assert_that(response, all_of(has_content('text not found'), has_status(501))) 


py.test --alluredir=/var/tmp/allure/ test_server.py .

, , , Ubuntu:

 sudo add-apt-repository ppa:yandex-qatools/allure-framework sudo apt-get install yandex-allure-cli allure generate -o /var/tmp/allure/output/ -- /var/tmp/allure/ 

/var/tmp/allure/output . index.html .

allure passed with headers allure passed with headers

, , allure javascript .

, , :



, PyTest . .

Source: https://habr.com/ru/post/J242795/


All Articles