Upgrade to Pro — share decks privately, control downloads, hide ads and more …

파이썬에서 편하게 테스트 케이스 작성하기: pytest, Travis CI, Docker

파이썬에서 편하게 테스트 케이스 작성하기: pytest, Travis CI, Docker

Python에는 unittest라는 단위 테스트를 위한 모듈이 포함되어 있다. unittest 모듈은 단위 테스트 작성에 필요한 다양한 decorator와 assertion 메소드, mocking 방법 등을 제공한다.

단위 테스트는 기본적으로 다른 테스트와 독립적으로 돌아가야 한다. 하지만 테스트 규모가 커질수록 서로 다른 테스트에서 공통되는 테스트 코드나 테스트 자원을 사용하게 되는 경우가 많아지는 것도 사실이다. 테스트의 독립성을 지키면서 중복 자원 재사용을 최대화하는 문제는 unittest 모듈만을 이용해서 다루기에는 어려운(또는 지저분한) 측면이 있다.

pytest는 더 나은 테스트 케이스 작성을 도와주는 파이썬 테스트 도구이다. pytest의 장점은 크게 두 가지 측면에서 찾을 수 있다. fixture 모듈과 플러그인을 통한 확장성이다. pytest는 중복되는 코드 또는 자원을 fixture라는 함수로 빼내 모듈화한다. 어떤 테스트 함수에서 필요한 fixture를 사용하기 위해서는 테스트 함수의 인자에 fixture 이름을 써주기만 하면 된다. 즉 fixture로 묶인 자원을 dependency injection 형태로 테스트 함수에 끼워 넣는 식이 되는데, 각 테스트 케이스에서는 필요한 자원만 선택적으로 골라 사용할 수 있다. 이를 통해 테스트의 독립성을 유지하면서도 테스트 자원의 재사용률을 쉽게 올릴 수 있다. 또한, pytest는 확장성이 아주 좋아서 다양한 플러그인을 제공한다. 예를 들어, pytest-django 플러그인은 Django의 서버 테스트를 위한 db 및 서버 관련 자원을 fixture 형태로 제공하는데, 이를 이용하면 더욱 간단하게 Django 테스트 케이스를 작성할 수 있다. 비동기 코드의 테스트 케이스를 작성하는 일은 특히 까다로운 편인데, 이 경우에도 각종 관련 pytest 플러그인을 사용하면 좀 더 편한 테스트를 작성할 수 있다.

이번 발표에서는 기본 unittest 대비 pytest의 장점에 대해 이야기 하고자 한다. 이에 더해 작성한 테스트를 자동화해서 실행할 수 있는 Travis CI에 대해 간략하게 설명하고, 도커 컨테이너화한 테스트 케이스 실행 환경을 Travis CI를 통해 자동화해본 개인적인 경험에 대해서도 짧게 소개하고자 한다.

이 발표는 pytest라는 파이썬 외부 패키지에 대해 다루므로, 파이썬 테스트 케이스 작성 경험이 있는 청중을 대상으로 한다. 뒷부분에 Travis CI와 도커관련 내용이 들어가는데, 도커 이미지와 컨테이너에 대한 기본적인 지식이 있으면 좀 더 쉽게 이해할 수 있을 것으로 생각한다 (필수는 아님). 이미 테스트와 빌드 자동화에 대한 경험이 많은 개발자는 대상이 아니다. 이 발표를 통해 테스트 케이스를 작성해보기는 했으나 더이상 무엇을 어떻게 해야할지 고민하고 있는 초보 테스터들에게 좀 더 강력하고 편한 테스트 개발 환경을 소개할 수 있기를 기대한다.

Jonghyun Park

August 09, 2017
Tweet

More Decks by Jonghyun Park

Other Decks in Programming

Transcript

  1. ౵੉ॆীࢲ ಞೞѱ పझ౟
    ா੉झ ੘ࢿೞӝ
    pytest, Travis CI, Docker
    ߅ઙഅ, (઱)ې࠶স

    View Slide

  2. ߊ಴੗
    • (઱) ې࠶স (~2.2֙)
    • HTML, CSS, JavaScript, Polymer, Django, Go
    • ನ೦ Ѣ઱ (ਗѺӔޖ)
    • Ӕ୊ ࢎदח ࠙ झఠ٣ ݽ੐ э਷ Ѫ ജ৔ ƕƕ

    View Slide

  3. ݈ೞ۰Ҋ ೞח Ѫ
    • పझ౟ ੘ࢿਸ ಞೞѱ: pytest
    • పझ౟ प೯ਸ ಞೞѱ: Travis CI
    • ౵੉ॆ unittest ݽٕ
    • unittest ؀࠺ pytest੄ ੢੼
    • Travis CIܳ ాೠ పझ౟ ੗زച
    • docker containerܳ ాೠ పझ౟ प೯

    View Slide

  4. ୒઺
    • ౵੉ॆਵ۽ పझ౟ா੉झ ੘ࢿਸ ೧ࠄ ҃೷੉ যו ੿ب ੓ח ࠙
    • pytest ੜ ॳҊ ҅दݶ Ҷ੉ উ ٜਵ࣊ب…
    • ੉޷ పझ౟ ੜ ೞदח ࠙਷ ׮ܲ ࣁ࣌ਸ…

    View Slide

  5. పझ౟
    పझ౟ ೤द׮

    View Slide

  6. పझ౟ ா੉झ
    • ௏٘о ਗೞח ؀۽ ز੘ೞח૑ పझ౟ೞח ௏٘
    • ա ؀न ௏٘ܳ పझ౟೧ ઴ ޖ঱оо ೙ਃ
    • ঱ઁө૑ա ࣻز పझ౟ܳ ೡ ࣻח হ਺
    • ܻಂష݂ೡ ٸ ୭ࣗೠ੄ ߡ౱ݾ
    def add_one(n):
    return n + 1
    def test_add_one():
    assert add_one(1) == 2
    assert add_one(8) == 9
    ௏٘ పझ౟ ா੉झ

    View Slide

  7. పझ౟ܳ ೤द׮
    • పझ౟ח ѐߊ੄ ೙ࣻ੸ੋ җ੿
    • పझ౟ হ੉ Ҋಿ૕ ௏٘ܳ য়ۖزউ ਬ૑ೞӝ য۰਑
    • (ޛۿ పझ౟о ੓׮Ҋ ௏٘੄ ૕੉ ࠁ੢غ૑ח ঋ਺)
    • ੗ӝо ੘ࢿೠ ௏٘ী ؀ೠ ୭ࣗೠ੄ పझ౟ۄب
    • ҳӖ੄ ҃਋
    • Software engineer (SWE)
    • Software engineer in test (SET)
    • Test engineer (TE)

    View Slide

  8. Test-Driven Development (TDD)
    • ௏٘о оઉঠ ೡ झಖਸ ੿੄ೞҊ Ӓী ݏח పझ౟ ݢ੷ ੘ࢿ
    • पઁ۽ ೧ࠁݶ ցޖ ൨ٝ
    https://goo.gl/IPwERJ

    View Slide

  9. ઑӘ औѱ ੽Ӕ (>> None)
    • ௏٘ܳ ੘ࢿೡ ٸ పझ౟ܳ ੸ӓ ഝਊ
    • ex) Selenium పझ౟ ࢲߡܳ ڸਕࢲ ௏٘ ੘ࢿ
    • ߡӒܳ ଺ਵݶ Ӓী ೧׼ೞח పझ౟ ੘ࢿ (য়׹֢౟?)
    • పझ౟ܳ ੘ࢿೞӝ য۰਍ ௏٘
    • Ӓ ௏٘ী ؀ೠ పझ౟ ੘ࢿਸ ನӝೠ׮ ನӝೞݶ ಞ೧

    • Mocking / fakes (੉Ѫب औ૑݅਷ ঋणפ׮݅)
    • ௏٘ ܻಂష݂ਸ Ҋ۰ (పझ౟ ೞӝ ए਍ ௏٘ ੘ࢿ)
    • పझ౟ח ௏٘ܳ о੢ ੸ӓ੸ਵ۽ ഝਊೞח ੌઙ੄ ࢎਊ੗

    View Slide

  10. ౵੉ॆ੄ unittest
    ౵੉ॆ੄ పझ౴ ೐ۨ੐ਕ௼

    View Slide

  11. unittest ݽٕ
    • ౵੉ॆ੄ పझ౟ ೐ۨ੐ਕ௼
    • ݔࣚਵ۽ పझ౟ ੘ࢿ೧ب غ૑݅ ب਑ਸ ߉ਵݶ ಞೣ
    • పझ౟ ா੉झ ੘ࢿ ߂ प೯ ੹߈ী Ѧ୛ ਬਊೠ بҳ ઁҕ
    • పझ౟ ா੉झ Ѩ࢝
    • పझ౟ী ೙ਃೠ ࢎ੹/ࢎറ ੘স (e.g. fixture)
    • పझ౟ ா੉झ प೯
    • పझ౟ Ѿҗ ࣻ૘
    • Ѿҗ ࠁҊ
    • ١١

    View Slide

  12. unittest (ױਤ పझ౟)
    • ױਤ పझ౟ (unit test)
    • ୭ࣗ ӝמ ױਤী ؀ೠ పझ౟ (ex. add_one ੗୓ ӝמ పझ౟)
    • ా೤ పझ౟ (integration test)
    • ݻ ѐ੄ ӝמ ױਤܳ ޘযࢲ పझ౟ (ex. add_one ࢎਊೞח ੄ઓ ӝמ పझ౟)
    • ӝמ పझ౟ (functional test)
    • ࢎਊ੗ झషܻ పझ౟
    • ਢ੄ ҃਋ Selenium పझ౟ ١
    • …
    Selenium పझ౟੄ ҃਋ PyCon APAC 2016 ߊ಴ ଵҊ
    - ௿ۄ਋٘ ࢚ীࢲ Seleniumਸ ੉ਊೠ Django ӝמ పझ౟ ੗زച
    - https://www.pycon.kr/2016apac/program/37
    Where do our flaky tests come from?
    https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html

    View Slide

  13. unittest ݽٕਸ ഝਊೠ పझ౟ ா੉झ
    • పझ౟ Ӓܛ : unittest.TestCase ࢚ࣘ
    • పझ౟ ா੉झ : test۽ द੘ೞח ݫࣗ٘
    import unittest
    from code import myabs
    class TestMyAbs(unittest.TestCase):
    def test_return_itself_with_positive_param(self):
    self.assertEqual(myabs(5), 5)
    def test_return_positive_with_negative_param(self):
    self.assertEqual(myabs(-7), 7)
    def myabs(n):
    if n >= 0:
    return n
    else:
    return -n
    code.py test_code_unittest.py
    self.assertEqual(a, b) : పझ౟ ী۞
    assert a == b : ী۞
    పझ౟ Ӓܛ
    పझ౟ ா੉झ
    పझ౟ ா੉झ

    View Slide

  14. ޖࣗध੉ ൞ࣗध
    • ೞ૑݅ పझ౟ח पಁ೧ঠ ઁ ݍ!
    pycon17) ~/D/pycon17 ››› python -m unittest test_code_unittest.py
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    OK
    ݽٚ పझ౟о ੜ ࣻ೯ؽ
    పझ౟ प೯:
    • python -m unittest
    • python -m unittest test_code_unittest.py
    • python -m unittest test_code_unittest.TestMyAbs
    • python -m unittest test_code_unittest.TestMyAbs.test_return_itself_with_positive_param

    View Slide

  15. పझ౟ ী۞ vs ী۞
    def test_this_will_fail(self):
    self.assertEqual(myabs(5), 6)
    FAIL: test_this_will_fail (test_code_unittest.TestMyAbs)
    --------------------------------------------------------
    Traceback (most recent call last):
    File
    "/Users/adrysn/Develop/pycon17/test_code_unittest.py",
    line 7, in test_this_will_fail
    self.assertEqual(myabs(5), 6)
    AssertionError: 5 != 6
    --------------------------------------------------------
    Ran 1 test in 0.000s
    FAILED (failures=1)
    def test_this_will_fail(self):
    assert myabs(5) == 6
    FAIL: test_this_will_fail (test_code_unittest.TestMyAbs)
    --------------------------------------------------------
    Traceback (most recent call last):
    File
    "/Users/adrysn/Develop/pycon17/test_code_unittest.py",
    line 7, in test_this_will_fail
    assert myabs(5) == 6
    AssertionError
    --------------------------------------------------------
    Ran 1 test in 0.000s
    FAILED (failures=1)
    Ӓېࢲ unittest ݽٕ ੉ਊೡ ҃਋ assert*() ݫࣗ٘ ࢎਊਸ ӂ੢
    test exception python exception

    View Slide

  16. 다양한 assertion ݫࣗ٘
    .FUIPE $IFDLT UIBU
    assertRaises(exc, fun, *args, **kwds) fun(*args, **kwds) raises exc
    assertRaisesRegex(exc, r, fun, *args, **kwds) fun(*args, **kwds) raises exc and the message matches regex r
    assertWarns(warn, fun, *args, **kwds) fun(*args, **kwds) raises warn
    assertWarnsRegex(warn, r, fun, *args, **kwds) fun(*args, **kwds) raises warn and the message matches regex r
    assertLogs(logger, level) The with block logs on logger with minimum level
    .FUIPE $IFDLT UIBU
    assertEqual(a, b) a == b
    assertNotEqual(a, b) a != b
    assertTrue(x) bool(x) is True
    assertFalse(x) bool(x) is False
    assertIs(a, b) a is b
    assertIsNot(a, b) a is not b
    assertIsNone(x) x is None
    assertIsNotNone(x) x is not None
    assertIn(a, b) a in b
    assertNotIn(a, b) a not in b
    assertIsInstance(a, b) isinstance(a, b)
    assertNotIsInstance(a, b) not isinstance(a, b)
    .FUIPE $IFDLT UIBU
    assertAlmostEqual(a, b) round(a-b, 7) == 0
    assertNotAlmostEqual(a, b) round(a-b, 7) != 0
    assertGreater(a, b) a > b
    assertGreaterEqual(a, b) a >= b
    assertLess(a, b) a < b
    assertLessEqual(a, b) a <= b
    assertRegex(s, r) r.search(s)
    assertNotRegex(s, r) not r.search(s)
    assertCountEqual(a, b) a and b have the same elements in the same
    number, regardless of their order
    .FUIPE 6TFE UP DPNQBSF
    assertMultiLineEqual(a, b) strings
    assertSequenceEqual(a, b) sequences
    assertListEqual(a, b) lists
    assertTupleEqual(a, b) tuples
    assertSetEqual(a, b) sets or frozensets
    assertDictEqual(a, b) dicts
    PEP8 ٮਤח ޖद೤פ׮…

    View Slide

  17. ੉ۧѱ పझ౟ܳ ੘ࢿೞ׮ ࠁݶ ೠ о૑ ޙઁо…
    • п పझ౟ח ׮ܲ పझ౟ী ة݀੸੉যঠ ೣ
    • ৈ۞ పझ౟ী ࢎਊغח ҕా పझ౟ ܻࣗझ੄ ೙ਃࢿ
    • పझ౟ীࢲب Don’t Repeat Yourself (DRY)
    class TestSomething(unittest.TestCase):
    def test_one (self):
    # ҕా ੗ਗ 1 ੿੄ (ex. ࢎਊ੗)
    # ҕా ੗ਗ 2 ੿੄ (ex. Mock obj)
    self.assertEqual(...)
    def test_two(self):
    # ҕా ੗ਗ 1 ژ ੿੄
    # ҕా ੗ਗ 2 ژ ੿੄
    self.assertEqual(...)

    View Slide

  18. unittest੄ fixture
    class Test(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
    # executed before all test cases in Test
    cls._conn = createExpensiveObject()
    @classmethod
    def tearDownClass(cls):
    # executed after all test cases in Test
    cls._conn.destroy()
    def setUp(self):
    # executed before each test case
    self.common_widget = createWidget('the widget')
    def tearDown(self):
    # executed after each test case
    self.common_widget.dispose()
    ௿ېझܳ ഝਊೞৈ ҕా ੗ਗ ҕਬ/੤ࢎਊ
    п పझ౟ ா੉झ प೯ ੹റ
    Test ղ੄ ੹୓
    పझ౟ प೯ ੹റ

    View Slide

  19. పझ౟ झझ۽ ҳઑܳ ऺӝ द੘
    CommonMethods
    WebPageBase FuncTestBase
    LogInPage
    SignupPage

    LogInTest
    SignupTest

    ೙ਃহח ੗ਗب ݽٚ పझ౟ Ӓܛ੉ ҕਬೞѱ ؽ

    View Slide

  20. pytest
    औ૑݅ ъ۱ೠ పझ౟ ۄ੉࠳۞ܻ

    View Slide

  21. pytest
    • పझ౟ܳ औѱ ೡ ࣻ ੓ب۾ ب৬઱ח ۄ੉࠳۞ܻ
    • ࢿࣼೠ ۄ੉࠳۞ܻ
    • 2007֙ 1ਘ ୐ ழ޿
    • ୭Ӕө૑ ഝߊೞѱ ழ޿
    “makes it easy to write small tests, yet scales to support
    complex functional testing for applications and libraries”

    View Slide

  22. ই઱ औѱ द੘
    • ӝઓ పझ౟ ா੉झܳ ইޖ ࣻ੿হ੉ प೯
    pip install pytest
    python –m pytest
    పझ౟ प೯:
    • python -m pytest 쏞쁢 pytest
    • python -m pytest test_code_unittest.py
    • python -m pytest test_code_unittest.py::TestMyAbs
    • python -m pytest test_code_unittest.py::TestMyAbs::test_return_itself_with_positive_param

    View Slide

  23. рಞೠ పझ౟ ੘ࢿ
    • పझ౟ܳ ߈٘द ௿ېझ۽ ੘ࢿೡ ೙ਃ হ਺
    • self.assert* ݫࣗ٘ ࢎਊೡ ೙ਃ হ਺
    class TestMyAbs(unittest.TestCase):
    def test_return_itself_with_positive_param(self):
    self.assertEqual(myabs(5), 5)
    test_code_pytest.py
    class TestMyAbs:
    def test_return_itself_with_positive_param(self):
    assert myabs(5) == 5
    test_code_unittest.py

    View Slide

  24. ੗ࣁೠ पಁ ࠁҊ
    FAIL: test_this_will_fail
    (test_code_unittest.TestMyAbs)
    -----------------------------------------------
    Traceback (most recent call last):
    File
    "/Users/adrysn/Develop/pycon17/test_code_unitte
    st.py", line 7, in test_this_will_fail
    self.assertEqual(myabs(5), 6)
    AssertionError: 5 != 6
    -----------------------------------------------
    Ran 1 test in 0.000s
    FAILED (failures=1)
    platform darwin -- Python 3.6.2, pytest-3.2.0, py-1.4.34, pluggy-
    0.4.0
    rootdir: /Users/adrysn/Develop/pycon17, inifile:
    collected 1 item s
    test_code_unittest.py F
    ========================== FAILURES ==========================
    _______________ TestMyAbs.test_this_will_fail ________________
    self = testMethod=test_this_will_fail>
    def test_this_will_fail(self):
    > assert myabs(5) == 6
    E AssertionError: assert 5 == 6
    E + where 5 = myabs(5)
    test_code_unittest.py:7: AssertionError
    ================== 1 failed in 0.04 seconds ==================
    test_code_pytest.py
    test_code_unittest.py
    test exceptionਵ۽ ੋध

    View Slide

  25. Fixture
    • పझ౟ী ೙ਃೠ ҕా ੗ਗ
    • ೣࣻ ഋక
    • పझ౟ ா੉झীࢲ ೙ਃ۽ ೞח fixtureܳ ࢶఖ੸ਵ۽ औѱ ࢎਊ
    • Dependency injection ഋక
    • పझ౟੄ ة݀ࢿҗ ੗ਗ ੤ࢎਊ ࢎ੉੄ ઑചܳ ଺ח ೠ ߑಞ

    View Slide

  26. Fixture ੘ࢿҗ ࢎਊ
    # content of ./test_smtpsimple.py
    import pytest
    @pytest.fixture
    def smtp():
    import smtplib
    return smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
    def test_ehlo(smtp):
    response, msg = smtp.ehlo()
    assert response == 250
    assert 0 # for demo purposes
    def test_ehlo2(smtp):
    # can use `smtp` again
    $ pytest test_smtpsimple.py
    ======= test session starts ========
    platform linux -- Python 3.x.y, pytest-
    3.x.y, py-1.x.y, pluggy-0.x.y
    rootdir: $REGENDOC_TMPDIR, inifile:
    collected 1 item
    test_smtpsimple.py F
    ======= FAILURES ========
    _______ test_ehlo ________
    smtp =
    def test_ehlo(smtp):
    response, msg = smtp.ehlo()
    assert response == 250
    > assert 0 # for demo purposes
    E assert 0
    test_smtpsimple.py:11: AssertionError
    ======= 1 failed in 0.12 seconds ========

    View Slide

  27. Fixture੄ ׮নೠ ২࣌
    @pytest.fixture()
    def smtp():
    smtp = smtplib.SMTP("smtp.gmail.com", 587, timeout=5)
    yield smtp # provide the fixture value
    print("teardown smtp")
    smtp.close()
    Finalization ௏٘ प೯ (yield ੉ਊ)
    @pytest.fixture(params=["smtp.gmail.com", "mail.py.org"])
    def smtp(request):
    return smtplib.SMTP(request.param, 587, timeout=5)
    Parametrization
    class App(object):
    def __init__(self, smtp):
    self.smtp = smtp
    @pytest.fixture(scope="module")
    def app(smtp):
    return App(smtp)
    Fixtureীࢲ ׮ܲ fixture ࢎਊ
    https://docs.pytest.org/en/latest/fixture.html
    ׮ܲ fixture

    View Slide

  28. Fixture য়ߡۄ੉٬ tests/
    __init__.py
    conftest.py
    @pytest.fixture
    def username():
    return 'username'
    test_something.py
    def test_username(username):
    assert username == 'username'
    subfolder/
    __init__.py
    conftest.py
    @pytest.fixture
    def username(username):
    return 'overridden-' + username
    test_something.py
    def test_username(username):
    assert username == 'overridden-username'
    @pytest.fixture
    def username():
    return 'username'
    class TestMyAbs:
    @pytest.fixture
    def username(username):
    return 'MyAbs-' + username
    def test_something(self, username):
    assert username == 'MyAbs-username'

    View Slide

  29. pytest builtin fixtures
    (neumann) ~/l/neumann ››› pytest -q –fixtures
    cache
    Return a cache object that can persist state between testing sessions.
    cache.get(key, default)
    cache.set(key, value)
    ...
    monkeypatch
    The returned ``monkeypatch`` fixture provides these
    helper methods to modify objects, dictionaries or os.environ::
    monkeypatch.setattr(obj, name, value, raising=True)
    monkeypatch.delattr(obj, name, raising=True)
    ...
    tmpdir
    Return a temporary directory path object
    which is unique to each test function invocation,
    created as a sub directory of the base temporary
    directory. The returned object is a `py.path.local`_
    path object.

    View Slide

  30. pytest ೒۞Ӓੋ
    • ઱۽ ਬਊೠ ӝמ੉ա fixture ઁҕ
    • pytest-cov : పझ౟ ழߡܻ૑
    • pytest-xdist : పझ౟ ߽۳ച
    • pytest-django : Django ۄ੉࠳ ࢲߡ
    • pytest-selenium : selenium పझ౟ ҙ۲ fixture
    • …

    View Slide

  31. class LablupWebdriver(webdriver.Chrome):
    """
    Wrapper for selenium's WebDriver.
    The purpose is to append additional features/helpers to WebDriver.
    Currently, only Chrome is taken accounted for.
    """
    def __init__(self, base_url, timeout=10, *args, **kwargs):
    self._base_url = base_url
    self._timeout = timeout
    self._web_element_cls = LablupWebElement
    desired_capabilities = dict()
    desired_capabilities['chromeOptions'] = {
    "args": ["--no-sandbox", "--proxy-server null"],
    "extensions": []
    }
    super().__init__(desired_capabilities=desired_capabilities, *args, **kwargs)
    @pytest.fixture
    def browser(live_server):
    driver = LablupWebdriver(live_server.url)
    driver.set_window_size(1280, 1024)
    yield driver
    driver.quit()
    def test_search_course(self, browser, tester):
    # Create a new course to be searched
    course = self.create_course(owner=tester)
    # Search course
    browser.login(tester)
    browser.get('dashboard:course')
    course_page = CoursePage(browser)
    course_page.search_course(course.title)
    # Visit searched circle
    url = self.reverse('course:course', args=(str(course.id),))
    browser.get(url)
    assert url in browser.current_url
    pytest-django + pytest-selenium
    ੉ਊ೧ࢲ పझ౟ प೯ೞח ৘

    View Slide

  32. pytest੄ ੢੼
    • ૊द द੘ оמ
    • Boilerplate ௏٘ܳ ੸ѱ
    • ب਑੉ غח पಁ Ѿҗ ࠁҊ
    • Fixtureܳ ా೧ పझ౟ ੄ઓ ੗ਗਸ ݽٕച
    • ׮নೠ ೒۞Ӓੋ

    View Slide

  33. Travis CI
    పझ౟ ੗زച

    View Slide

  34. పझ౟ प೯ೞח Ѫب ࠺ਊ
    • ੹୓ పझ౟ܳ प೯೧ࢲ ઱ӝ੸ਵ۽ ௏٘ উ੿ࢿ ഛੋ ೙ਃ
    • పझ౟ ࣻ೯ী ߹ب दр ೙ਃ
    • ױਤ పझ౟ח ࡅܰ૑݅ ੹୓ పझ౟ח दр ೙ਃ
    • Selenium పझ౟ э਷ ҃਋ ೠ ઴ ૞ܻ పझ౟ب ࣻ ୡ ࣗਃ
    • ઁо ଵৈೞח ೠ ೐۽ં౟੄ ੹୓ పझ౟ ࣻ೯ दр
    • ױਤ పझ౟: 3~4࠙ (582ѐ)
    • Selenium పझ౟: 20࠙ (161ѐ)

    View Slide

  35. ੗୓ పझ౟ ࢲߡ – ৉द दрҗ ֢۱੉ ೙ਃ
    Unit tests
    Selenium tests
    Local1
    Test server (on EC2)
    git commit; git push
    Actual codes
    Github
    GithubAutoDeploy
    (listen hook ➡ run scripts)
    hook
    git pull
    post-merge hook
    Run DB migration
    Run unit tests
    listen
    err no err
    Error posting script
    Run functional tests
    err
    no err
    Unit tests
    Selenium tests
    Local2
    git commit; git push
    Actual codes
    once a day
    comment on issue

    Slack notification

    View Slide

  36. Travis CI
    • పझ౟/ߓನ ੗زച ࢲ࠺झ
    • Github ҅੿ োز
    • ࢜ commit ߊࢤೡ ٸ݃׮ ੗زਵ۽ పझ౟ (࢜۽਍ о࢚ݠन ࢤࢿ)
    • పझ౟ ജ҃ ߂ ݺ۸਷ .travis.yml ౵ੌ۽ ࢸ੿
    • য়೑ࣗझ ೐۽ં౟ח ޖܐ : https://travis-ci.org
    • ࠺ҕѐ ೐۽ં౟ח җӘ : https://travis-ci.com

    View Slide

  37. Travis CI۽ పझ౟ प೯
    • Github ҅੿ োز + Travis CIীࢲ ࣻ೯ೡ ੘স ࢸ੿ (.travis.yml)
    language: python
    python:
    - "2.7"
    - "3.6"
    install: "pip install -r requirements.txt"
    script: pytest
    notifications:
    slack:
    secure:
    .travis.yml
    పझ౟ ജ҃ ࢸ੿
    ೙ਃೠ ੄ઓಁః૑ ࢸ஖
    పझ౟ प೯
    పझ౟ Ѿҗ ঌۈ

    View Slide

  38. travis.yml੄ ੗ࣁೠ ࢸ੿਷
    https://docs.travis-ci.com/user/customizing-the-build/

    View Slide

  39. Commit status ੗ز সؘ੉౟
    ௿ܼ

    View Slide

  40. ݽٚ җ੿ਸ ௑ࣛ
    ۽Ӓ۽ ഛੋ оמ

    View Slide

  41. աب ੉ઁ పझ౟ ੗زച
    • ই઱ рױ൤ పझ౟ ੗زച
    • CRON job ӝמ ഝਊ ઱ӝ੸ పझ౟ оמ (ই૒ ߬ఋ)
    • ৘) ೞܖ ೠ ߣ selenium పझ౟ܳ ࣻ೯
    • Ҷ੉ ױ੼ਸ Ԟ੗ݶ
    • పझ౟݃׮ ࢜ о࢚ݠनਸ ࢤࢿ/౵Ҧೞ޲۽ ӝࠄ੸ਵ۽ ੟ইݡח दр੉ ੓਺
    • .travis.yml ࣻ੿ೠ Ѿҗܳ commit റ Travis CI ࢎ੉౟ оঠ݅ ഛੋೡ ࣻ ੓
    যࢲ, ખ ࠂ੟ೠ ࢸ੿ਸ ઱Ҋ रਸ ٸ ٣ߡӒо য۰਑

    View Slide

  42. ஶప੉ցܳ ాೠ పझ౟ प೯
    Docker + Travis CI

    View Slide

  43. Docker
    • ஶప੉ց о࢚ച ೒ۖಬ
    • ة݀੸੉Ҋ ӵՖೠ о࢚ݠनਸ ࡅܰѱ ࢤࢿ/౵Ҧ
    Eliminate “works on my computer” problem
    https://www.docker.com

    View Slide

  44. Images vs containers
    • Images : ౵ੌदझమ੄ झշࢫ
    • Containers : ة݀੸ੋ о࢚ ݠन
    • ݎೠ ࠺ਬ
    • ੗ࣁೠ Ѫ਷ بழ കಕ੉૑ܳ ଵઑ
    ਦب਋ૉ ੉޷૑
    ਦب਋ૉ PC (ஶప੉ց?)
    ࢎਊ੗ ࢎਊ
    - dir
    - run.bat
    - starcraft

    View Slide

  45. Images vs containers
    (pycon17) ~/D/pycon17 ››› docker ps
    CONTAINER ID IMAGE COMMAND CREATED STATUS
    74342efab4b6 neumann_neumann-web "supervisord -n -c..." 6 weeks ago Up About a minute
    cada03cea397 rabbitmq "docker-entrypoint..." 6 weeks ago Up About a minute
    036f711b0530 redis "docker-entrypoint..." 6 weeks ago Up About a minute
    6e19dd3e97bf postgres "/docker-entrypoin..." 6 weeks ago Up About a minute
    (pycon17) ~/D/pycon17 ››› docker images
    REPOSITORY TAG IMAGE ID CREATED SIZE
    alpine 3.6 a41a7446062d 2 months ago 3.97MB
    neumann_neumann-web latest 84404c459bed 2 months ago 1.75GB
    redis latest 45c3ea2cecac 6 months ago 183MB
    rabbitmq 3.6-alpine 241555f3f25a 2 months ago 37.5MB
    postgres latest c9994f4753b2 6 months ago 265MB
    ubuntu 14.04 302fa07d8117 3 months ago 188MB
    lablup/kernel-python3-tensorflow latest b3245c09ab05 4 months ago 1.68GB
    sornaweb_sorna-web latest 622345c5c78c 6 months ago 1.01GB
    ࢎਊ੗о ੽ࣘ೧ࢲ ݺ۸ਸ प೯ೡ ࣻ ੓਺

    View Slide

  46. Dockerfile : ੉޷૑ܳ ݅٘ח ౵ੌ
    FROM ubuntu:latest
    MAINTAINER Jonghyun Park "[email protected]"
    ENV PATH $pyenv/bin:/usr/local/sbin
    RUN apt-get update
    RUN apt-get install -y python python-pip wget
    RUN pip install Django
    ADD test_myabs.py /home/test_myabs.py
    WORKDIR /home
    EXPOSE 80, 443
    CMD ["echo", "hello"]
    Dockerfile
    docker build -t "my_image" .
    docker run my_image
    my_image ੉޷૑۽ࠗఠ ஶప੉ց प೯
    my_image ੉޷૑ ࢤࢿ

    View Slide

  47. docker-compose : ৈ۞ ஶప੉ց زद प೯
    version: '2'
    services:
    neumann-web:
    container_name: web
    build:
    context: .
    dockerfile: Dockerfile.dev
    ports:
    - 8080:8080
    extra_hosts:
    - "docker.local:192.168.65.1"
    volumes:
    - .:/neumann
    - /dev/shm:/dev/shm
    environment:
    - DBUS_SESSION_BUS_ADDRESS=/dev/null
    depends_on:
    - neumann-db
    - neumann-mq
    - redis
    redis:
    container_name: cache
    image: redis
    ports:
    - 6379:6379
    neumann-db:
    container_name: neumann-db
    image: postgres
    ports:
    - 5442:5432
    environment:
    - POSTGRES_PASSWORD=develove
    - POSTGRES_DB=neumann
    neumann-mq:
    container_name: neumann-mq
    image: rabbitmq
    docker-compose.yml
    docker-compose -f docker-compose.yml up
    ੉޷૑ ੉ܴ
    ੉޷૑ ࠽٘

    View Slide

  48. Travis CIীࢲ ஶప੉ց۽ పझ౟ प೯ೞ۰ݶ
    • .travis.ymlী ׮਺ ݺ۸ਸ ӝࣿ
    • Dockerfile۽ ೙ਃೠ ੉޷૑ ੘ࢿ
    • ೙ਃೠ ҃਋ docker-compose۽ ৈ۞ ੉޷૑ زद ஶప੉ցച
    • ஶప੉ց উীࢲ పझ౟ী ೙ਃೠ ജ҃ ࢸ੿
    • ஶప੉ց উীࢲ పझ౟ ݺ۸ प೯

    View Slide

  49. sudo: required
    language: python
    python:
    - "3.6"
    services:
    - docker
    env:
    global:
    - DOCKER_COMPOSE_VERSION=1.14.0
    - CACHE_DIR=$HOME/.cache
    - DOCKER_CACHE_FILE=$CACHE_DIR/docker/cache-image-web.tar.gz
    - NEUMANN_IMAGE_NAME=neumann_neumann-web
    - RUN_IN_PYVENV="docker exec -it web /neumann/run-in-pyvenv.sh"
    cache:
    directories:
    - $CACHE_DIR/docker
    install:
    # Install newer version of docker.
    - sudo apt-get update
    - sudo apt-get -y -o Dpkg::Options::="--force-confold" --force-yes install docker-ce
    - docker-compose --version
    - sudo rm /usr/local/bin/docker-compose
    - curl -L https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-
    compose-`uname -s`-`uname -m` > docker-compose
    - chmod +x docker-compose
    - sudo mv docker-compose /usr/local/bin
    بழ ࢲ࠺झܳ ࢎਊ
    ஶప੉ց ղ virtualenv प೯
    .travis.yml
    ؀ࠗ࠙਷ بழ ੉޷૑ நय ҙ۲ ݺ۸

    View Slide

  50. before_script:
    # Load neumann docker image previously cached if exists.
    - if [ -f ${DOCKER_CACHE_FILE} ]; then gunzip -c ${DOCKER_CACHE_FILE} | docker load; fi
    - NEUMANN_IMAGE_ID=`docker images -q | head -n1`
    - docker-compose -f docker-compose.travis-ci.yml build
    - NEUMANN_IMAGE_ID_NEW=`docker images -q ${NEUMANN_IMAGE_NAME}`
    # Run neumann server by docker-compose.
    - find * -name '*.py[co]' -delete
    - docker-compose -f docker-compose.travis-ci.yml up -d
    - docker ps
    script:
    - echo ${TRAVIS_EVENT_TYPE}
    - SELENIUM=0 && [[ ${TRAVIS_EVENT_TYPE} == "cron" ]] && SELENIUM=1 || true
    - if [[ SELENIUM -eq 0 ]]; then ${RUN_IN_PYVENV} pytest --debug-mode --cov=. -m "not
    selenium" ; else ${RUN_IN_PYVENV} xvfb-run pytest --debug-mode -m "selenium" --rerun 2; fi
    # Save neumann docker image to cache directory only if new image was built.
    - if [[ ${NEUMANN_IMAGE_ID} != ${NEUMANN_IMAGE_ID_NEW} ]]; then mkdir -p $(dirname
    ${DOCKER_CACHE_FILE}); docker save $(docker history -q ${NEUMANN_IMAGE_NAME} | grep -v
    '') | gzip > ${DOCKER_CACHE_FILE}; fi
    notifications:
    webhooks:
    - https://outlook.office.com/webhook/******************
    4ѐ੄ بழ ஶప੉ց ڸ਑
    ஶప੉ցীࢲ
    pytest प೯
    notification

    View Slide

  51. ಞೞ૓ ঋ਷ Ѫ э਷ؘਃ…
    • بழ۽ పझ౟ ೞݶ ࢎप ੌ੉ ݆णפ׮
    • ೞ૑݅ জਸ ੉޷૑۽ ೠ ߣ ٜ݅য فݶ જणפ׮
    • పझ౟ ೡ ٸח ખ ࠂ੟ೞ૑݅, ੹߈੸ਵ۽ ಞೣ
    • ৈ۞ о૑۽ જ਷ ੼੉ ݆ਵפ ॄࠁ૑ ঋਵन ࠙਷ ੉ߣ ӝഥী ೠ ߣ...

    View Slide

  52. ੿ܻ
    • pytestܳ ੜ ഝਊೞݶ పझ౟੄ ة݀ࢿਸ ਬ૑ೞݶࢲ పझ౟ ੗ਗ ੤ࢎ
    ਊਸ ࠁ׮ औѱ ೡ ࣻ ੓णפ׮
    • য়೑ࣗझ ࢎਊೞח ҃਋ Travis CI ࢎਊ೤द׮
    • పझ౟݅ਸ ਤ೧ بழܳ ࢎਊೡ Ѫө૑ח হח Ѫ эणפ׮…
    • ೞ૑݅ জ ੗୓ܳ بழച ೞݶ ಞ೤פ׮

    View Slide

  53. хࢎ೤פ׮!
    ੹੗ন੄ Էਸ Բח উ٘۽੉٘: Pythonҗ NLTK, TensorFlowܳ ੉ਊೠ ୁࠈ х੿ݽഋ ҳഅ
    14:40 (न੿ӏ)
    Meet aiotools: asyncio Idiom Library
    ղੌ(8/13) 13:30 (ӣળӝ)

    View Slide