Python UnitTest
무엇을 테스트 해야 하지? == 무엇을 만들어야 하는지 모름
Test passes 
테스트를 통과 할 수 있게 최소 
한의 프로그래밍 
Test fails Refactor 
실패하는 테스트를 작성 
코드 의존성 제거 및 품질 향상
Basic Example 
import random 
import unittest 
class PizzaTest(unittest.TestCase): 
def setUp(self): 
self.seq = list(range(10)) 
def test_shuffle(self): 
unittest.TestCase를 상속받아야 함 
random.shuffle(self.seq) 
self.seq.sort() 
self.assertEqual(self.seq, list(range(10))) 
#리스트를 변경해야하는데 튜플은 변경이 안되어 TypeError 발생 
self.assertRaises(TypeError, random.shuffle, (1,2,3)) 
def test_choice(self): 
element = random.choice(self.seq) 
self.assertTrue(element in self.seq) 
def test_sample(self): 
with self.assertRaises(ValueError): 
#첫번째 파라미터가 가진 리스트 갯수 보다 많은 수를 입력하여 ValueError가 발생함 
random.sample(self.seq, 20) 
for element in random.sample(self.seq, 5): 
self.assertTrue(element in self.seq) 
테스트 함수 실행 될때마다 먼저 실행되는 준비 함수 
테스트 할 함수의 이름이 
test로 시작해야 함 
$ python3 -m unittest tests.py
Test Discovery 
테스트를 찾는 패턴을 커스터마이징 할 수 있다. 
python -m unittest discover -s project_directory -p '*_test.py' 
-v, --verbose 
Verbose output 
-s, --start-directory directory 
Directory to start discovery (. default) 
-p, --pattern pattern 
Pattern to match test files (test*.py default) 
-t, --top-level-directory directory
Test Suite 
내가 원하는 대로 테스트 구성 하기 
def suite(): 
suite = unittest.TestSuite() 
suite.addTest(PizzaTest('test_choice')) 
# suite.addTest(PizzaTest('test_sample')) 
return suite 
if __name__ == '__main__': 
unittest.TextTestRunner().run(suite())
Test Skip 
@unittest.skip("demonstrating skipping") 
def test_nothing(self): 
self.fail("shouldn't happen") 
@unittest.skipIf(mylib.__version__ < (1, 3), 
"not supported in this library version") 
def test_format(self): 
# Tests that work for only a certain version of the library. 
pass 
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") 
def test_windows_support(self): 
# windows specific testing code 
pass
Python3.4 Assertions
assertEqual(a, b) 
a와 b가 같으면 테스트 성공 그렇지 않으면 실패
assertNotEqual(a, b) 
a와 b가 같지 않으면 테스트 성공
assertTrue(x), assertFalse(x) 
함수 의미와 동일하게 True이거나 False이면 성공
assertIs(a, b), assertIsNot(a, b) 
a와 b가 같은 오브젝트 이면 성공하거나 아니면 성공
assertIsNone(x), assertIsNotNone(x) 
x가 None 이면 성공이거나 None이 아니면 성공
assertIn(a, b), assertNotIn(a, b) 
a가 b에 속해 있으면 성공, a가 b에 속하지 성공
assertIsInstance(a, b), assertNotIsInstance(a, b) 
a = object 
b= class 
a가 b의 인스턴스이면 성공, 아니면 성공
다양한 Exception을 테스트 할 수 있다 
assertRaises(exc, fun, *args, **kwds) 
def order(): 
raise Exception 
def test_order(self): 
with self.assertRaises(Exception): 
order()
Exception 메세지에 정규식이 매치가 되면 성공 
assertRaisesRegex(exc, r, fun, *args, **kwds) 
def test_exception_message_regex(self): 
self.assertRaisesRegex(ValueError, "invalid literal for.*XYZ'$", int, 'XYZ')
더 있지만 생략 
https://docs.python.org/3.4/library/unittest.html#unittest.TestCase.assertAlmostEqual
Django에는 assertion이 더 있음 
https://docs.djangoproject.com/en/dev/topics/testing/tools/#assertions
Mock 
실제 어플리케이션 객체를 특정한 방식으로 흉내 내는 객체
3을 리턴하는 모의 객체 만들어 보기 
from unittest.mock import MagicMock 
class A(object): 
def test(self): 
pass 
class AuthTest(TestCase): 
def setUp(self): 
pass 
3을 리턴하도록 목 객체 생성 
def test_mock(self): 
inst = A() 
inst.method = MagicMock(return_value=3) 
self.assertEqual(inst.method(1), 3) 
inst.method.assert_called_with(1) 
정말 3을 리턴하는지 
테스트 
mock 객체가 호출 되었는지 확인
Raising an exception when a mock is called 
def test_side_effect(self): 
mock = Mock(side_effect=KeyError('Foo')) 
self.assertRaises(KeyError, mock)
patch 
mock.patch를 사용하면 외부 라이브러리의 행위를 바꿀 수 있다.
Fake client 
from django.test import Client 
from unittest.mock import MagicMock, Mock, patch 
def get_fake_request(status_code, content): 
m = Mock() 
m.status_code = status_code 
m.content = content 
def fake_get(url, *args, **kwargs): 
return m 
return fake_get 
class MockTest(TestCase): 
@patch('django.test.Client.get', get_fake_request(200, 'Fake')) 
def test_request(self): 
res = Client.get('http://fake.com') 
self.assertEqual(res.status_code, 200)
Django UnitTest
Request test 
def test_test_view(self): 
c = Client() 
res = c.get('/test_view/', {}) 
e = {'result': 1} 
self.assertJSONEqual(res.content.decode('utf-8'), json.dumps(e)) 
post, head, put, patch, delete, trace
login_required view 라면? 
@login_required 
def auth_view(request): 
return HttpResponse(status=200) 
def test_login_required_view(self): 
client = Client() 
res = client.login(username='debug', password='debug') 
res = client.get('/auth_view/') 
self.assertEqual(res.status_code, 200) 
Client.login 을 호출하여 로그인을 하면 됨
Rainist, For Making Better Decision 
http://www.rainist.com 
최명규

Python Unittest

  • 1.
  • 2.
    무엇을 테스트 해야하지? == 무엇을 만들어야 하는지 모름
  • 3.
    Test passes 테스트를통과 할 수 있게 최소 한의 프로그래밍 Test fails Refactor 실패하는 테스트를 작성 코드 의존성 제거 및 품질 향상
  • 4.
    Basic Example importrandom import unittest class PizzaTest(unittest.TestCase): def setUp(self): self.seq = list(range(10)) def test_shuffle(self): unittest.TestCase를 상속받아야 함 random.shuffle(self.seq) self.seq.sort() self.assertEqual(self.seq, list(range(10))) #리스트를 변경해야하는데 튜플은 변경이 안되어 TypeError 발생 self.assertRaises(TypeError, random.shuffle, (1,2,3)) def test_choice(self): element = random.choice(self.seq) self.assertTrue(element in self.seq) def test_sample(self): with self.assertRaises(ValueError): #첫번째 파라미터가 가진 리스트 갯수 보다 많은 수를 입력하여 ValueError가 발생함 random.sample(self.seq, 20) for element in random.sample(self.seq, 5): self.assertTrue(element in self.seq) 테스트 함수 실행 될때마다 먼저 실행되는 준비 함수 테스트 할 함수의 이름이 test로 시작해야 함 $ python3 -m unittest tests.py
  • 5.
    Test Discovery 테스트를찾는 패턴을 커스터마이징 할 수 있다. python -m unittest discover -s project_directory -p '*_test.py' -v, --verbose Verbose output -s, --start-directory directory Directory to start discovery (. default) -p, --pattern pattern Pattern to match test files (test*.py default) -t, --top-level-directory directory
  • 6.
    Test Suite 내가원하는 대로 테스트 구성 하기 def suite(): suite = unittest.TestSuite() suite.addTest(PizzaTest('test_choice')) # suite.addTest(PizzaTest('test_sample')) return suite if __name__ == '__main__': unittest.TextTestRunner().run(suite())
  • 7.
    Test Skip @unittest.skip("demonstratingskipping") def test_nothing(self): self.fail("shouldn't happen") @unittest.skipIf(mylib.__version__ < (1, 3), "not supported in this library version") def test_format(self): # Tests that work for only a certain version of the library. pass @unittest.skipUnless(sys.platform.startswith("win"), "requires Windows") def test_windows_support(self): # windows specific testing code pass
  • 8.
  • 9.
    assertEqual(a, b) a와b가 같으면 테스트 성공 그렇지 않으면 실패
  • 10.
    assertNotEqual(a, b) a와b가 같지 않으면 테스트 성공
  • 11.
    assertTrue(x), assertFalse(x) 함수의미와 동일하게 True이거나 False이면 성공
  • 12.
    assertIs(a, b), assertIsNot(a,b) a와 b가 같은 오브젝트 이면 성공하거나 아니면 성공
  • 13.
    assertIsNone(x), assertIsNotNone(x) x가None 이면 성공이거나 None이 아니면 성공
  • 14.
    assertIn(a, b), assertNotIn(a,b) a가 b에 속해 있으면 성공, a가 b에 속하지 성공
  • 15.
    assertIsInstance(a, b), assertNotIsInstance(a,b) a = object b= class a가 b의 인스턴스이면 성공, 아니면 성공
  • 16.
    다양한 Exception을 테스트할 수 있다 assertRaises(exc, fun, *args, **kwds) def order(): raise Exception def test_order(self): with self.assertRaises(Exception): order()
  • 17.
    Exception 메세지에 정규식이매치가 되면 성공 assertRaisesRegex(exc, r, fun, *args, **kwds) def test_exception_message_regex(self): self.assertRaisesRegex(ValueError, "invalid literal for.*XYZ'$", int, 'XYZ')
  • 18.
    더 있지만 생략 https://docs.python.org/3.4/library/unittest.html#unittest.TestCase.assertAlmostEqual
  • 19.
    Django에는 assertion이 더있음 https://docs.djangoproject.com/en/dev/topics/testing/tools/#assertions
  • 20.
    Mock 실제 어플리케이션객체를 특정한 방식으로 흉내 내는 객체
  • 21.
    3을 리턴하는 모의객체 만들어 보기 from unittest.mock import MagicMock class A(object): def test(self): pass class AuthTest(TestCase): def setUp(self): pass 3을 리턴하도록 목 객체 생성 def test_mock(self): inst = A() inst.method = MagicMock(return_value=3) self.assertEqual(inst.method(1), 3) inst.method.assert_called_with(1) 정말 3을 리턴하는지 테스트 mock 객체가 호출 되었는지 확인
  • 22.
    Raising an exceptionwhen a mock is called def test_side_effect(self): mock = Mock(side_effect=KeyError('Foo')) self.assertRaises(KeyError, mock)
  • 23.
    patch mock.patch를 사용하면외부 라이브러리의 행위를 바꿀 수 있다.
  • 24.
    Fake client fromdjango.test import Client from unittest.mock import MagicMock, Mock, patch def get_fake_request(status_code, content): m = Mock() m.status_code = status_code m.content = content def fake_get(url, *args, **kwargs): return m return fake_get class MockTest(TestCase): @patch('django.test.Client.get', get_fake_request(200, 'Fake')) def test_request(self): res = Client.get('http://fake.com') self.assertEqual(res.status_code, 200)
  • 25.
  • 26.
    Request test deftest_test_view(self): c = Client() res = c.get('/test_view/', {}) e = {'result': 1} self.assertJSONEqual(res.content.decode('utf-8'), json.dumps(e)) post, head, put, patch, delete, trace
  • 27.
    login_required view 라면? @login_required def auth_view(request): return HttpResponse(status=200) def test_login_required_view(self): client = Client() res = client.login(username='debug', password='debug') res = client.get('/auth_view/') self.assertEqual(res.status_code, 200) Client.login 을 호출하여 로그인을 하면 됨
  • 28.
    Rainist, For MakingBetter Decision http://www.rainist.com 최명규