SlideShare a Scribd company logo
1 of 94
Download to read offline
Python 테스트 시작하기
이호성
에서 함께 보실 수 있습니다
http://slides.com/hosunglee-1/deck/live
누구세요누구세요??
이호성이호성
Enswers
DevOps
Python, Cloud Computing
함께하는 개발
http://blog.novice.io
2014 Pycon Korea 구경
Pycon 2014 Getting
Started Testing
오늘오늘 발표의발표의 목적은목적은??
테스트에테스트에 관심을관심을 가져보세요가져보세요
테스팅이테스팅이 처음이신처음이신 분들께분들께
다른다른 사람은사람은 어떻게어떻게
테스트테스트 할까할까??
테스팅을테스팅을 이미이미 ((잘잘)) 하고하고
계신계신 분들께분들께
꼭꼭 테스트를테스트를
작성해야작성해야 하나요하나요??
누군가는누군가는 여러분의여러분의 코드를코드를 테테
스트스트 해야해야 합니다합니다
길게길게 보면보면 시간이시간이
절약절약 됩니다됩니다
변경에변경에 대한대한 자신감을자신감을 얻으얻으
세요세요
더더 나은나은 코드를코드를
만들어만들어 줍니다줍니다
PythonPython 은은
컴파일러가컴파일러가 없잖아요없잖아요
어떤어떤 테스트를테스트를
작성해야작성해야 하나요하나요??
수많은수많은 종류의종류의 테스트가테스트가 있습니다있습니다
Testing Types
Installation testing
Compatibility testing
Smoke and sanity testing
Regression testing
Acceptance testing
Alpha testing
Beta testing
Functional vs non-
functional testing
Destructive testing
Software performance
testing
Usability testing
Accessibility testing
Security testing
Internationalization and
localization
Development testing
A/B testing
Concurrent testing
Conformance testing or
type testing
Testing levels
Unit testing
Integration testing
Component interface testing
System testing
Operational Acceptance
testing
D
Whitebox testing
Blackbox testing
Testing methods
Static testing
ynamic testing
단위단위 테스트테스트
(Unit Test)
기능기능 테스트테스트
(Function Test)
개발자 뷰
함수 단위
Mock 을 사용
빠름
더 좋은 코드에 기여
사용자 뷰
요구사항 단위
Fixture 를 사용
느림
퇴근에 기여
일단일단 하나만하나만
보여줘보여줘 봐요봐요
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
첫번째첫번째 테스트테스트 -- 쉘쉘
Python 3.4.0 (default, Jun 19 2015, 14:20:21)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more informati
>>> 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
다시 테스트 하려면?
직접 입력
그래서 잘 된건가?
두번째두번째 테스트테스트 -- 기대값기대값
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.0
With 100 Google @ 176.48: 17648.0, should be 17648.0
With 100 Yahoo @ 36.15: 21263.0, should be 21263.0
세번째세번째 테스트테스트 -- 결과결과 확인확인
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 # Failed
print("With 100 Google @ 176.48: %s, should be 17648.0" % p.cost())
p.buy('Yahoo', 100, 36.15)
assert p.cost() == 21263.0
print("With 100 Yahoo @ 36.15: %s, should be 21263.0" % p.cost())
Good
다시 테스트 할 수 있음
잘 된건지 자동으로 확인 가
능
Bad
왜 틀렸는지 알기 힘듬
두번째 테스트가 실행 되지
않음
Empty portfolio cost: 0.0, should be 0.0
Traceback (most recent call last):
File "portfolio_test2.py", line 6, in <module>
assert p.cost() == 17649.0 # Failed
AssertionError
((개발개발 시작시의시작시의 마음과마음과 달리달리))
테스트를테스트를 지속하지지속하지
못하는못하는 이유이유??
점점점점 복잡해복잡해 진다진다. (. (코드와코드와 함께함께))
반복되는반복되는 노력이노력이 아깝다아깝다..
frameworkframework 의의 사용을사용을
고민해고민해 볼볼 때때!!
어떻게어떻게 테스트테스트 해야해야 할지할지 모른다모른다..
Unittest !Unittest !
(들어봤지만 친하지 않은 그대)
unittestunittest
파이썬파이썬 표준표준 라이브러리라이브러리
테스트테스트 자동화자동화
테스트테스트 구조화구조화
테스트테스트 결과결과 보고보고
TestCaseTestCase
독립적인독립적인 테스트테스트 단위단위
TestSuiteTestSuite
TestCaseTestCase 의의 묶음묶음
TestRunnerTestRunner
테스트를테스트를 실제로실제로 수행하고수행하고
결과를결과를 레포트레포트 한다한다..
테스트테스트 흐름흐름
First UnittestFirst Unittest
# portfolio_test3.py
import unittest
from 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
테스트가테스트가 실패실패 했어요했어요!!
- 실패하라고 테스트는 만드는 겁니다.
테스트테스트 추가추가
# portfolio_test4.py
import unittest
from 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()
UnittestUnittest 실패실패
$ 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
테스트 실패가 다른 테스트에 영향을 미치지 않음
실패한 위치와 이유를 알 수 있음
실패한실패한 테스트만테스트만
다시다시 돌리고돌리고 싶어요싶어요..
TestTest 고르기고르기
$ python portfolio_test4.py PortfolioTestCase.test_google_yahoo
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 1 test in 0.005s
FAILED (failures=1)
Good
원하는 테스트만 빠르게 실행 해 볼 수 있음
출력이 간단해짐
테스트테스트 파일이파일이 늘어나면늘어나면 어어
떻게떻게 한꺼번에한꺼번에 실행실행 시키시키
죠죠??
테스트테스트 한꺼번에한꺼번에 실행하기실행하기
$ python -m unittest discover --help
Usage: 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
복수개의 파일을 한꺼번에 테스트를 실행 할 수있음
ExceptionException 도도 테스트테스트
할할 수수 있나요있나요??
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
ExceptionException 을을 발생시키는발생시키는 테스트테스트
import unittest
from 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)
ExceptionException 을을 테스트테스트
import unittest
from 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
TestCase.assert*TestCase.assert* 는는
어떤것들이어떤것들이 있나요있나요??
적절한적절한 assertassert 의의 사용은사용은 테테
스트스트 코드를코드를 간단하게간단하게 합니합니
다다..
TestCase.assert*TestCase.assert*
Unittest Assert Methods
테스트마다테스트마다 반복되는반복되는 초기초기
화는화는 어떻게어떻게
관리하나요관리하나요??
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()
테스트테스트 흐름흐름
비슷한비슷한 테스트를테스트를
반복반복 하고하고
싶을싶을 때는때는??
반복되는반복되는 테스트테스트 실행하기실행하기
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 까지 짝수인지를 테스트 합니다.
SubtestSubtest 실행실행 결과결과
$ 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 에 추가 되었음
UnittestUnittest 복잡하군요복잡하군요
# test_runner.py
def 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())
다시다시 보는보는 unittestunittest 구조구조
의존성이의존성이 있어요있어요..
어떻게어떻게 테스트테스트 하죠하죠??
의존성이의존성이 있는있는 코드코드
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)
DBDB 가가 있어야있어야 테스트테스트 할할 수수 있음있음
DBDB 에에 테스트전테스트전 데이터를데이터를 넣어넣어 주어야주어야 함함
CICI 에서는에서는 어떻게어떻게 실행실행 할까할까??
테스트는테스트는 여기까지여기까지 하는하는 것것
이이 좋겠다좋겠다..
......가가 아니고아니고
mockmock
을을 사용합시다사용합시다..
Mock?!Mock?!
MockMock
unittest.mockunittest.mock
파이썬 표준 라이브러리 (3.3 부터)
이전 버젼은 pip install mock
python object 들을 동적으로 대체하고
사용 결과를 확인 하기 위한 다양한 기능들을 제공
의존성이 있는것들을 실제로 실행시키지 말고 호출 여
부, 인터페이스만 확인 하자
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
런타임에 클래스, 함수등을 변경하는 것
mockmock 사용사용 예예
>>> 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 되었음
이를 테스트에 어떻게 활용 할까?
test rmtest rm
# Rm.py
import os
def rm(filename):
os.remove(filename)
# test_rm.py
from 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')
첫번째첫번째 Mock testMock test
import os.path
import tempfile
import unittest
from 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()
Good
setUp, tearDown 이 없어졌음
실제로 os.remove 이 호출되지 않았음
os.remove 가 호출되었는지는 확인 했음
test_rm
Rm.rm
os.remove
mock_os.
remove
어떻게어떻게 된걸까된걸까??
# Rm.py
import 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
MockMock 대상이대상이 되는되는 것것
시간이 오래 걸리는 것
값이 변하는 것
상태가 유지 되는 것
(다른 테스트에 영향을 주는 것)
시스템 콜
네트워크로 연결된 것
준비하기 복잡한 것
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 이 다운되어도 내 테스트는 실패 하지 않는다.
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')
보통 분기를 따라가면서 테스트 하기는 쉽지 않음
MockMock 예외예외
# Rm.py
class 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 이 발생되는 경우를 만들지 않아도 됨
그래도그래도 합쳐서합쳐서 테스트는테스트는 해해
봐야봐야 하잖아요하잖아요??
Integration TestIntegration Test
테스트 환경이 동작하지 않아요.
프로덕션 환경이랑 테스트 환경이 다른 것 같은데요?
저 지금 테스트 환경에 배포 해도 돼요?
제가 지금 테스트 하고 있으니, 다른 분은 나중에 테스트 해주세
요.
다른 모듈,서비스 등을 붙여서 그 관계에서의
문제점을 확인하는 과정
나만 쓰는 프로덕션 환경과 동일한 테스
트 환경이 있으면 좋겠다.
Redis ClientRedis Client
# client.py
import sys
import 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.py
import unittest
from 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')))
나만 쓰는 프로덕션 환경과 동일한 테스
트 환경이 있으면 좋겠다.
Host Host
DockerDocker
Linux Container 를 이용
가벼운 VM 이라고 생각하자
Host 와 격리시킬 수 있음
https://www.docker.com/
DockerfileDockerfile
FROM python:3.4
MAINTAINER lee ho sung
RUN pip install redis
RUN mkdir /code
ADD . /code
WORKDIR /code
CMD python -m unittest discover
Docker 이미지를 정의한다.
Docker-composeDocker-compose
Python! 프로젝트
복수개의 Docker Container 를 관리
https://github.com/docker/compose
docker-compose.ymldocker-compose.yml
client:
build: .
links:
- redis
redis:
image: redis
ports:
- 6379:6379
192.168.0.2192.168.0.1
/etc/hosts
redis 192.168.0.2
docker-compose updocker-compose up
Good
Localhost 에서 모든 테스트가 가능
Host 에 영향 없음
CI 에서도 실행 가능
$ docker-compose up
redis_1 | 1:M 28 Aug 06:05:53.613 # Server started, Redis version 3.0.3
redis_1 | 1:M 28 Aug 06:05:53.613 # WARNING overcommit_memory is set to 0! Background save may fai
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 bec
redis_1 | 1:M 28 Aug 06:05:53.614 * DB loaded from disk: 0.000 seconds
redis_1 | 1:M 28 Aug 06:05:53.614 * The server is now ready to accept connections on port 6379
client_1 | .
client_1 | ----------------------------------------------------------------------
client_1 | Ran 1 test in 1.003s
client_1 |
client_1 | OK
integrationtest_client_1 exited with code 0
Gracefully stopping... (press Ctrl+C again to force)
Stopping integrationtest_redis_1... done
$
UIUI가가 잘잘 동작하는지동작하는지
어떻게어떻게 테스트테스트 하죠하죠??
그냥그냥 손으로손으로 합니다합니다
...... 가가 아니고아니고
SeleniumSelenium
웹 브라우저 자동화 툴/라이브러리
https://github.com/seleniumhq/selenium
from selenium import webdriver
driver = webdriver.Firefox()
driver.get("http://www.python.org")
assert "Python" in driver.title
사람과사람과 동일한동일한 방식으로방식으로!!
Test Pycon 2015Test Pycon 2015
import unittest
from selenium import webdriver
from 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-me@gmail.com")
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')
어디까지어디까지 테스트테스트 해야해야 하나하나
요요??
경우에경우에 따라따라 많이많이 다릅니다다릅니다..
최소 : 테스트 케이스가 메인 로직을 검증 한다.
최대 : 개발 시간의 2배 이상을 쓰지 않는다.
이 사이에서 가장 가성비 높은 지점을
찾아내는 것이 좋은 개발자!
테스트는테스트는 언제언제 실행실행
하나요하나요??
문제는문제는 빠르게빠르게 찾을찾을 수록수록 좋습니다좋습니다..
가능한가능한 자주자주 실행실행 합니다합니다..
개발할 때
commit 할 때
push 할 때
CI 서버에서
오픈 소스를 받자마자
좋은좋은 테스트란테스트란
무엇인가요무엇인가요??
한번에한번에 하나만하나만 테스트테스트 합니다합니다
실패가실패가 명확해야명확해야 합니다합니다
빠르게빠르게 테스트테스트 되어야되어야 합니다합니다
중복중복 되지되지 않습니다않습니다
자동화자동화 되어야되어야 합니다합니다
독립적이어야독립적이어야 합니다합니다
((다른것의다른것의 영향을영향을 받지받지 않습니다않습니다))
오늘오늘 무슨무슨 이야기를이야기를 했죠했죠??
테스트를테스트를 작성작성 하는하는 이유이유
간단한간단한 테스트테스트
unittestunittest 의의 사용사용
의존성이의존성이 있을있을 때때 mockmock 사용하기사용하기
dockerdocker 사용해서사용해서 통합통합 테스트테스트 하기하기
seleniumselenium을을 사용해서사용해서 웹웹 테스트테스트 하기하기
테스트는 힘들지만
가치 있는 일입니다
지금 시작하세요!
참고참고 자료자료
Pycon 2014 Getting Started Testing
Python Testing Style Guide
An Introduction to mocking in Python
Test Driven Development with Python
감사합니다감사합니다..

More Related Content

What's hot

고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들Chris Ohk
 
장재화, Replay system, NDC2011
장재화, Replay system, NDC2011장재화, Replay system, NDC2011
장재화, Replay system, NDC2011재화 장
 
KGC2010 - 낡은 코드에 단위테스트 넣기
KGC2010 - 낡은 코드에 단위테스트 넣기KGC2010 - 낡은 코드에 단위테스트 넣기
KGC2010 - 낡은 코드에 단위테스트 넣기Ryan Park
 
Karate - powerful and simple framework for REST API automation testing
Karate - powerful and simple framework for REST API automation testingKarate - powerful and simple framework for REST API automation testing
Karate - powerful and simple framework for REST API automation testingRoman Liubun
 
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)SangIn Choung
 
Py.test
Py.testPy.test
Py.testsoasme
 
취미로 엔진 만들기
취미로 엔진 만들기취미로 엔진 만들기
취미로 엔진 만들기Jiho Choi
 
SDC 3rd 최흥배님 - Boost.multi_index 사용하기
SDC 3rd 최흥배님 - Boost.multi_index 사용하기SDC 3rd 최흥배님 - Boost.multi_index 사용하기
SDC 3rd 최흥배님 - Boost.multi_index 사용하기OnGameServer
 
[MLOps KR 행사] MLOps 춘추 전국 시대 정리(210605)
[MLOps KR 행사] MLOps 춘추 전국 시대 정리(210605)[MLOps KR 행사] MLOps 춘추 전국 시대 정리(210605)
[MLOps KR 행사] MLOps 춘추 전국 시대 정리(210605)Seongyun Byeon
 
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)Heungsub Lee
 
[143] Modern C++ 무조건 써야 해?
[143] Modern C++ 무조건 써야 해?[143] Modern C++ 무조건 써야 해?
[143] Modern C++ 무조건 써야 해?NAVER D2
 
Quic을 이용한 네트워크 성능 개선
 Quic을 이용한 네트워크 성능 개선 Quic을 이용한 네트워크 성능 개선
Quic을 이용한 네트워크 성능 개선NAVER D2
 
Getting started with Svelte Presentation
Getting started with Svelte PresentationGetting started with Svelte Presentation
Getting started with Svelte PresentationKnoldus Inc.
 
1인개발자가되기전알아야할것들
1인개발자가되기전알아야할것들1인개발자가되기전알아야할것들
1인개발자가되기전알아야할것들Jinsub Jung
 
[XECon2016] A-4 조정현 GitHub + Jenkins + Docker로 자동배포 시스템 구축하기
[XECon2016] A-4 조정현 GitHub + Jenkins + Docker로 자동배포 시스템 구축하기[XECon2016] A-4 조정현 GitHub + Jenkins + Docker로 자동배포 시스템 구축하기
[XECon2016] A-4 조정현 GitHub + Jenkins + Docker로 자동배포 시스템 구축하기XpressEngine
 
Karate for Complex Web-Service API Testing by Peter Thomas
Karate for Complex Web-Service API Testing by Peter ThomasKarate for Complex Web-Service API Testing by Peter Thomas
Karate for Complex Web-Service API Testing by Peter Thomasintuit_india
 
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - PerfornanceGCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance상현 조
 
성장을 좋아하는 사람이, 성장하고 싶은 사람에게
성장을 좋아하는 사람이, 성장하고 싶은 사람에게성장을 좋아하는 사람이, 성장하고 싶은 사람에게
성장을 좋아하는 사람이, 성장하고 싶은 사람에게Seongyun Byeon
 
게임 애셋 스트리밍 패치
게임 애셋 스트리밍 패치게임 애셋 스트리밍 패치
게임 애셋 스트리밍 패치Seungjae Lee
 

What's hot (20)

고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
고려대학교 컴퓨터학과 특강 - 대학생 때 알았더라면 좋았을 것들
 
장재화, Replay system, NDC2011
장재화, Replay system, NDC2011장재화, Replay system, NDC2011
장재화, Replay system, NDC2011
 
KGC2010 - 낡은 코드에 단위테스트 넣기
KGC2010 - 낡은 코드에 단위테스트 넣기KGC2010 - 낡은 코드에 단위테스트 넣기
KGC2010 - 낡은 코드에 단위테스트 넣기
 
Karate - powerful and simple framework for REST API automation testing
Karate - powerful and simple framework for REST API automation testingKarate - powerful and simple framework for REST API automation testing
Karate - powerful and simple framework for REST API automation testing
 
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
[기본과정] 코드 테스트와 커버리지 기본 교육(개념)
 
Py.test
Py.testPy.test
Py.test
 
취미로 엔진 만들기
취미로 엔진 만들기취미로 엔진 만들기
취미로 엔진 만들기
 
SDC 3rd 최흥배님 - Boost.multi_index 사용하기
SDC 3rd 최흥배님 - Boost.multi_index 사용하기SDC 3rd 최흥배님 - Boost.multi_index 사용하기
SDC 3rd 최흥배님 - Boost.multi_index 사용하기
 
[MLOps KR 행사] MLOps 춘추 전국 시대 정리(210605)
[MLOps KR 행사] MLOps 춘추 전국 시대 정리(210605)[MLOps KR 행사] MLOps 춘추 전국 시대 정리(210605)
[MLOps KR 행사] MLOps 춘추 전국 시대 정리(210605)
 
파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)파이썬 생존 안내서 (자막)
파이썬 생존 안내서 (자막)
 
[143] Modern C++ 무조건 써야 해?
[143] Modern C++ 무조건 써야 해?[143] Modern C++ 무조건 써야 해?
[143] Modern C++ 무조건 써야 해?
 
Quic을 이용한 네트워크 성능 개선
 Quic을 이용한 네트워크 성능 개선 Quic을 이용한 네트워크 성능 개선
Quic을 이용한 네트워크 성능 개선
 
Svelte
SvelteSvelte
Svelte
 
Getting started with Svelte Presentation
Getting started with Svelte PresentationGetting started with Svelte Presentation
Getting started with Svelte Presentation
 
1인개발자가되기전알아야할것들
1인개발자가되기전알아야할것들1인개발자가되기전알아야할것들
1인개발자가되기전알아야할것들
 
[XECon2016] A-4 조정현 GitHub + Jenkins + Docker로 자동배포 시스템 구축하기
[XECon2016] A-4 조정현 GitHub + Jenkins + Docker로 자동배포 시스템 구축하기[XECon2016] A-4 조정현 GitHub + Jenkins + Docker로 자동배포 시스템 구축하기
[XECon2016] A-4 조정현 GitHub + Jenkins + Docker로 자동배포 시스템 구축하기
 
Karate for Complex Web-Service API Testing by Peter Thomas
Karate for Complex Web-Service API Testing by Peter ThomasKarate for Complex Web-Service API Testing by Peter Thomas
Karate for Complex Web-Service API Testing by Peter Thomas
 
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - PerfornanceGCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
GCGC- CGCII 서버 엔진에 적용된 기술 (2) - Perfornance
 
성장을 좋아하는 사람이, 성장하고 싶은 사람에게
성장을 좋아하는 사람이, 성장하고 싶은 사람에게성장을 좋아하는 사람이, 성장하고 싶은 사람에게
성장을 좋아하는 사람이, 성장하고 싶은 사람에게
 
게임 애셋 스트리밍 패치
게임 애셋 스트리밍 패치게임 애셋 스트리밍 패치
게임 애셋 스트리밍 패치
 

Viewers also liked

Python Unittest
Python UnittestPython Unittest
Python Unittest명규 최
 
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)Kyoungchan Lee
 
Python 이해하기 20160815
Python 이해하기 20160815Python 이해하기 20160815
Python 이해하기 20160815Yong Joon Moon
 
도도와 파이썬: 좋은 선택과 나쁜 선택
도도와 파이썬: 좋은 선택과 나쁜 선택도도와 파이썬: 좋은 선택과 나쁜 선택
도도와 파이썬: 좋은 선택과 나쁜 선택Jc Kim
 
Python on Android
Python on AndroidPython on Android
Python on Android용 최
 
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보Young Hoo Kim
 
Selenium을 이용한 동적 사이트 크롤러 만들기
Selenium을 이용한 동적 사이트 크롤러 만들기Selenium을 이용한 동적 사이트 크롤러 만들기
Selenium을 이용한 동적 사이트 크롤러 만들기Gyuhyeon Jeon
 
Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.Kyoung Up Jung
 
141118 최창원 웹크롤러제작
141118 최창원 웹크롤러제작141118 최창원 웹크롤러제작
141118 최창원 웹크롤러제작Changwon Choe
 
테스트자동화 성공전략
테스트자동화 성공전략테스트자동화 성공전략
테스트자동화 성공전략SangIn Choung
 
Jamin's portfolio for camp mobile_compact.version
Jamin's portfolio for camp mobile_compact.versionJamin's portfolio for camp mobile_compact.version
Jamin's portfolio for camp mobile_compact.versionJamin Park
 
Python 표준 라이브러리
Python 표준 라이브러리Python 표준 라이브러리
Python 표준 라이브러리용 최
 
Automating Django Functional Tests Using Selenium on Cloud
Automating Django Functional Tests Using Selenium on CloudAutomating Django Functional Tests Using Selenium on Cloud
Automating Django Functional Tests Using Selenium on CloudJonghyun Park
 
샌드박스
샌드박스샌드박스
샌드박스Baekjoon Choi
 
Portfolio - Creative Copywriter
Portfolio - Creative CopywriterPortfolio - Creative Copywriter
Portfolio - Creative CopywriterTatiana Zubkova
 
The Copywriting Portfolio of Henry Jian (Sample #2)
The Copywriting Portfolio of Henry Jian (Sample #2)The Copywriting Portfolio of Henry Jian (Sample #2)
The Copywriting Portfolio of Henry Jian (Sample #2)Henry Jian
 
REST Easy with Django-Rest-Framework
REST Easy with Django-Rest-FrameworkREST Easy with Django-Rest-Framework
REST Easy with Django-Rest-FrameworkMarcel Chastain
 
[동유럽] 동유럽 여행안내서 (2012)
[동유럽] 동유럽 여행안내서 (2012)[동유럽] 동유럽 여행안내서 (2012)
[동유럽] 동유럽 여행안내서 (2012)Mal-Yong Yoon
 

Viewers also liked (20)

Python Unittest
Python UnittestPython Unittest
Python Unittest
 
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
GopherCon Korea 2015 - Python 개발자를 위한 Go (이경찬)
 
Python 이해하기 20160815
Python 이해하기 20160815Python 이해하기 20160815
Python 이해하기 20160815
 
Ajax in Django
Ajax in DjangoAjax in Django
Ajax in Django
 
도도와 파이썬: 좋은 선택과 나쁜 선택
도도와 파이썬: 좋은 선택과 나쁜 선택도도와 파이썬: 좋은 선택과 나쁜 선택
도도와 파이썬: 좋은 선택과 나쁜 선택
 
Python on Android
Python on AndroidPython on Android
Python on Android
 
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
PyCon 12월 세미나 - 실전 파이썬 프로그래밍 책 홍보
 
Selenium을 이용한 동적 사이트 크롤러 만들기
Selenium을 이용한 동적 사이트 크롤러 만들기Selenium을 이용한 동적 사이트 크롤러 만들기
Selenium을 이용한 동적 사이트 크롤러 만들기
 
Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.Django, 저는 이렇게 씁니다.
Django, 저는 이렇게 씁니다.
 
141118 최창원 웹크롤러제작
141118 최창원 웹크롤러제작141118 최창원 웹크롤러제작
141118 최창원 웹크롤러제작
 
테스트자동화 성공전략
테스트자동화 성공전략테스트자동화 성공전략
테스트자동화 성공전략
 
Jamin's portfolio for camp mobile_compact.version
Jamin's portfolio for camp mobile_compact.versionJamin's portfolio for camp mobile_compact.version
Jamin's portfolio for camp mobile_compact.version
 
Python 표준 라이브러리
Python 표준 라이브러리Python 표준 라이브러리
Python 표준 라이브러리
 
Testing In Django
Testing In DjangoTesting In Django
Testing In Django
 
Automating Django Functional Tests Using Selenium on Cloud
Automating Django Functional Tests Using Selenium on CloudAutomating Django Functional Tests Using Selenium on Cloud
Automating Django Functional Tests Using Selenium on Cloud
 
샌드박스
샌드박스샌드박스
샌드박스
 
Portfolio - Creative Copywriter
Portfolio - Creative CopywriterPortfolio - Creative Copywriter
Portfolio - Creative Copywriter
 
The Copywriting Portfolio of Henry Jian (Sample #2)
The Copywriting Portfolio of Henry Jian (Sample #2)The Copywriting Portfolio of Henry Jian (Sample #2)
The Copywriting Portfolio of Henry Jian (Sample #2)
 
REST Easy with Django-Rest-Framework
REST Easy with Django-Rest-FrameworkREST Easy with Django-Rest-Framework
REST Easy with Django-Rest-Framework
 
[동유럽] 동유럽 여행안내서 (2012)
[동유럽] 동유럽 여행안내서 (2012)[동유럽] 동유럽 여행안내서 (2012)
[동유럽] 동유럽 여행안내서 (2012)
 

Similar to Python 테스트 시작하기

테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)Suwon Chae
 
Advanced Python Testing Techniques (Pycon KR 2019) [Korean Ver.]
Advanced Python Testing Techniques (Pycon KR 2019) [Korean Ver.]Advanced Python Testing Techniques (Pycon KR 2019) [Korean Ver.]
Advanced Python Testing Techniques (Pycon KR 2019) [Korean Ver.]Jaeman An
 
[방송통신대 컴퓨터과학과] C++ 프로그래밍 과제물 작성
[방송통신대 컴퓨터과학과] C++ 프로그래밍 과제물 작성[방송통신대 컴퓨터과학과] C++ 프로그래밍 과제물 작성
[방송통신대 컴퓨터과학과] C++ 프로그래밍 과제물 작성Lee Sang-Ho
 
C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기Heo Seungwook
 
10장 결과 검증
10장 결과 검증10장 결과 검증
10장 결과 검증dagri82
 
Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018KyungHo Jung
 
Javascript개발자의 눈으로 python 들여다보기
Javascript개발자의 눈으로 python 들여다보기Javascript개발자의 눈으로 python 들여다보기
Javascript개발자의 눈으로 python 들여다보기지수 윤
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDSuwon Chae
 
[2011 04 11]mock_object 소개
[2011 04 11]mock_object 소개[2011 04 11]mock_object 소개
[2011 04 11]mock_object 소개Jong Pil Won
 
자바 테스트 자동화
자바 테스트 자동화자바 테스트 자동화
자바 테스트 자동화Sungchul Park
 
파이썬 쪼렙 탈출 1주차
파이썬 쪼렙 탈출 1주차 파이썬 쪼렙 탈출 1주차
파이썬 쪼렙 탈출 1주차 건환 손
 
Robot framework 을 이용한 기능 테스트 자동화
Robot framework 을 이용한 기능 테스트 자동화Robot framework 을 이용한 기능 테스트 자동화
Robot framework 을 이용한 기능 테스트 자동화Jaehoon Oh
 
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅NAVER D2
 
Refactoring - Chapter 8.2
Refactoring - Chapter 8.2Refactoring - Chapter 8.2
Refactoring - Chapter 8.2Ji Ung Lee
 
시작하자 단위테스트
시작하자 단위테스트시작하자 단위테스트
시작하자 단위테스트YongEun Choi
 
코딩인카페 C&JAVA 기초과정 C프로그래밍(3)
코딩인카페 C&JAVA 기초과정 C프로그래밍(3)코딩인카페 C&JAVA 기초과정 C프로그래밍(3)
코딩인카페 C&JAVA 기초과정 C프로그래밍(3)유익아카데미
 

Similar to Python 테스트 시작하기 (20)

테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
테스트 가능한 소프트웨어 설계와 TDD작성 패턴 (Testable design and TDD)
 
Advanced Python Testing Techniques (Pycon KR 2019) [Korean Ver.]
Advanced Python Testing Techniques (Pycon KR 2019) [Korean Ver.]Advanced Python Testing Techniques (Pycon KR 2019) [Korean Ver.]
Advanced Python Testing Techniques (Pycon KR 2019) [Korean Ver.]
 
[방송통신대 컴퓨터과학과] C++ 프로그래밍 과제물 작성
[방송통신대 컴퓨터과학과] C++ 프로그래밍 과제물 작성[방송통신대 컴퓨터과학과] C++ 프로그래밍 과제물 작성
[방송통신대 컴퓨터과학과] C++ 프로그래밍 과제물 작성
 
C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기C++ 프로젝트에 단위 테스트 도입하기
C++ 프로젝트에 단위 테스트 도입하기
 
Tdd 4장
Tdd 4장Tdd 4장
Tdd 4장
 
10장 결과 검증
10장 결과 검증10장 결과 검증
10장 결과 검증
 
Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018Droid knights android test @Droid Knights 2018
Droid knights android test @Droid Knights 2018
 
JUnit & AssertJ
JUnit & AssertJJUnit & AssertJ
JUnit & AssertJ
 
Javascript개발자의 눈으로 python 들여다보기
Javascript개발자의 눈으로 python 들여다보기Javascript개발자의 눈으로 python 들여다보기
Javascript개발자의 눈으로 python 들여다보기
 
ES6 for Node.js Study 5주차
ES6 for Node.js Study 5주차ES6 for Node.js Study 5주차
ES6 for Node.js Study 5주차
 
TDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDDTDD&Refactoring Day 03: TDD
TDD&Refactoring Day 03: TDD
 
[2011 04 11]mock_object 소개
[2011 04 11]mock_object 소개[2011 04 11]mock_object 소개
[2011 04 11]mock_object 소개
 
자바 테스트 자동화
자바 테스트 자동화자바 테스트 자동화
자바 테스트 자동화
 
파이썬 쪼렙 탈출 1주차
파이썬 쪼렙 탈출 1주차 파이썬 쪼렙 탈출 1주차
파이썬 쪼렙 탈출 1주차
 
Robot framework 을 이용한 기능 테스트 자동화
Robot framework 을 이용한 기능 테스트 자동화Robot framework 을 이용한 기능 테스트 자동화
Robot framework 을 이용한 기능 테스트 자동화
 
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅[D2 오픈세미나]5.robolectric 안드로이드 테스팅
[D2 오픈세미나]5.robolectric 안드로이드 테스팅
 
Refactoring - Chapter 8.2
Refactoring - Chapter 8.2Refactoring - Chapter 8.2
Refactoring - Chapter 8.2
 
시작하자 단위테스트
시작하자 단위테스트시작하자 단위테스트
시작하자 단위테스트
 
IPython
IPythonIPython
IPython
 
코딩인카페 C&JAVA 기초과정 C프로그래밍(3)
코딩인카페 C&JAVA 기초과정 C프로그래밍(3)코딩인카페 C&JAVA 기초과정 C프로그래밍(3)
코딩인카페 C&JAVA 기초과정 C프로그래밍(3)
 

Python 테스트 시작하기

  • 1. Python 테스트 시작하기 이호성 에서 함께 보실 수 있습니다 http://slides.com/hosunglee-1/deck/live
  • 3. 이호성이호성 Enswers DevOps Python, Cloud Computing 함께하는 개발 http://blog.novice.io 2014 Pycon Korea 구경 Pycon 2014 Getting Started Testing
  • 6. 다른다른 사람은사람은 어떻게어떻게 테스트테스트 할까할까?? 테스팅을테스팅을 이미이미 ((잘잘)) 하고하고 계신계신 분들께분들께
  • 8. 누군가는누군가는 여러분의여러분의 코드를코드를 테테 스트스트 해야해야 합니다합니다
  • 14. 수많은수많은 종류의종류의 테스트가테스트가 있습니다있습니다 Testing Types Installation testing Compatibility testing Smoke and sanity testing Regression testing Acceptance testing Alpha testing Beta testing Functional vs non- functional testing Destructive testing Software performance testing Usability testing Accessibility testing Security testing Internationalization and localization Development testing A/B testing Concurrent testing Conformance testing or type testing Testing levels Unit testing Integration testing Component interface testing System testing Operational Acceptance testing D Whitebox testing Blackbox testing Testing methods Static testing ynamic testing
  • 15. 단위단위 테스트테스트 (Unit Test) 기능기능 테스트테스트 (Function Test) 개발자 뷰 함수 단위 Mock 을 사용 빠름 더 좋은 코드에 기여 사용자 뷰 요구사항 단위 Fixture 를 사용 느림 퇴근에 기여
  • 17. 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
  • 18. 첫번째첫번째 테스트테스트 -- 쉘쉘 Python 3.4.0 (default, Jun 19 2015, 14:20:21) [GCC 4.8.2] on linux Type "help", "copyright", "credits" or "license" for more informati >>> 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 다시 테스트 하려면? 직접 입력 그래서 잘 된건가?
  • 19. 두번째두번째 테스트테스트 -- 기대값기대값 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.0 With 100 Google @ 176.48: 17648.0, should be 17648.0 With 100 Yahoo @ 36.15: 21263.0, should be 21263.0
  • 20. 세번째세번째 테스트테스트 -- 결과결과 확인확인 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 # Failed print("With 100 Google @ 176.48: %s, should be 17648.0" % p.cost()) p.buy('Yahoo', 100, 36.15) assert p.cost() == 21263.0 print("With 100 Yahoo @ 36.15: %s, should be 21263.0" % p.cost()) Good 다시 테스트 할 수 있음 잘 된건지 자동으로 확인 가 능 Bad 왜 틀렸는지 알기 힘듬 두번째 테스트가 실행 되지 않음 Empty portfolio cost: 0.0, should be 0.0 Traceback (most recent call last): File "portfolio_test2.py", line 6, in <module> assert p.cost() == 17649.0 # Failed AssertionError
  • 21. ((개발개발 시작시의시작시의 마음과마음과 달리달리)) 테스트를테스트를 지속하지지속하지 못하는못하는 이유이유??
  • 22. 점점점점 복잡해복잡해 진다진다. (. (코드와코드와 함께함께)) 반복되는반복되는 노력이노력이 아깝다아깝다.. frameworkframework 의의 사용을사용을 고민해고민해 볼볼 때때!! 어떻게어떻게 테스트테스트 해야해야 할지할지 모른다모른다..
  • 23. Unittest !Unittest ! (들어봤지만 친하지 않은 그대)
  • 24. unittestunittest 파이썬파이썬 표준표준 라이브러리라이브러리 테스트테스트 자동화자동화 테스트테스트 구조화구조화 테스트테스트 결과결과 보고보고
  • 29. First UnittestFirst Unittest # portfolio_test3.py import unittest from 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
  • 30. 테스트가테스트가 실패실패 했어요했어요!! - 실패하라고 테스트는 만드는 겁니다.
  • 31. 테스트테스트 추가추가 # portfolio_test4.py import unittest from 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()
  • 32. UnittestUnittest 실패실패 $ 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 테스트 실패가 다른 테스트에 영향을 미치지 않음 실패한 위치와 이유를 알 수 있음
  • 34. TestTest 고르기고르기 $ python portfolio_test4.py PortfolioTestCase.test_google_yahoo 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 1 test in 0.005s FAILED (failures=1) Good 원하는 테스트만 빠르게 실행 해 볼 수 있음 출력이 간단해짐
  • 35. 테스트테스트 파일이파일이 늘어나면늘어나면 어어 떻게떻게 한꺼번에한꺼번에 실행실행 시키시키 죠죠??
  • 36. 테스트테스트 한꺼번에한꺼번에 실행하기실행하기 $ python -m unittest discover --help Usage: 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 복수개의 파일을 한꺼번에 테스트를 실행 할 수있음
  • 38. 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
  • 39. ExceptionException 을을 발생시키는발생시키는 테스트테스트 import unittest from 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)
  • 40. ExceptionException 을을 테스트테스트 import unittest from 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
  • 42. 적절한적절한 assertassert 의의 사용은사용은 테테 스트스트 코드를코드를 간단하게간단하게 합니합니 다다..
  • 44. 테스트마다테스트마다 반복되는반복되는 초기초기 화는화는 어떻게어떻게 관리하나요관리하나요??
  • 45. 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()
  • 48. 반복되는반복되는 테스트테스트 실행하기실행하기 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 까지 짝수인지를 테스트 합니다.
  • 49. SubtestSubtest 실행실행 결과결과 $ 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 에 추가 되었음
  • 51. # test_runner.py def 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()) 다시다시 보는보는 unittestunittest 구조구조
  • 53. 의존성이의존성이 있는있는 코드코드 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) DBDB 가가 있어야있어야 테스트테스트 할할 수수 있음있음 DBDB 에에 테스트전테스트전 데이터를데이터를 넣어넣어 주어야주어야 함함 CICI 에서는에서는 어떻게어떻게 실행실행 할까할까??
  • 54. 테스트는테스트는 여기까지여기까지 하는하는 것것 이이 좋겠다좋겠다.. ......가가 아니고아니고
  • 58. unittest.mockunittest.mock 파이썬 표준 라이브러리 (3.3 부터) 이전 버젼은 pip install mock python object 들을 동적으로 대체하고 사용 결과를 확인 하기 위한 다양한 기능들을 제공 의존성이 있는것들을 실제로 실행시키지 말고 호출 여 부, 인터페이스만 확인 하자
  • 59. 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 런타임에 클래스, 함수등을 변경하는 것
  • 60. mockmock 사용사용 예예 >>> 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 되었음 이를 테스트에 어떻게 활용 할까?
  • 61. test rmtest rm # Rm.py import os def rm(filename): os.remove(filename) # test_rm.py from 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')
  • 62. 첫번째첫번째 Mock testMock test import os.path import tempfile import unittest from 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() Good setUp, tearDown 이 없어졌음 실제로 os.remove 이 호출되지 않았음 os.remove 가 호출되었는지는 확인 했음 test_rm Rm.rm os.remove mock_os. remove
  • 63. 어떻게어떻게 된걸까된걸까?? # Rm.py import 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
  • 64. MockMock 대상이대상이 되는되는 것것 시간이 오래 걸리는 것 값이 변하는 것 상태가 유지 되는 것 (다른 테스트에 영향을 주는 것) 시스템 콜 네트워크로 연결된 것 준비하기 복잡한 것
  • 65. 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 이 다운되어도 내 테스트는 실패 하지 않는다.
  • 66. 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') 보통 분기를 따라가면서 테스트 하기는 쉽지 않음
  • 67. MockMock 예외예외 # Rm.py class 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 이 발생되는 경우를 만들지 않아도 됨
  • 68. 그래도그래도 합쳐서합쳐서 테스트는테스트는 해해 봐야봐야 하잖아요하잖아요??
  • 69. Integration TestIntegration Test 테스트 환경이 동작하지 않아요. 프로덕션 환경이랑 테스트 환경이 다른 것 같은데요? 저 지금 테스트 환경에 배포 해도 돼요? 제가 지금 테스트 하고 있으니, 다른 분은 나중에 테스트 해주세 요. 다른 모듈,서비스 등을 붙여서 그 관계에서의 문제점을 확인하는 과정
  • 70. 나만 쓰는 프로덕션 환경과 동일한 테스 트 환경이 있으면 좋겠다.
  • 71. Redis ClientRedis Client # client.py import sys import 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.py import unittest from 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')))
  • 72. 나만 쓰는 프로덕션 환경과 동일한 테스 트 환경이 있으면 좋겠다. Host Host
  • 73. DockerDocker Linux Container 를 이용 가벼운 VM 이라고 생각하자 Host 와 격리시킬 수 있음 https://www.docker.com/
  • 74. DockerfileDockerfile FROM python:3.4 MAINTAINER lee ho sung RUN pip install redis RUN mkdir /code ADD . /code WORKDIR /code CMD python -m unittest discover Docker 이미지를 정의한다.
  • 75. Docker-composeDocker-compose Python! 프로젝트 복수개의 Docker Container 를 관리 https://github.com/docker/compose
  • 76. docker-compose.ymldocker-compose.yml client: build: . links: - redis redis: image: redis ports: - 6379:6379 192.168.0.2192.168.0.1 /etc/hosts redis 192.168.0.2
  • 77. docker-compose updocker-compose up Good Localhost 에서 모든 테스트가 가능 Host 에 영향 없음 CI 에서도 실행 가능 $ docker-compose up redis_1 | 1:M 28 Aug 06:05:53.613 # Server started, Redis version 3.0.3 redis_1 | 1:M 28 Aug 06:05:53.613 # WARNING overcommit_memory is set to 0! Background save may fai 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 bec redis_1 | 1:M 28 Aug 06:05:53.614 * DB loaded from disk: 0.000 seconds redis_1 | 1:M 28 Aug 06:05:53.614 * The server is now ready to accept connections on port 6379 client_1 | . client_1 | ---------------------------------------------------------------------- client_1 | Ran 1 test in 1.003s client_1 | client_1 | OK integrationtest_client_1 exited with code 0 Gracefully stopping... (press Ctrl+C again to force) Stopping integrationtest_redis_1... done $
  • 80. SeleniumSelenium 웹 브라우저 자동화 툴/라이브러리 https://github.com/seleniumhq/selenium from selenium import webdriver driver = webdriver.Firefox() driver.get("http://www.python.org") assert "Python" in driver.title
  • 82. Test Pycon 2015Test Pycon 2015 import unittest from selenium import webdriver from 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-me@gmail.com") 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')
  • 84. 경우에경우에 따라따라 많이많이 다릅니다다릅니다.. 최소 : 테스트 케이스가 메인 로직을 검증 한다. 최대 : 개발 시간의 2배 이상을 쓰지 않는다. 이 사이에서 가장 가성비 높은 지점을 찾아내는 것이 좋은 개발자!
  • 86. 문제는문제는 빠르게빠르게 찾을찾을 수록수록 좋습니다좋습니다.. 가능한가능한 자주자주 실행실행 합니다합니다.. 개발할 때 commit 할 때 push 할 때 CI 서버에서 오픈 소스를 받자마자
  • 88. 한번에한번에 하나만하나만 테스트테스트 합니다합니다 실패가실패가 명확해야명확해야 합니다합니다 빠르게빠르게 테스트테스트 되어야되어야 합니다합니다 중복중복 되지되지 않습니다않습니다 자동화자동화 되어야되어야 합니다합니다 독립적이어야독립적이어야 합니다합니다 ((다른것의다른것의 영향을영향을 받지받지 않습니다않습니다))
  • 90. 테스트를테스트를 작성작성 하는하는 이유이유 간단한간단한 테스트테스트 unittestunittest 의의 사용사용 의존성이의존성이 있을있을 때때 mockmock 사용하기사용하기 dockerdocker 사용해서사용해서 통합통합 테스트테스트 하기하기 seleniumselenium을을 사용해서사용해서 웹웹 테스트테스트 하기하기
  • 91. 테스트는 힘들지만 가치 있는 일입니다 지금 시작하세요!
  • 93. Pycon 2014 Getting Started Testing Python Testing Style Guide An Introduction to mocking in Python Test Driven Development with Python