ã¿ãªããããã«ã¡ã¯ïŒ ç§ã®ååã¯ã»ã«ã²ã€ã§ããYandexã§ã¯ãåçåãµãŒãã¹ã®èªååãã¹ãããŒã ã«æå±ããŠããŸãã ãã¹ãã®èªååã®ã¿ã¹ã¯ãæ±ãåããŒã ã®åã«ããã©ã®[ãã¬ãŒã ã¯ãŒã¯|ããŒã«]ãã¹ããæžãã¹ãã§ããïŒããšãã質åãçºçããŸãããã®æçš¿ã§ã¯ãåçã«åœ¹ç«ãŠãããšæããŸãã å
·äœçã«ã¯ãPythonã®ãã¹ãããŒã«ã«ã€ããŠèª¬æããŸãããå€ãã®ã¢ã€ãã¢ãšçµè«ã¯ä»ã®ããã°ã©ãã³ã°èšèªã«ãæ¡åŒµã§ããŸããã¢ãããŒãã¯å€ãã®å Žåãç¹å®ã®ãã¯ãããžãŒã«äŸåããªãããã§ãã

Pythonã«ã¯ãã¹ããäœæããããã®å€ãã®ããŒã«ãããããããã®éžæã¯æããã§ã¯ãããŸããã PyTestã®èå³æ·±ã䜿çšæ³ã説æãããã®[é·æ|çæ|æé»ã®æ©èœ]ã«ã€ããŠèª¬æããŸãã èšäºã§ã¯ãèªåãã¹ãã®ç°¡åã§ç解ããããã¬ããŒããäœæããã®ã«åœ¹ç«ã€
Allureã®äœ¿çšã®è©³çŽ°ãªäŸãèŠã€ããã§ãããã ãŸããäŸã§ã¯ãããã£ãŒãèšè¿°ããããã®ãã¬ãŒã ã¯ãŒã¯
-Pythonçš
Hamcrestã䜿çšãããŸãã æçµçã«ãçŸåšãã¹ãããŒã«ãæ¢ããŠãã人ããäžèšã®äŸã«åºã¥ããŠãç°å¢ã§æ©èœãã¹ããè¿
éã«å®è£
ã§ããããšãé¡ã£ãŠããŸãã æ¢ã«äœããã®ããŒã«ã䜿çšããŠãã人ã¯ãæ°ããã¢ãããŒãããŠãŒã¹ã±ãŒã¹ãæŠå¿µãåŠã¶ããšãã§ããŸãã
æŽå²çã«ãç§ãã¡ã®ãããžã§ã¯ãã«ã¯ãçžäºäœçšã®è€éãªãã¿ãŒã³ãæã€ãã¯ãããžãŒåç©åå
šäœããããŸãã åæã«ãAPIãšæ©èœã¯æé·ããŠããã ããªã®ã§ãçµ±åãã¹ããå®è£
ããå¿
èŠããããŸãã
èªååãšã³ãžãã¢ãšããŠãæé©ãªãã¹ãããã»ã¹ã確ç«ããã«ã¯ããã¹ããäœæããããã®æã䟿å©ã§æè»ãªããŒã«ãå¿
èŠã§ãã Pythonãéžã°ããã®ã¯ãç¿åŸãç°¡åã§ããã®ã³ãŒããéåžžèªã¿ããããæãéèŠãªããšãšããŠãè±å¯ãªæšæºã©ã€ãã©ãªãšå€ãã®æ¡åŒµããã±ãŒãžãåããŠããããã§ãã
æ©èœãã¹ãçšã®ããŒã«ã®ãªã¹ããèŠãåŸãããªãã¯ç¡æèã®ãã¡ã«stè¿·ã«é¥ããã©ã®ããŒã«ãéžæããããèãããã¹ãããã°ããèšè¿°ãããµããŒãã«åé¡ããªãããã«ããæ°ããåŸæ¥å¡ãç°¡åã«èšç·ŽããŠããã䜿çšããããã«ããŸãã
ãã€ãŠå®éšããæ©äŒãããããã®éžæã¯ææãªPyTestãã¬ãŒã ã¯ãŒã¯ã«ããã£ãŠããŸããã ããããããã¯ãŸã ããã»ã©æ®åããŠããããå°æ°ã®äººã
ãããã䜿çšããŸããã APIã䜿çšããã«ããã£ã¯ã¹ãã£ã䜿çšããéåžžã®Pythonã¢ãžã¥ãŒã«ã®åœ¢åŒã§ãã¹ããèšè¿°ãããšããã³ã³ã»ãããæ°ã«å
¥ããŸããã ãã®çµæãPyTestãèµ·åãã次ã®ãããªå€ãã®æ©èœãåããéåžžã«æè»ãªãœãªã¥ãŒã·ã§ã³ãã§ããŸããã
- ãã¹ãèªäœããè£å©æ©èœãåé¢ã§ãããã¹ãé¢æ°ãžã®åŒæ°åœ¢åŒã®ãã£ã¯ã¹ãã£ã
- çµã¿èŸŒã¿ã¢ãµãŒãããšã©ãŒã䟿å©ãªæ¹æ³ã§è¡šç€ºããŸãã
- pytest.mark.parametrizeã¯ãã³ãŒããè€è£œããã«ç°ãªãããŒã¿ã»ããã§ãã¹ããå®è¡ããŸãã
- ãã¹ãã«ããŒã¯ãä»ããŠãèœäžãã¹ããããŒã¯ããããé·æéå®è¡ãããŠãããã¹ãã匷調衚瀺ããŠåå¥ã«å®è¡ãããããæ©èœã
- --junit-xmlåŒæ°ã䜿çšããJUnitã¬ããŒãã®ãµããŒãã«å ããŠãç°ãªã圢åŒã®ã¬ããŒããçæããæ©èœã
ããã§ãPyTestã§ã®ãã£ã¯ã¹ãã£ããã©ã¡ãŒã¿åãããŒãã³ã°ã®ä»çµã¿ã«ã€ããŠè©³ãã説æããŸãã PyHamcrestãã¬ãŒã ã¯ãŒã¯ã䜿çšããŠãããã£ãŒãèšè¿°ããAllureã䜿çšããŠçµæã¬ããŒããäœæããæ¹æ³ã
ãã¹ããæžã
åå
åžžèã§ã¯ã
ãã£ã¯ã¹ãã£ã¯ãã¹ããå®è¡ãããã¹ã¿ã³ãã®åºå®ç¶æ
ã§ãã ããã¯ãã·ã¹ãã ãç¹å®ã®ç¶æ
ã«ããã¢ã¯ã·ã§ã³ã«ãé©çšãããŸãã
pytestãã£ã¯ã¹ãã£ã§ã¯ã
@pytest.fixture.
ãã³ã¬ãŒã¿ã§ã©ãããããé¢æ°
@pytest.fixture.
é¢æ°èªäœã¯ãå¿
èŠãªç¬éïŒãã¹ãã¯ã©ã¹ãã¢ãžã¥ãŒã«ããŸãã¯é¢æ°ã®åïŒã«å®è¡ãããè¿ãããå€ããã¹ãèªäœã§å©çšã§ããããã«ãªããŸãã åæã«ãã£ã¯ã¹ãã£ãŒã¯ä»ã®ãã£ã¯ã¹ãã£ãŒã䜿çšã§ããŸããããã«ãçŸåšã®ã»ãã·ã§ã³ãã¢ãžã¥ãŒã«ãã¯ã©ã¹ããŸãã¯é¢æ°å
ã®ç¹å®ã®ãã£ã¯ã¹ãã£ãŒã®æå¹æéã決å®ã§ããŸãã ãããã¯ããã¹ããã¢ãžã¥ãŒã«åããã®ã«åœ¹ç«ã¡ãŸãã ãŸããçµ±åããã¹ããããšãã¯ãé£æ¥ãããã¹ãã©ã€ãã©ãªããããããåå©çšããŸãã æè»æ§ãšäœ¿ããããã¯ã
pytestãéžæããããã®äž»èŠãªåºæºã®äžéšã§ããã ãã£ã¯ã¹ãã£ã䜿çšããã«ã¯ããã®ååããã¹ãã®ãã©ã¡ãŒã¿ãŒãšããŠæå®ããå¿
èŠããããŸãã
å¿
èŠãªãšãã«ååãå©ãã«ãªããŸãïŒ
- ãã¹ãããŒã¿ãçæããŸãã
- ãã¹ãã¹ã¿ã³ããæºåããŸãã
- ã¹ã¿ã³ãã®åäœãå€æŽããŸãã
setUp/tearDown
ãŸãã- ãµãŒãã¹ãã°ãŸãã¯
crashdump
åéããŸãã - ã·ã¹ãã ãšãã¥ã¬ãŒã¿ãŒãŸãã¯ã¹ã¿ãã䜿çšãã
- ãªã©ãªã©ã
ãã¹ããµãŒããŒ
次ã®äŸã§ã¯ã
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
ãã¹ããæåã«å®è¡ããããããã®æåããå®è¡ãä»ã®ãã¹ããå®è¡ããããã®æ¡ä»¶ã«ãªããŸãã
å¥ã®ã¿ã€ãã®ã©ãã«ä»ãã¯ããã¹ãã
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ãéå§ãããšã
- ãã£ã¬ã¯ããªãŸãã¯ãã¡ã€ã«ãã¹ãæãã³ãã³ãã©ã€ã³åŒæ°ã䜿çšããŠãã¹ãã®åéãéå§ããŸãã
norecursedirs
ãã©ã¡ãŒã¿ãŒã«ééãããŸã§ããã£ã¬ã¯ããªå
ãååž°çã«åç
§ãç¶ããŸããtest_*.py
äžèŽãããã¹ãŠã®ãã¡ã€ã«test_*.py
*_test.py
ãŸãã¯*_test.py
;__init__
ã¡ãœãããæããªãTest
ã§å§ãŸãååã®ã¯ã©ã¹ã- ååã
test_
ã§test_
ã¯ã©ã¹ã®é¢æ°ãŸãã¯ã¡ãœããã
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ããã³
錻ãã¹ããå®è¡ã§ããããšã«æ³šæããŠãã ããã
ãããã°

éåžžã®ã³ãŒããšåæ§ã«ããã¹ãã«ã¯ãããã°ãå¿
èŠã§ãã éåžžããã¹ãã
ERROR
ã¹ããŒã¿ã¹ã§å€±æããçç±ã
ã¹ã¿ãã¯ãã¬ãŒã¹ã§ãŸã äžæãªå Žå
ã«äœ¿çšãããŸãã
pytestã«ã¯ããã«å¯Ÿããããã€ãã®ã¢ãããŒãããããŸãã
- PyCharmã®ãã©ã°ã€ã³ã
- ãã¹ãã®ä»»æã®å Žæã«
pytest.set_trace()
èšè¿°ããpytest.set_trace()
ãæå®ããå Žæã®pdb
ã§ããã«ãã©ãŒã«ã¢ãŠãã§ããŸãã - IDEã§ç°¡åã«ãããã°ãæ§æã§ããŸãã
--pdb
䜿çšã--pdb
ãããã¯ããšã©ãŒãçºçãããšãã«ãããã¬ãŒãèµ·åããŸãã- ãŸãã¯
import pudb;pudb.set_trace()
ãçãããå Žæã®åã«import pudb;pudb.set_trace()
èŠããŠããã¹ãäž»ãªããšã¯ã -s
ãã©ã¡ãŒã¿ãŒããã¹ãèµ·åè¡ã«è¿œå ããããšã§ãïŒã
ãã¹ãã®ãããã°ã«åœ¹ç«ã€
Pytestãªãã·ã§ã³ïŒ
-k
ããçš®ã®åå¥ã®ãã¹ããå®è¡ããå¿
èŠãããå Žåã 2ã€ã®ãã¹ããå®è¡ããå ŽåããŸãã¯è¿œå ã®ãã£ã«ã¿ãŒã䜿çšããå Žåã¯ããã®ãã©ã¡ãŒã¿ãŒã®æ°ããæ§æã確èªããå¿
èŠãããããšã«æ³šæããŠãã ããã py.test -k "prepare or http and proxy" tests/functional/
;-x
æåã®ãã¹ãã倱æããå Žåã«ãã¹ããåæ¢ããå¿
èŠãããå Žåã--collect-only
ã¯ããã¹ãçšã«çæããããã©ã¡ãŒã¿ãŒã®æ£ç¢ºæ§ãšæ°ãããã³å®è¡ããããã¹ãã®ãªã¹ãã確èªããå¿
èŠãããå ŽåïŒ --collect-only
ãšåæ§ïŒã--no-magic
ã¯éæ³ãããããšã瀺åããŠããŸã:)
çµæåæ
çµæã®ã¬ããŒãã¯ãæåãããã¹ãã倱æãããã¹ãã倱æãããã¹ãã«é¢ããããŒã¿ã®ã»ããã§ãã èœã¡ããã¹ãã¯ãã·ã¹ãã ã®ç¶æ
ãã·ã¹ãã ããã®ãããªçµæã«å°ãã¹ãããããã¹ããèœã¡ããã©ã¡ãŒã¿ãŒãããã³ãããã®ãã©ã¡ãŒã¿ãŒã䜿çšãããšãã«ã·ã¹ãã ã«æåŸ
ãããããšãèšè¿°ããå¿
èŠããããŸãã : , , , , :
smoke functional , . , .
pytest JUnit ,
Jenkins-CI . ,
Allure , .
pytest :
- PASSED â , , ;
- FAILED â , ,
assert
; - ERROR â , ., ;
- SKIPPED â ,
depends
pytest.mark.skip[if]
; - xfail â ,
assert
; - XPASS â ,
xfail
. , , , .
Allure PyTest ,
Allure PyTest . , :
- Step' ;
- description docstring;
- attachment', ;
- .
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 javascript .
, , :
- PyTest-localserver , .
- PyTest-timeout , .
- PyTest-xdist â musthave, .
- PyTest-capturelog ,
logging
. - PyTester , . pytest .
- PyTest-httpretty
uri
, . pytest-localserver
- PyTest-ordering â . - .
- PyTest-incremental , , , , . Jenkins.
,
PyTest .
.