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와 도커관련 내용이 들어가는데, 도커 이미지와 컨테이너에 대한 기본적인 지식이 있으면 좀 더 쉽게 이해할 수 있을 것으로 생각한다 (필수는 아님). 이미 테스트와 빌드 자동화에 대한 경험이 많은 개발자는 대상이 아니다. 이 발표를 통해 테스트 케이스를 작성해보기는 했으나 더이상 무엇을 어떻게 해야할지 고민하고 있는 초보 테스터들에게 좀 더 강력하고 편한 테스트 개발 환경을 소개할 수 있기를 기대한다.

51df149d8dc13916f924acf398e72234?s=128

Jonghyun Park

August 09, 2017
Tweet

Transcript

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

    (઱)ې࠶স
  2. ߊ಴੗ • (઱) ې࠶স (~2.2֙) • HTML, CSS, JavaScript, Polymer,

    Django, Go • ನ೦ Ѣ઱ (ਗѺӔޖ) • Ӕ୊ ࢎदח ࠙ झఠ٣ ݽ੐ э਷ Ѫ ജ৔ ƕƕ
  3. ݈ೞ۰Ҋ ೞח Ѫ • పझ౟ ੘ࢿਸ ಞೞѱ: pytest • పझ౟

    प೯ਸ ಞೞѱ: Travis CI • ౵੉ॆ unittest ݽٕ • unittest ؀࠺ pytest੄ ੢੼ • Travis CIܳ ాೠ పझ౟ ੗زച • docker containerܳ ాೠ పझ౟ प೯
  4. ୒઺ • ౵੉ॆਵ۽ పझ౟ா੉झ ੘ࢿਸ ೧ࠄ ҃೷੉ যו ੿ب ੓ח

    ࠙ • pytest ੜ ॳҊ ҅दݶ Ҷ੉ উ ٜਵ࣊ب… • ੉޷ పझ౟ ੜ ೞदח ࠙਷ ׮ܲ ࣁ࣌ਸ…
  5. పझ౟ పझ౟ ೤द׮

  6. పझ౟ ா੉झ • ௏٘о ਗೞח ؀۽ ز੘ೞח૑ పझ౟ೞח ௏٘ •

    ա ؀न ௏٘ܳ పझ౟೧ ઴ ޖ঱оо ೙ਃ • ঱ઁө૑ա ࣻز పझ౟ܳ ೡ ࣻח হ਺ • ܻಂష݂ೡ ٸ ୭ࣗೠ੄ ߡ౱ݾ def add_one(n): return n + 1 def test_add_one(): assert add_one(1) == 2 assert add_one(8) == 9 ௏٘ పझ౟ ா੉झ
  7. పझ౟ܳ ೤द׮ • పझ౟ח ѐߊ੄ ೙ࣻ੸ੋ җ੿ • పझ౟ হ੉

    Ҋಿ૕ ௏٘ܳ য়ۖزউ ਬ૑ೞӝ য۰਑ • (ޛۿ పझ౟о ੓׮Ҋ ௏٘੄ ૕੉ ࠁ੢غ૑ח ঋ਺) • ੗ӝо ੘ࢿೠ ௏٘ী ؀ೠ ୭ࣗೠ੄ పझ౟ۄب • ҳӖ੄ ҃਋ • Software engineer (SWE) • Software engineer in test (SET) • Test engineer (TE)
  8. Test-Driven Development (TDD) • ௏٘о оઉঠ ೡ झಖਸ ੿੄ೞҊ Ӓী

    ݏח పझ౟ ݢ੷ ੘ࢿ • पઁ۽ ೧ࠁݶ ցޖ ൨ٝ https://goo.gl/IPwERJ
  9. ઑӘ औѱ ੽Ӕ (>> None) • ௏٘ܳ ੘ࢿೡ ٸ పझ౟ܳ

    ੸ӓ ഝਊ • ex) Selenium పझ౟ ࢲߡܳ ڸਕࢲ ௏٘ ੘ࢿ • ߡӒܳ ଺ਵݶ Ӓী ೧׼ೞח పझ౟ ੘ࢿ (য়׹֢౟?) • పझ౟ܳ ੘ࢿೞӝ য۰਍ ௏٘ • Ӓ ௏٘ী ؀ೠ పझ౟ ੘ࢿਸ ನӝೠ׮ ನӝೞݶ ಞ೧ • Mocking / fakes (੉Ѫب औ૑݅਷ ঋणפ׮݅) • ௏٘ ܻಂష݂ਸ Ҋ۰ (పझ౟ ೞӝ ए਍ ௏٘ ੘ࢿ) • పझ౟ח ௏٘ܳ о੢ ੸ӓ੸ਵ۽ ഝਊೞח ੌઙ੄ ࢎਊ੗
  10. ౵੉ॆ੄ unittest ౵੉ॆ੄ పझ౴ ೐ۨ੐ਕ௼

  11. unittest ݽٕ • ౵੉ॆ੄ పझ౟ ೐ۨ੐ਕ௼ • ݔࣚਵ۽ పझ౟ ੘ࢿ೧ب

    غ૑݅ ب਑ਸ ߉ਵݶ ಞೣ • పझ౟ ா੉झ ੘ࢿ ߂ प೯ ੹߈ী Ѧ୛ ਬਊೠ بҳ ઁҕ • పझ౟ ா੉झ Ѩ࢝ • పझ౟ী ೙ਃೠ ࢎ੹/ࢎറ ੘স (e.g. fixture) • పझ౟ ா੉झ प೯ • పझ౟ Ѿҗ ࣻ૘ • Ѿҗ ࠁҊ • ١١
  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
  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 : ী۞ పझ౟ Ӓܛ పझ౟ ா੉झ పझ౟ ா੉झ
  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
  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
  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 ٮਤח ޖद೤פ׮…
  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(...)
  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 ղ੄ ੹୓ పझ౟ प೯ ੹റ
  19. పझ౟ झझ۽ ҳઑܳ ऺӝ द੘ CommonMethods WebPageBase FuncTestBase LogInPage SignupPage

    … LogInTest SignupTest … ೙ਃহח ੗ਗب ݽٚ పझ౟ Ӓܛ੉ ҕਬೞѱ ؽ
  20. pytest औ૑݅ ъ۱ೠ పझ౟ ۄ੉࠳۞ܻ

  21. pytest • పझ౟ܳ औѱ ೡ ࣻ ੓ب۾ ب৬઱ח ۄ੉࠳۞ܻ •

    ࢿࣼೠ ۄ੉࠳۞ܻ • 2007֙ 1ਘ ୐ ழ޿ • ୭Ӕө૑ ഝߊೞѱ ழ޿ “makes it easy to write small tests, yet scales to support complex functional testing for applications and libraries”
  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
  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
  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 = <test_code_unittest.TestMyAbs 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ਵ۽ ੋध
  25. Fixture • పझ౟ী ೙ਃೠ ҕా ੗ਗ • ೣࣻ ഋక •

    పझ౟ ா੉झীࢲ ೙ਃ۽ ೞח fixtureܳ ࢶఖ੸ਵ۽ औѱ ࢎਊ • Dependency injection ഋక • పझ౟੄ ة݀ࢿҗ ੗ਗ ੤ࢎਊ ࢎ੉੄ ઑചܳ ଺ח ೠ ߑಞ
  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 = <smtplib.SMTP object at 0xdeadbeef> 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 ========
  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
  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'
  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.
  30. pytest ೒۞Ӓੋ • ઱۽ ਬਊೠ ӝמ੉ա fixture ઁҕ • pytest-cov

    : పझ౟ ழߡܻ૑ • pytest-xdist : పझ౟ ߽۳ച • pytest-django : Django ۄ੉࠳ ࢲߡ • pytest-selenium : selenium పझ౟ ҙ۲ fixture • …
  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 ੉ਊ೧ࢲ పझ౟ प೯ೞח ৘
  32. pytest੄ ੢੼ • ૊द द੘ оמ • Boilerplate ௏٘ܳ ੸ѱ

    • ب਑੉ غח पಁ Ѿҗ ࠁҊ • Fixtureܳ ా೧ పझ౟ ੄ઓ ੗ਗਸ ݽٕച • ׮নೠ ೒۞Ӓੋ
  33. Travis CI పझ౟ ੗زച

  34. పझ౟ प೯ೞח Ѫب ࠺ਊ • ੹୓ పझ౟ܳ प೯೧ࢲ ઱ӝ੸ਵ۽ ௏٘

    উ੿ࢿ ഛੋ ೙ਃ • పझ౟ ࣻ೯ী ߹ب दр ೙ਃ • ױਤ పझ౟ח ࡅܰ૑݅ ੹୓ పझ౟ח दр ೙ਃ • Selenium పझ౟ э਷ ҃਋ ೠ ઴ ૞ܻ పझ౟ب ࣻ ୡ ࣗਃ • ઁо ଵৈೞח ೠ ೐۽ં౟੄ ੹୓ పझ౟ ࣻ೯ दр • ױਤ పझ౟: 3~4࠙ (582ѐ) • Selenium పझ౟: 20࠙ (161ѐ)
  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
  36. Travis CI • పझ౟/ߓನ ੗زച ࢲ࠺झ • Github ҅੿ োز

    • ࢜ commit ߊࢤೡ ٸ݃׮ ੗زਵ۽ పझ౟ (࢜۽਍ о࢚ݠन ࢤࢿ) • పझ౟ ജ҃ ߂ ݺ۸਷ .travis.yml ౵ੌ۽ ࢸ੿ • য়೑ࣗझ ೐۽ં౟ח ޖܐ : https://travis-ci.org • ࠺ҕѐ ೐۽ં౟ח җӘ : https://travis-ci.com
  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: <random-secure-key...> .travis.yml పझ౟ ജ҃ ࢸ੿ ೙ਃೠ ੄ઓಁః૑ ࢸ஖ పझ౟ प೯ పझ౟ Ѿҗ ঌۈ
  38. travis.yml੄ ੗ࣁೠ ࢸ੿਷ https://docs.travis-ci.com/user/customizing-the-build/

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

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

  41. աب ੉ઁ పझ౟ ੗زച • ই઱ рױ൤ పझ౟ ੗زച •

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

  43. Docker • ஶప੉ց о࢚ച ೒ۖಬ • ة݀੸੉Ҋ ӵՖೠ о࢚ݠनਸ ࡅܰѱ

    ࢤࢿ/౵Ҧ Eliminate “works on my computer” problem https://www.docker.com
  44. Images vs containers • Images : ౵ੌदझమ੄ झշࢫ • Containers

    : ة݀੸ੋ о࢚ ݠन • ݎೠ ࠺ਬ • ੗ࣁೠ Ѫ਷ بழ കಕ੉૑ܳ ଵઑ ਦب਋ૉ ੉޷૑ ਦب਋ૉ PC (ஶప੉ց?) ࢎਊ੗ ࢎਊ - dir - run.bat - starcraft
  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 ࢎਊ੗о ੽ࣘ೧ࢲ ݺ۸ਸ प೯ೡ ࣻ ੓਺
  46. Dockerfile : ੉޷૑ܳ ݅٘ח ౵ੌ FROM ubuntu:latest MAINTAINER Jonghyun Park

    "jpark@lablup.com" 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 ੉޷૑ ࢤࢿ
  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 ੉޷૑ ੉ܴ ੉޷૑ ࠽٘
  48. Travis CIীࢲ ஶప੉ց۽ పझ౟ प೯ೞ۰ݶ • .travis.ymlী ׮਺ ݺ۸ਸ ӝࣿ

    • Dockerfile۽ ೙ਃೠ ੉޷૑ ੘ࢿ • ೙ਃೠ ҃਋ docker-compose۽ ৈ۞ ੉޷૑ زद ஶప੉ցച • ஶప੉ց উীࢲ పझ౟ী ೙ਃೠ ജ҃ ࢸ੿ • ஶప੉ց উীࢲ పझ౟ ݺ۸ प೯
  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 ؀ࠗ࠙਷ بழ ੉޷૑ நय ҙ۲ ݺ۸
  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 '<missing>') | gzip > ${DOCKER_CACHE_FILE}; fi notifications: webhooks: - https://outlook.office.com/webhook/****************** 4ѐ੄ بழ ஶప੉ց ڸ਑ ஶప੉ցীࢲ pytest प೯ notification
  51. ಞೞ૓ ঋ਷ Ѫ э਷ؘਃ… • بழ۽ పझ౟ ೞݶ ࢎप ੌ੉

    ݆णפ׮ • ೞ૑݅ জਸ ੉޷૑۽ ೠ ߣ ٜ݅য فݶ જणפ׮ • పझ౟ ೡ ٸח ખ ࠂ੟ೞ૑݅, ੹߈੸ਵ۽ ಞೣ • ৈ۞ о૑۽ જ਷ ੼੉ ݆ਵפ ॄࠁ૑ ঋਵन ࠙਷ ੉ߣ ӝഥী ೠ ߣ...
  52. ੿ܻ • pytestܳ ੜ ഝਊೞݶ పझ౟੄ ة݀ࢿਸ ਬ૑ೞݶࢲ పझ౟ ੗ਗ

    ੤ࢎ ਊਸ ࠁ׮ औѱ ೡ ࣻ ੓णפ׮ • য়೑ࣗझ ࢎਊೞח ҃਋ Travis CI ࢎਊ೤द׮ • పझ౟݅ਸ ਤ೧ بழܳ ࢎਊೡ Ѫө૑ח হח Ѫ эणפ׮… • ೞ૑݅ জ ੗୓ܳ بழച ೞݶ ಞ೤פ׮
  53. хࢎ೤פ׮! ੹੗ন੄ Էਸ Բח উ٘۽੉٘: Pythonҗ NLTK, TensorFlowܳ ੉ਊೠ ୁࠈ

    х੿ݽഋ ҳഅ 14:40 (न੿ӏ) Meet aiotools: asyncio Idiom Library ղੌ(8/13) 13:30 (ӣળӝ)