94
Python 테스트 시작하기 이호성 에서 함께 보실 있습니다 http://slides.com/hosunglee-1/deck/live

Python 테스트 시작하기

Embed Size (px)

Citation preview

Page 1: Python 테스트 시작하기

Python 테스트 시작하기이호성

에서 함께 보실 수 있습니다http://slides.com/hosunglee-1/deck/live

Page 2: Python 테스트 시작하기

누구세요누구세요??

Page 3: Python 테스트 시작하기

이호성이호성

EnswersDevOpsPython, Cloud Computing함께하는 개발 http://blog.novice.io

2014 Pycon Korea 구경

Pycon 2014 GettingStarted Testing

Page 4: Python 테스트 시작하기

오늘오늘 발표의발표의 목적은목적은??

Page 5: Python 테스트 시작하기

테스트에테스트에 관심을관심을 가져보세요가져보세요

테스팅이테스팅이 처음이신처음이신 분들께분들께

Page 6: Python 테스트 시작하기

다른다른 사람은사람은 어떻게어떻게테스트테스트 할까할까??

테스팅을테스팅을 이미이미 ( (잘잘) ) 하고하고계신계신 분들께분들께

Page 7: Python 테스트 시작하기

꼭꼭 테스트를테스트를작성해야작성해야 하나요하나요??

Page 8: Python 테스트 시작하기

누군가는누군가는 여러분의여러분의 코드를코드를 테테스트스트 해야해야 합니다합니다

Page 9: Python 테스트 시작하기

길게길게 보면보면 시간이시간이절약절약 됩니다됩니다

Page 10: Python 테스트 시작하기

변경에변경에 대한대한 자신감을자신감을 얻으얻으세요세요

Page 11: Python 테스트 시작하기

더더 나은나은 코드를코드를만들어만들어 줍니다줍니다

Page 12: Python 테스트 시작하기

PythonPython 은은컴파일러가컴파일러가 없잖아요없잖아요

Page 13: Python 테스트 시작하기

어떤어떤 테스트를테스트를작성해야작성해야 하나요하나요??

Page 14: Python 테스트 시작하기

수많은수많은 종류의종류의 테스트가테스트가 있습니다있습니다

Testing TypesInstallation testingCompatibility testingSmoke and sanity testingRegression testingAcceptance testingAlpha testingBeta testingFunctional vs non-functional testingDestructive testingSoftware performancetestingUsability testingAccessibility testingSecurity testingInternationalization andlocalizationDevelopment testingA/B testingConcurrent testingConformance testing ortype testing

Testing levelsUnit testingIntegration testingComponent interface testingSystem testingOperational Acceptancetesting

DWhitebox testingBlackbox testing

Testing methodsStatic testing

ynamic testing

Page 15: Python 테스트 시작하기

단위단위 테스트테스트(Unit Test)

기능기능 테스트테스트(Function Test)

개발자 뷰함수 단위Mock 을 사용 빠름더 좋은 코드에 기여

사용자 뷰요구사항 단위Fixture 를 사용느림퇴근에 기여

Page 16: Python 테스트 시작하기

일단일단 하나만하나만보여줘보여줘 봐요봐요

Page 17: Python 테스트 시작하기

PortfolioPortfolio# portfolio.py from http://nedbatchelder.com/text/st.html#7

class Portfolio(object): """간단한 주식 포트폴리오""" def __init__(self): # stocks is a list of lists: # [[name, shares, price], ...] self.stocks = [] def buy(self, name, shares, price): """name 주식을 shares 만큼 주당 price 에 삽니다""" self.stocks.append([name, shares, price]) def cost(self): """이 포트폴리오의 총액은 얼마일까요?""" amt = 0.0 for name, shares, price in self.stocks: amt += shares * price return amt

Page 18: Python 테스트 시작하기

첫번째첫번째 테스트테스트 - - 쉘쉘Python 3.4.0 (default, Jun 19 2015, 14:20:21) [GCC 4.8.2] on linuxType "help", "copyright", "credits" or "license" for more information.>>> from portfolio import Portfolio>>> p = Portfolio()>>> print(p.cost())0.0>>> p.buy('Google', 100, 176.48)>>> p.cost()17648.0>>> p.buy('Yahoo', 100, 36.15)>>> p.cost()21263.0

Good테스트 했어요!

Bad다시 테스트 하려면? 직접 입력 그래서 잘 된건가?

Page 19: Python 테스트 시작하기

두번째두번째 테스트테스트 - - 기대값기대값from portfolio import Portfolio

p = Portfolio()print("Empty portfolio cost: %s, should be 0.0" % p.cost())p.buy('Google', 100, 176.48)print("With 100 Google @ 176.48: %s, should be 17648.0" % p.cost())p.buy('Yahoo', 100, 36.15)print("With 100 Yahoo @ 36.15: %s, should be 21263.0" % p.cost())

Good테스트 했음다시 테스트 할 수 있음잘 된건지 확인 가능

Bad눈으로 확인 해야 함

Empty portfolio cost: 0.0, should be 0.0With 100 Google @ 176.48: 17648.0, should be 17648.0With 100 Yahoo @ 36.15: 21263.0, should be 21263.0

Page 20: Python 테스트 시작하기

세번째세번째 테스트테스트 - - 결과결과 확인확인from portfolio import Portfolio

p = Portfolio()print("Empty portfolio cost: %s, should be 0.0" % p.cost())p.buy('Google', 100, 176.48)assert p.cost() == 17649.0 # Failedprint("With 100 Google @ 176.48: %s, should be 17648.0" % p.cost())p.buy('Yahoo', 100, 36.15)assert p.cost() == 21263.0print("With 100 Yahoo @ 36.15: %s, should be 21263.0" % p.cost())

Good다시 테스트 할 수 있음잘 된건지 자동으로 확인 가능

Bad왜 틀렸는지 알기 힘듬두번째 테스트가 실행 되지않음

Empty portfolio cost: 0.0, should be 0.0Traceback (most recent call last): File "portfolio_test2.py", line 6, in <module> assert p.cost() == 17649.0 # FailedAssertionError

Page 21: Python 테스트 시작하기

( (개발개발 시작시의시작시의 마음과마음과 달리달리))

테스트를테스트를 지속하지지속하지못하는못하는 이유이유??

Page 22: Python 테스트 시작하기

점점점점 복잡해복잡해 진다진다. (. (코드와코드와 함께함께))

반복되는반복되는 노력이노력이 아깝다아깝다..

framework framework 의의 사용을사용을고민해고민해 볼볼 때때!!

어떻게어떻게 테스트테스트 해야해야 할지할지 모른다모른다..

Page 23: Python 테스트 시작하기

Unittest ! Unittest ! (들어봤지만 친하지 않은 그대)

Page 24: Python 테스트 시작하기

unittestunittest

파이썬파이썬 표준표준 라이브러리라이브러리

테스트테스트 자동화자동화

테스트테스트 구조화구조화

테스트테스트 결과결과 보고보고

Page 25: Python 테스트 시작하기

TestCaseTestCase

독립적인독립적인 테스트테스트 단위단위

Page 26: Python 테스트 시작하기

TestSuiteTestSuite

TestCase TestCase 의의 묶음묶음

Page 27: Python 테스트 시작하기

TestRunnerTestRunner

테스트를테스트를 실제로실제로 수행하고수행하고 결과를결과를 레포트레포트 한다한다..

Page 28: Python 테스트 시작하기

테스트테스트 흐름흐름

Page 29: Python 테스트 시작하기

First UnittestFirst Unittest# portfolio_test3.pyimport unittestfrom portfolio import Portfolio class PortfolioTest(unittest.TestCase): def test_google(self): p = Portfolio() p.buy("Google", 100, 176.48) self.assertEqual(17648.0, p.cost()) if __name__ == '__main__': unittest.main()

$ python portfolio_test3.py .----------------------------------------------------------------------Ran 1 test in 0.000s

OK

Page 30: Python 테스트 시작하기

테스트가테스트가 실패실패 했어요했어요!!- 실패하라고 테스트는 만드는 겁니다.

Page 31: Python 테스트 시작하기

테스트테스트 추가추가

# portfolio_test4.pyimport unittestfrom portfolio import Portfolio

class PortfolioTestCase(unittest.TestCase): def test_google(self): p = Portfolio() p.buy("Goole", 100, 176.48) self.assertEqual(17648.0, p.cost())

def test_google_yahoo(self): p = Portfolio() p.buy("Google", 100, 176.48) p.buy("Yahoo", 100, 36.15) self.assertEqual(21264.0, p.cost()) # 21263.0

if __name__ == '__main__': unittest.main()

Page 32: Python 테스트 시작하기

Unittest Unittest 실패실패$ python portfolio_test4.py.F======================================================================FAIL: test_google_yahoo (__main__.PortfolioTestCase)----------------------------------------------------------------------Traceback (most recent call last): File "portfolio_test4.py", line 15, in test_google_yahoo self.assertEqual(21264.0, p.cost())AssertionError: 21264.0 != 21263.0

----------------------------------------------------------------------Ran 2 tests in 0.005s

FAILED (failures=1)

Good테스트 실패가 다른 테스트에 영향을 미치지 않음실패한 위치와 이유를 알 수 있음

Page 33: Python 테스트 시작하기

실패한실패한 테스트만테스트만다시다시 돌리고돌리고 싶어요싶어요..

Page 34: Python 테스트 시작하기

Test Test 고르기고르기$ python portfolio_test4.py PortfolioTestCase.test_google_yahooF======================================================================FAIL: test_google_yahoo (__main__.PortfolioTestCase)----------------------------------------------------------------------Traceback (most recent call last): File "portfolio_test4.py", line 15, in test_google_yahoo self.assertEqual(21264.0, p.cost())AssertionError: 21264.0 != 21263.0

----------------------------------------------------------------------Ran 1 test in 0.005s

FAILED (failures=1)

Good원하는 테스트만 빠르게 실행 해 볼 수 있음출력이 간단해짐

Page 35: Python 테스트 시작하기

테스트테스트 파일이파일이 늘어나면늘어나면 어어떻게떻게 한꺼번에한꺼번에 실행실행 시키시키

죠죠??

Page 36: Python 테스트 시작하기

테스트테스트 한꺼번에한꺼번에 실행하기실행하기$ python -m unittest discover --helpUsage: python -m unittest discover [options]

Options: ,,, -s START, --start-directory=START Directory to start discovery ('.' default) -p PATTERN, --pattern=PATTERN Pattern to match tests ('test*.py' default) -t TOP, --top-level-directory=TOP Top level directory of project (defaults to start directory)$ python -m unittest discover

----------------------------------------------------------------------Ran 15 tests in 0.130s

OK

Good복수개의 파일을 한꺼번에 테스트를 실행 할 수있음

Page 37: Python 테스트 시작하기

Exception Exception 도도 테스트테스트할할 수수 있나요있나요??

Page 38: Python 테스트 시작하기

Portfolio - Portfolio - 타입타입 확인확인class Portfolio(object): """간단한 주식 포트폴리오""" def __init__(self): # stocks is a list of lists: # [[name, shares, price], ...] self.stocks = [] def buy(self, name, shares, price): """name 주식을 shares 만큼 주당 price 에 삽니다""" self.stocks.append([name, shares, price]) if not isinstance(shares, int): raise Exception("shares must be an integer") def cost(self): """이 포트폴리오의 총액은 얼마일까요?""" amt = 0.0 for name, shares, price in self.stocks: amt += shares * price return amt

Page 39: Python 테스트 시작하기

Exception Exception 을을 발생시키는발생시키는 테스트테스트import unittestfrom portfolio import Portfolio class PortfolioTestCase(unittest.TestCase): def test_google(self): p = Portfolio() p.buy("Goole", "many", 176.48) self.assertEqual(17648.0, p.cost()) if __name__ == '__main__': unittest.main()

$ python ./portfolio_test5.py E======================================================================ERROR: test_google (__main__.PortfolioTestCase)----------------------------------------------------------------------Traceback (most recent call last): File "./portfolio_test5.py", line 8, in test_google p.buy("Goole", "many", 176.48) File "/home/leclipse/git/pycon-testing/unit_test/portfolio.py", line 14, in buy raise Exception("shares must be an integer")Exception: shares must be an integer

----------------------------------------------------------------------Ran 1 test in 0.001s

FAILED (errors=1)

Page 40: Python 테스트 시작하기

Exception Exception 을을 테스트테스트import unittestfrom portfolio import Portfolio class PortfolioTestCase(unittest.TestCase): def test_google(self): p = Portfolio() with self.assertRaises(Exception) as context: p.buy("Goole", "many", 176.48)

self.assertTrue("shares must be an integer", context.exception) if __name__ == '__main__': unittest.main()

$ python ./portfolio_test6.py .----------------------------------------------------------------------Ran 1 test in 0.004s

OK

Page 41: Python 테스트 시작하기

TestCase.assert* TestCase.assert* 는는어떤것들이어떤것들이 있나요있나요??

Page 42: Python 테스트 시작하기

적절한적절한 assertassert 의의 사용은사용은 테테스트스트 코드를코드를 간단하게간단하게 합니합니

다다..

Page 43: Python 테스트 시작하기

TestCase.assert*TestCase.assert*

Unittest Assert Methods

Page 44: Python 테스트 시작하기

테스트마다테스트마다 반복되는반복되는 초기초기화는화는 어떻게어떻게관리하나요관리하나요? ?

Page 45: Python 테스트 시작하기

import unittest

class WidgetTestCase(unittest.TestCase): def setUp(self): self.widget = Widget('The widget')

def tearDown(self): self.widget.dispose() self.widget = None

def test_default_size(self): self.assertEqual(self.widget.size(), (50,50), 'incorrect default size')

def test_resize(self): self.widget.resize(100,150) self.assertEqual(self.widget.size(), (100,150), 'wrong size after resize')

setUp(), tearDown()setUp(), tearDown()

Page 46: Python 테스트 시작하기

테스트테스트 흐름흐름

Page 47: Python 테스트 시작하기

비슷한비슷한 테스트를테스트를반복반복 하고하고싶을싶을 때는때는??

Page 48: Python 테스트 시작하기

반복되는반복되는 테스트테스트 실행하기실행하기

import unittest

class NumberTest(unittest.TestCase):

def test_even(self): for i in range(0, 6): self.assertEqual(i % 2, 0)

def test_even_with_subtest(self): for i in range(0, 6): with self.subTest(i=i): self.assertEqual(i % 2, 0)

unittest.main()

0 부터 5 까지 짝수인지를 테스트 합니다.

Page 49: Python 테스트 시작하기

Subtest Subtest 실행실행 결과결과$ python ./subtest.py F======================================================================FAIL: test_even (__main__.NumberTest)----------------------------------------------------------------------Traceback (most recent call last): File "./subtest.py", line 7, in test_even self.assertEqual(i % 2, 0)AssertionError: 1 != 0

======================================================================FAIL: test_even_with_subtest (__main__.NumberTest) (i=1)----------------------------------------------------------------------Traceback (most recent call last): File "./subtest.py", line 12, in test_even_with_subtest self.assertEqual(i % 2, 0)AssertionError: 1 != 0

======================================================================FAIL: test_even_with_subtest (__main__.NumberTest) (i=3)----------------------------------------------------------------------Traceback (most recent call last): File "./subtest.py", line 12, in test_even_with_subtest self.assertEqual(i % 2, 0)AssertionError: 1 != 0

======================================================================FAIL: test_even_with_subtest (__main__.NumberTest) (i=5)----------------------------------------------------------------------Traceback (most recent call last): File "./subtest.py", line 12, in test_even_with_subtest self.assertEqual(i % 2, 0)AssertionError: 1 != 0

----------------------------------------------------------------------Ran 2 tests in 0.001s

FAILED (failures=4)

Good중단 되지 않고 모두 테스트변경 되는 값을 확인 할수 있다.

python 3.4 에 추가 되었음

Page 50: Python 테스트 시작하기

Unittest Unittest 복잡하군요복잡하군요

Page 51: Python 테스트 시작하기

# test_runner.pydef do_test(): for testcase in testsuite: for test_method in testcase.test_methods: try: testcase.setUp() except: [record error] else: try: test_method() except AssertionError: [record failure] except: [record error] else: [record success] finally: try: testcase.tearDown() except: [record error]print(do_test())

다시다시 보는보는 unittest unittest 구조구조

Page 52: Python 테스트 시작하기

의존성이의존성이 있어요있어요..어떻게어떻게 테스트테스트 하죠하죠??

Page 53: Python 테스트 시작하기

의존성이의존성이 있는있는 코드코드

def get_username(user_id): user = db.user.query(user_id = user_id) return user.name

def delete_user(user_id): return db.user.delete(user_id = user_id)

DB DB 가가 있어야있어야 테스트테스트 할할 수수 있음있음

DB DB 에에 테스트전테스트전 데이터를데이터를 넣어넣어 주어야주어야 함함

CI CI 에서는에서는 어떻게어떻게 실행실행 할까할까??

Page 54: Python 테스트 시작하기

테스트는테스트는 여기까지여기까지 하는하는 것것이이 좋겠다좋겠다..

......가가 아니고아니고

Page 55: Python 테스트 시작하기

mock mock을을 사용합시다사용합시다..

Page 56: Python 테스트 시작하기

Mock?!Mock?!

Page 57: Python 테스트 시작하기

MockMock

Page 58: Python 테스트 시작하기

unittest.mockunittest.mock

파이썬 표준 라이브러리 (3.3 부터)이전 버젼은 pip install mockpython object 들을 동적으로 대체하고 사용 결과를 확인 하기 위한 다양한 기능들을 제공

의존성이 있는것들을 실제로 실행시키지 말고 호출 여부, 인터페이스만 확인 하자

Page 59: Python 테스트 시작하기

Monkey PatchMonkey Patch>>> class Class():... def add(self, x, y):... return x + y...>>> inst = Class()>>> def not_exactly_add(self, x, y):... return x * y...>>> Class.add = not_exactly_add>>> inst.add(3, 3)9

런타임에 클래스, 함수등을 변경하는 것

Page 60: Python 테스트 시작하기

mock mock 사용사용 예예>>> from unittest.mock import MagicMock>>> thing = ProductionClass()>>> thing.method = MagicMock(return_value=3)>>> thing.method(3, 4, 5, key='value')3>>> thing.method.assert_called_with(3, 4, 5, key='value')

thing.method 가 monkey patch 되었음이를 테스트에 어떻게 활용 할까?

Page 61: Python 테스트 시작하기

test rmtest rm# Rm.pyimport os

def rm(filename): os.remove(filename)

# test_rm.pyfrom Rm import rm

class RmTestCase(unittest.TestCase):

tmpfilepath = os.path.join(tempfile.gettempdir(), 'temp-testfile')

def setUp(self): with open(self.tmpfilepath, 'w') as f: f.write('Delete me!')

def tearDown(self): if os.path.isfile(self.tmpfilepath): os.remove(self.tmpfilepath)

def test_rm(self): rm(self.tmpfilepath) self.assertFalse(os.path.isfile(self.tmpfilepath), 'Failed to remove the file')

Page 62: Python 테스트 시작하기

첫번째첫번째 Mock test Mock testimport os.pathimport tempfileimport unittestfrom unittest import mock

from Rm import rm

class RmTestCase(unittest.TestCase):

@mock.patch('Rm.os') def test_rm(self, mock_os): rm('/tmp/tmpfile') mock_os.remove.assert_called_with('/tmp/tmpfile')

if __name__ == '__main__': unittest.main()

GoodsetUp, tearDown 이 없어졌음실제로 os.remove 이 호출되지 않았음os.remove 가 호출되었는지는 확인 했음

test_rm

Rm.rm

os.removemock_os.remove

Page 63: Python 테스트 시작하기

어떻게어떻게 된걸까된걸까??# Rm.pyimport os

def rm(filename): print(os.remove) os.remove(filename)

$ python ./test_rm.py <built-in function remove>.----------------------------------------------------------------------Ran 1 test in 0.007s

OK$ python ./mock_rm.py<MagicMock name='os.remove' id='139901238735592'>.----------------------------------------------------------------------Ran 1 test in 0.002s

OK

Page 64: Python 테스트 시작하기

Mock Mock 대상이대상이 되는되는 것것

시간이 오래 걸리는 것값이 변하는 것상태가 유지 되는 것 (다른 테스트에 영향을 주는 것)시스템 콜네트워크로 연결된 것준비하기 복잡한 것

Page 65: Python 테스트 시작하기

Realworld exampleRealworld example

class SimpleFacebook(object):

def __init__(self, oauth_token): self.graph = facebook.GraphAPI(oauth_token)

def post_message(self, message): self.graph.put_object('me', 'feed', message=message)

class SimpleFacebookTestCase(unittest.TestCase):

@mock.patch.object(facebook.GraphAPI, 'put_object', autospect=True) def test_post_message(self, mock_put_object): sf = SimpleFacebook('fake oauth token') sf.post_message('Hello World!')

mock_put_object.assert_called_with('me', 'feed', message='Hello World!')

facebook 이 다운되어도 내 테스트는 실패 하지 않는다.

Page 66: Python 테스트 시작하기

Mock - Mock - 조건조건 확인확인# 24시간이 지난 경우에만 삭제 합니다.def rm(filename): file_modified = datetime.datetime.fromtimestamp(os.path.getmtime(filename)) if datetime.datetime.now() - file_modified > datetime.timedelta(hours=24): os.remove(filename)

class RmTestCase(unittest.TestCase):

@mock.patch('__main__.os') def test_rm(self, mock_os): mock_os.path.getmtime.return_value = time.time() rm('/tmp/tmpfile') self.assertFalse(mock_os.remove.called)

mock_os.path.getmtime.return_value = time.time() - 86400*2 rm('/tmp/tmpfile') mock_os.remove.assert_called_with('/tmp/tmpfile')

보통 분기를 따라가면서 테스트 하기는 쉽지 않음

Page 67: Python 테스트 시작하기

Mock Mock 예외예외# Rm.pyclass MyError(Exception): pass

def rm(filename): try: os.remove(filename) except FileNotFoundError: raise MyError

class RmTestCase(unittest.TestCase):

@mock.patch.object(os, 'remove', side_effect=FileNotFoundError) def test_rm_without_file(self, mock_remove): with self.assertRaises(MyError) as context: rm('not_exist_file')

Exception 이 발생되는 경우를 만들지 않아도 됨

Page 68: Python 테스트 시작하기

그래도그래도 합쳐서합쳐서 테스트는테스트는 해해봐야봐야 하잖아요하잖아요??

Page 69: Python 테스트 시작하기

Integration TestIntegration Test

테스트 환경이 동작하지 않아요.프로덕션 환경이랑 테스트 환경이 다른 것 같은데요?저 지금 테스트 환경에 배포 해도 돼요?제가 지금 테스트 하고 있으니, 다른 분은 나중에 테스트 해주세요.

다른 모듈,서비스 등을 붙여서 그 관계에서의 문제점을 확인하는 과정

Page 70: Python 테스트 시작하기

나만 쓰는 프로덕션 환경과 동일한 테스트 환경이 있으면 좋겠다.

Page 71: Python 테스트 시작하기

Redis Client Redis Client# client.pyimport sysimport redis

class Client(): def __init__(self): self.conn = redis.StrictRedis("redis")

def set(self, key, value): self.conn.set(key, value)

def get(self, key): return self.conn.get(key)

실제 Redis 와 연결했을 때 잘 동작하는지 확인이 필요하다.

# test_client.pyimport unittestfrom client import Client

class ClientTestCase(unittest.TestCase):

def test_with_redis(self): client = Client() client.set('tomato', 2) self.assertEqual(2, int(client.get('tomato')))

Page 72: Python 테스트 시작하기

나만 쓰는 프로덕션 환경과 동일한 테스트 환경이 있으면 좋겠다.

Host Host

Page 73: Python 테스트 시작하기

Docker Docker

Linux Container 를 이용가벼운 VM 이라고 생각하자Host 와 격리시킬 수 있음

https://www.docker.com/

Page 74: Python 테스트 시작하기

Dockerfile Dockerfile

FROM python:3.4MAINTAINER lee ho sung

RUN pip install redis

RUN mkdir /codeADD . /codeWORKDIR /code

CMD python -m unittest discover

Docker 이미지를 정의한다.

Page 75: Python 테스트 시작하기

Docker-compose Docker-compose

Python! 프로젝트복수개의 Docker Container 를 관리

https://github.com/docker/compose

Page 76: Python 테스트 시작하기

docker-compose.ymldocker-compose.yml

client: build: . links: - redisredis: image: redis ports: - 6379:6379

192.168.0.2192.168.0.1

/etc/hosts redis 192.168.0.2

Page 77: Python 테스트 시작하기

docker-compose updocker-compose up

GoodLocalhost 에서 모든 테스트가 가능Host 에 영향 없음CI 에서도 실행 가능

$ docker-compose up redis_1 | 1:M 28 Aug 06:05:53.613 # Server started, Redis version 3.0.3redis_1 | 1:M 28 Aug 06:05:53.613 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. redis_1 | 1:M 28 Aug 06:05:53.613 # WARNING you have Transparent Huge Pages (THP) support enabled redis_1 | 1:M 28 Aug 06:05:53.613 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn redis_1 | 1:M 28 Aug 06:05:53.614 * DB loaded from disk: 0.000 secondsredis_1 | 1:M 28 Aug 06:05:53.614 * The server is now ready to accept connections on port 6379client_1 | .client_1 | ----------------------------------------------------------------------client_1 | Ran 1 test in 1.003sclient_1 | client_1 | OKintegrationtest_client_1 exited with code 0Gracefully stopping... (press Ctrl+C again to force)Stopping integrationtest_redis_1... done$

Page 78: Python 테스트 시작하기

UI UI가가 잘잘 동작하는지동작하는지어떻게어떻게 테스트테스트 하죠하죠??

Page 79: Python 테스트 시작하기

그냥그냥 손으로손으로 합니다합니다

... ... 가가 아니고아니고

Page 80: Python 테스트 시작하기

Selenium Selenium

웹 브라우저 자동화 툴/라이브러리https://github.com/seleniumhq/selenium

from selenium import webdriver

driver = webdriver.Firefox()driver.get("http://www.python.org")assert "Python" in driver.title

Page 81: Python 테스트 시작하기

사람과사람과 동일한동일한 방식으로방식으로!!

Page 82: Python 테스트 시작하기

Test Pycon 2015Test Pycon 2015import unittestfrom selenium import webdriverfrom selenium.webdriver.common.keys import Keys

class PyconUserTestCase(unittest.TestCase):

def setUp(self): self.driver = webdriver.Firefox()

def test_log_in_pycon_2015(self): driver = self.driver driver.get("http://www.pycon.kr/2015/login")

# US1 : 사용자는 Pycon Korea 2015 페이지 제목을 볼 수 있습니다. self.assertIn("PyCon Korea 2015", driver.title)

# US2 : 사용자는 로그인을 할 수 있습니다. # 로그인 하면 "One-time login token ..." 메시지를 볼 수 있습니다. elem = driver.find_element_by_id("id_email") elem.send_keys("[email protected]") elem.send_keys(Keys.RETURN) self.assertIn("One-time login token url was sent to your mail", driver.page_source)

def tearDown(self): self.driver.close()

if __name__ == "__main__": unittest.main(warnings='ignore')

Page 83: Python 테스트 시작하기

어디까지어디까지 테스트테스트 해야해야 하나하나요요??

Page 84: Python 테스트 시작하기

경우에경우에 따라따라 많이많이 다릅니다다릅니다. .

최소 : 테스트 케이스가 메인 로직을 검증 한다.최대 : 개발 시간의 2배 이상을 쓰지 않는다.

이 사이에서 가장 가성비 높은 지점을 찾아내는 것이 좋은 개발자!

Page 85: Python 테스트 시작하기

테스트는테스트는 언제언제 실행실행하나요하나요??

Page 86: Python 테스트 시작하기

문제는문제는 빠르게빠르게 찾을찾을 수록수록 좋습니다좋습니다. . 가능한가능한 자주자주 실행실행 합니다합니다..

개발할 때 commit 할 때push 할 때CI 서버에서오픈 소스를 받자마자

Page 87: Python 테스트 시작하기

좋은좋은 테스트란테스트란

무엇인가요무엇인가요??

Page 88: Python 테스트 시작하기

한번에한번에 하나만하나만 테스트테스트 합니다합니다

실패가실패가 명확해야명확해야 합니다합니다

빠르게빠르게 테스트테스트 되어야되어야 합니다합니다

중복중복 되지되지 않습니다않습니다

자동화자동화 되어야되어야 합니다합니다

독립적이어야독립적이어야 합니다합니다((다른것의다른것의 영향을영향을 받지받지 않습니다않습니다))

Page 89: Python 테스트 시작하기

오늘오늘 무슨무슨 이야기를이야기를 했죠했죠??

Page 90: Python 테스트 시작하기

테스트를테스트를 작성작성 하는하는 이유이유

간단한간단한 테스트테스트

unittestunittest 의의 사용사용

의존성이의존성이 있을있을 때때 mockmock 사용하기사용하기

dockerdocker 사용해서사용해서 통합통합 테스트테스트 하기하기

seleniumselenium을을 사용해서사용해서 웹웹 테스트테스트 하기하기

Page 91: Python 테스트 시작하기

테스트는 힘들지만가치 있는 일입니다

지금 시작하세요!

Page 92: Python 테스트 시작하기

참고참고 자료자료

Page 93: Python 테스트 시작하기

Pycon 2014 Getting Started TestingPython Testing Style GuideAn Introduction to mocking in PythonTest Driven Development with Python

Page 94: Python 테스트 시작하기

감사합니다감사합니다..