unittest는 python에 내장되어 있는 표준 라이브러리 입니다. 그래서 바로 import 하여 사용하는 것이 가능합니다.
import unittest
pytest는 설치하고 import 하여 사용합니다.
$ pip install pytest
import pytest
python [테스트 파일명]
python -m unittest [option] [테스트 파일명]
pytest에서는 아래와 같은 규칙으로 작성만해주면 pytest라는 명령어로 테스트를 탐색하여 테스트를 진행합니다.
파일명: pytest will run all files of the form test_ or _test.py inthe current directory and its subdirectories
함수명: test로 시작 (pytest discovers all tests following its conventions for python test discovery, so it finds both test prefixed functions)
Class 명: Test로 시작 (prefix your class with Test otherwise the class will be skipped)
실행명령:
unittest의 경우 unittest.TestCase를 상속받아서 class 형태로 작성해야 한다.
from tests.unittest1.exl import add
import unittest
class Add_testing(unittest.TestCase):
def test1(self):
self.assertEqual(add(1,2), 3)
def test_str(self):
self.assertEqual(add("a", "b"), "ab")
if __name__ == '__main__':
unittest.main()
pytest는 convention을 지켜서 함수형으로 작성할 수도 있고, unittest와 달리 TestCase처럼 상속받지 않아도 됩니다.
def validate_age(age):
if age<0:
raise ValueError("Age cannot be less than 0"
Raise Error를 작성하였다면 이 역시 해당 상황에서 기대대로 실행되는 지 테스트를 하는 것이 필요합니다. 이때 unittest에서는 assertRaise, pytest에서는 pytest.raises를 사용할 수 있습니다.
unittest에서 컨텍스트 관리자로 사용되면, 컨텍스트 관리자는 잡은 예외 객체를 exception attribute에 저장하는데, 이를 예외에 대해서 추가적인 검사를 수행하려는 경우 위의 예시 코드처럼 사용할 수 있습니다.
from tests.unittest2.ex2 import validate_age
import unittest
class Age_testing(unittest.TestCase):
def test_validate_age_valid_age(self):
validate_age(10)
def test_validate_age_invalid_age(self):
validate_age(-1)
def test_validate_age_invalid_age2(self):
with self.assertRaises(ValueError) as exc_info:
validate_age(-1)
the_exception = exc_info.exception
self.assertEqual(str(the_exception), "Age cannot be less than 0")
if __name__ == '__main__':
unittest.main()
import pytest
from tests.pytest2.ex2 import validate_age
def test_validate_age_valid_age():
validate_age(10)
def test_validate_age_invalid_age():
with pytest.raises(ValueError, match="Age cannot be less than 0"):
validate_age(-1)
제목 그대로 test를 skip할 수 있는 방법입니다.
import sys
import unittest
from tests.unittest6.ex6 import add
class Skip_testing(unittest.TestCase):
@unittest.skip(reason="just wanna skip it"
def test_add_num(self):
self.assertEqual(add(1, 2), 3)
@unittest.skipIf(sys.version_info > (3, 7), reason="use python 3.7 or less")
def test_add_srt(self):
self.assertEqual(add("a", "b"), "ab")
pytest에서 skip할 때 사용한 mark는 skip하는 역할 외에도 테스트에 대한 metadata를 지정해주는 역할을 합니다. 예를 들어 pytest-django에서 테스트할 때 DB에 접근이 필요한 경우
@pytest.mark.django_db 이런 식으로 적엉줍니다.
import pytest
from tests.pytest6.ex6 import add
@pytest.mark.skip(reason="just wanna skip it")
def test_add_num():
assert add(1, 2) == 3
@pytest.mark.skipif(sys.version_info > (3, 7), reason="use python 3.7 or less")
def test_add_str():
assert add("a", "b") == "ab"
테스트가 많아지는 경우 사전 설정이 반복되어야 하는 경우가 있다. 이때 Test Fixture가 필요합니다. Fixture는 여러 개의 테스트를 수행할 때 필요한 준비 도구와 재료 및 그에 관련된 정리 동작에 해당합니다.
반복되는 사전 설정을 setUp()을 사용하여 사전 설정 코드를 밖으로 분리할 수 있습니다. 그리고 테스트 메소드가 실행되고 나서 정리를 위해 tearDown()이 사용됩니다. 정리하면 각각의 테스트 메소드가 실행되기 전에 항상 setUp() 메소드가 실행되어 setUp 내용을 생성하고, 테스트 실행 후 tearDown() 메소드가 실행됩니다.
class Student_testing(unittest.TestCase):
def setUp(self):
print("!setUp making dummy student!")
self.dummy_student = Student('nik', datetime(2000, 1, 1), "coe")
def test_student_get_age(self):
self.dummy_student_age = (datetime.now() - self.dummy_student.dob).days
self.assertEqual(self.dummy_student.get_age(), self.dummy_student_age)
def test_student_add_credits(self):
self.dummy_student.add_credits(5)
self.assertEqual(self.dummy_student.get_credits(), 5)
def test_student_get_credits(self):
self.assertEqual(self.dummy_student.get_credits(), 0)
def tearDown(self):
del self.dummy_student
테스트마다 setUp해주고 tearDown 했다가 다시 setUp 하는 것보다 모든 테스트가 끝난 이후에 tearDown 하는 것도 필요합니다. 이러한 경우에는 setUpClass, tearDownClass를 사용할 수 있습니다.
pytest에서는 단순하게 pytest.fixture 데코레이터를 이용하여 구현해줄 수 있습니다.
pytest의 Fixture에는 4가지 scope가 있고, 각각의 scope마다 한 번씩 실행됩니다.
@pytest.fixture(scope="function")
def dummy_student(request):
print("setUp making dummy student")
dummy_student = Student("nik", datetime(2000, 1, 1), "coe")
def teardown():
print("tearDown dummy student")
request.addfinalizer(teardown)
return dummy_student
def test_student_get_age(dummy_student):
dummy_student_age = (datetime.now() - dummy_student.dob).days
assert dummy_student.get_age() == dummy_student_age
def test_student_add_credits(dummy_student):
dummy_student.add_credits(5)
assert dummy_student.get_credits() == 5
def test_student_get_credits(dummy_student):
assert dummy_student.get_credits() == 0