PortfolioPortfolio
# portfolio.py fromhttp://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
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
importunittest
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 실패실패
$ pythonportfolio_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 고르기고르기
$ pythonportfolio_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
복수개의 파일을 한꺼번에 테스트를 실행 할 수있음
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 을을 테스트테스트
importunittest
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
반복되는반복되는 테스트테스트 실행하기실행하기
importunittest
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 에 추가 되었음
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
런타임에 클래스, 함수등을 변경하는 것
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 testMocktest
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
importos
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
classSimpleFacebook(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
classMyError(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
테스트환경이 동작하지 않아요.
프로덕션 환경이랑 테스트 환경이 다른 것 같은데요?
저 지금 테스트 환경에 배포 해도 돼요?
제가 지금 테스트 하고 있으니, 다른 분은 나중에 테스트 해주세
요.
다른 모듈,서비스 등을 붙여서 그 관계에서의
문제점을 확인하는 과정
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
$
Test Pycon 2015TestPycon 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')