파이썬 내장 라이브러리 중 unittest를 활용하면 JUnit처럼 유닛테스트가 가능하다. 이 라이브러리를 알기 전에는 Jupyter Notebook에서 코드를 돌려보는 방법을 사용하고 있었다.
Q. unittest로 테스트 코드를 작성하면 좋은 점?
A. 리턴 타입이 void인 함수도 Mock을 사용해서 assert_not_called와 assert_called 함수로 호출 여부를 알 수 있다. 외부 API를 호출하는 등의 경우, 외부 디펜던시와 무관하게 현재 작성한 코드가 API를 호출하는지를 분리해서 테스트할 수 있으므로 유용하다.
import unittest
from unittest import TestCase
from unittest.mock import MagicMock
from controller import Controller
class TestController(TestCase):
def test_swith_as_selected(self):
controller = Controller()
controller.make_card = MagicMock()
controller.insert_card = MagicMock()
controller.switch_as_selected(1)
controller.make_card.assert_not_called() # ok
controller.insert_card.assert_called_once() # ok
# controller.make_card.assert_called() # FAILED
if __name__ == '__main__':
unittest.main()
쪼개서 보면
if __name__ == '__main__':
unittest.main()
이 코드가 있으면 파일을 실행했을 때 test_로 시작하는 함수를 모두 실행한다.
def test_swith_as_selected(self):
controller = Controller()
controller.make_card = MagicMock()
controller.insert_card = MagicMock()
controller.switch_as_selected(1)
controller.make_card.assert_not_called() # OK
controller.insert_card.assert_called_once() # OK
# controller.make_card.assert_called() # FAILED
Controller가 테스트할 실제 코드 클래스이다.
이 클래스의 make_card와 insert_card가 switch_as_selected() 함수 실행 시 전달된 파라미터에 따라 실행되도록 작성했다.
파라미터가 1/2/3이면 insert_card(), 4이면 make_card()가 실행되어야 한다.
위 코드에서 넘겨진 파라미터가 1이므로 make_card()는 호출되지 않고, insert_card()는 호출되었다.
실제로 호출되지 않았을 make_card가 호출되었다고 쓴 코드의 주석을 해제하면 결과는 FAILED이다.
이제 테스트 케이스를 다양하게 해 보려고 아래와 같이 수정했다.
import unittest
from unittest import TestCase
from unittest.mock import MagicMock
from controller import Controller
class TestController(TestCase):
def test_swith_as_selected_1(self):
controller.switch_as_selected(1)
controller.make_card.assert_not_called()
controller.insert_card.assert_called_once()
def test_switch_as_selected_2(self):
controller.switch_as_selected(2)
controller.make_card.assert_not_called()
controller.insert_card.assert_called_once()
def test_switch_as_selected_3(self):
controller.switch_as_selected(3)
controller.make_card.assert_not_called()
controller.insert_card.assert_called_once()
def test_switch_as_selected_4(self):
controller.switch_as_selected(4)
controller.make_card.assert_called_once()
controller.insert_card.assert_not_called()
def test_switch_as_answer_Y(self):
controller.switch_as_answer("Y")
controller.ask_for_new_card.assert_called_once()
def test_switch_as_answer_N(self):
controller.switch_as_answer("N")
controller.ask_for_new_card.assert_not_called()
if __name__ == '__main__':
controller = Controller()
controller.make_card = MagicMock()
controller.insert_card = MagicMock()
controller.ask_for_new_card = MagicMock()
unittest.main()
그런데 이렇게 했더니 test_switch_as_selected_3에서부터 예상치 못한 FAILED 발생
당연하다.. controller 객체가 공유되고 있기 때문
아래와 같이 setUp 함수를 작성하면
각각의 테스트 함수를 실행하기 전에 setUp이 실행되어 객체가 공유되지 않고 원하던 대로 단위 테스트를 하게 된다.
class TestController(TestCase):
def setUp(self):
self.controller = Controller()
self.controller.make_card = MagicMock()
self.controller.insert_card = MagicMock()
self.controller.ask_for_new_card = MagicMock()
def test_swith_as_selected_1(self):
self.controller.switch_as_selected(1)
self.controller.make_card.assert_not_called()
self.controller.insert_card.assert_called_once()
...