๐ก ๋ณธ ๋ฌธ์๋ 'Python ๋จ์ ํ ์คํธ(Unit Test): unittest ์ฌ์ฉ๋ฒ'์ ๋ํด ์ ๋ฆฌํด๋์ ๊ธ์ ๋๋ค.
Python ์ฝ๋๋ฅผ ๋จ์ํ ์คํธํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ธ 'unittest'์ ๋ํ ์ค๋ช ๋ฐ ์์ ๋ฅผ ์ ๋ฆฌํ์์ผ๋ ์ฐธ๊ณ ํ์๊ธฐ ๋ฐ๋๋๋ค.
1. Python ๋จ์ ํ ์คํธ: unittest
Python์์ ๋จ์ ํ ์คํธ๋ฅผ unittest ์ฌ์ฉ๋ฒ์ ์ค๋ช ํฉ๋๋ค. unittest๋ Python ๊ธฐ๋ณธ Lib๋ก ๋ณ๋์ ๋ชจ๋์ ์ค์นํ ํ์๊ฐ ์๊ณ , ์ฌ์ฉ๋ฒ์ Java์ JUnit๊ณผ ์ ์ฌํ์ฌ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.
Python unittest๋ ํ ์คํธ ์๋ํ, ์๋ํ๋ฅผ ์ํ ์ค์ , ์ข ๋ฃ, ๊ฐ ํ ์คํธ case ์คํํ๊ณ ์คํ ๊ฒฐ๊ณผ๋ฅผ report ํ ์ ์๋๋ก ๊ตฌ์ฑ๋์ด ์์ต๋๋ค. unittest๋ unittest.TestCase ์ ํจ์๋ฅผ ์์๋ฐ์ ๊ฐ์ฒด ์งํฅ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ๊ฐ๊ฐ์ ํจ์๋ฅผ ์ง์ํฉ๋๋ค.
- Test Fixture: Test๋ฅผ ์ํํ ๋ ์ฌ์ ์ ํ์ํ ์ค๋น์ ๊ทธ์ ๊ด๋ จ๋ ๋์์ ์คํํฉ๋๋ค. ์๋ฅผ ๋ค์ด ๋ก๊ทธ์ธ์ด ํ์ํ ๊ธฐ๋ฅ์ ๋ํ ๋ก๊ทธ์ธ์ ํ ์คํธ ์ ์ ์ฌ์ ์ ์คํํ๊ฑฐ๋, ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ, ํด๋ ์์ฑ, DB ์ฐ๊ฒฐ ๋ฑ์ ๋์์ ํ ์ ์์ต๋๋ค.
- Test Case: ํ ์คํธ์ ๊ฐ๋ฐ ๋จ์์ ๋๋ค. ํจ์์ ๋ฆฌํด ๊ฐ์ ํ์ธํ๊ฑฐ๋, ๋ณ์ ๊ฐ์ ํ์ธํฉ๋๋ค.
- Test Suite: Test Case์ ์งํฉ์ ๋๋ค. ํ ๋ฒ์ ๊ฐ์ด ์คํํด์ผ ํ ํ ์คํธ๋ค์ ์ข ํฉํ ๊ฒฐ๊ณผ์ ๋๋ค.
- Test Runner: ์ค์ Test case๋ฅผ ์คํํ๊ณ , ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ์ฌ์ฉ์์ ์ ๊ณตํ๋ ์ญํ ์ ์ปดํฌ๋ํธ์ ๋๋ค.
2. Python Unittest class ์ฝ๋ ์์ฑ
Uniitest code๋ฅผ ์์ฑํ๋ ๊ณผ์ ์ ์๋์ ๊ฐ์ด 6๋จ๊ณ๋ก ๊ตฌ์ฑํ ์ ์์ต๋๋ค.
- ์ฝ๋ ์ต์๋จ์ unittest lib๋ฅผ import ํฉ๋๋ค.
- TestClass ๋ง๋ค๊ณ unittest.TestCase class์์ ์์์ ๋ฐ์ต๋๋ค.
- @classmethod decorator์ ํจ๊ป setUpClass(cls)์ tearDownClass(cls)๋ฅผ ํจ์๋ฅผ ๊ตฌํํฉ๋๋ค. setUpClass์ tearDownClass์ Class ์์ฑ๊ณผ ์๋ฉธ ์ ํธ์ถ๋๋ ํจ์์ ๋๋ค. @classmethod๋ก ๊ตฌํํ๊ธฐ ๋๋ฌธ์ cls๋ฅผ ํตํด์ class๋ณ์๋ฅผ ์๋ก ๋ง๋ค๊ฑฐ๋ ์ ๊ทผํ ์ ์์ต๋๋ค. setUpClass(cls)์ tearDownClass(cls)๋ ์๋ตํด๋ ๋ฌด๋ฐฉํฉ๋๋ค.
- Testcast์ ๋ํ Fixture ํจ์์ธ setUp(self)์ tearDown(self)๋ฅผ ๊ตฌํํฉ๋๋ค. setUp(self)์ tearDown(self) ํจ์๋ ๊ฐ์ฒด์ ๊ฐ์ self๋ก ํตํด์ ์ ๊ทผ ๊ฐ๋ฅํฉ๋๋ค. ๊ฐ TestCase ํจ์๊ฐ ์คํํ ๋๋ง๋ค ์๋ค๋ก ํธ์ถ๋ฉ๋๋ค.
- Testcase ํจ์๋ฅผ ๊ตฌํํฉ๋๋ค. ํจ์ ์ด๋ฆ์ด test_ ๋ก ์์ํ๋ฉด TestRunner๊ฐ ์์์ ์คํํด์ค๋๋ค. ์คํ ์์๋ ๊ตฌํ ์์์ ์๊ด์์ด ํจ์ ์ด๋ฆ ์์๋ก ์คํ๋ฉ๋๋ค. ๊ฐ๊ฐ์ Testcase ํจ์๋ ๋ ๋ฆฝ์ ์ผ๋ก ์คํํ ์ ์๋๋ก ๊ฐ๋ฐํด์ผ ํฉ๋๋ค. ๊ฐ TestCaseํจ์์์๋ self.assertEqual(), self.assertInl() ๋ฑ์ ํจ์๋ฅผ ์ฌ์ฉํ๊ณ Test ๊ฒฐ๊ณผ์ Pass์ Fail์ ๊ฒฐ์ ํฉ๋๋ค.
- ๋ง์ง๋ง์ผ๋ก unittest.main() ํจ์๋ฅผ ํธ์ถํ์ฌ TestRunner๋ฅผ ์คํํฉ๋๋ค.
3. Python Unittest ์คํ
TestCase class๋ฅผ ๊ตฌํํ๊ณ ํฐ๋ฏธ๋์์ ์๋์ ๊ฐ์ด ์คํํฉ๋๋ค.
3.1 ์ ์ฒด Test Case ์คํ
$ python3 your_test_cases.py
$ python3 -m unittest your_test_cases.py
3.2 ํน์ Test Case๋ง์ ์คํ
$ python3 -m unittest test_module1 test_module2
$ python3 -m unittest test_module.TestClass
$ python3 -m unittest test_module.TestClass.test_method
๊ธฐ๋ณธ ์ต์ ์ผ๋ก๋ Test Case ์ค ํ๋๊ฐ ์คํจํ๋๋ผ๋ ๋ชจ๋ Test Caseํจ์๋ฅผ ์คํํ์ง๋ง, -f --failtest ์ต์ ์ ์ฌ์ฉํ๋ฉด ์ฒซ ๋ฒ์งธ ์คํ ์ค ์คํ์ ์ค๋จํฉ๋๋ค.
3.3 Test Case ํ์ (Discovery)
Project ํด๋์์ ํ์ผ๋ช Pattern์ ์ค์ ํ์ฌ Test Case๋ฅผ ์คํํ ์ ์์ต๋๋ค. ์๋ ๋ช ๋ น์ด๋ project_directory ํด๋์์ _test.py๋ฅผ ์ฐพ์์ ์คํํฉ๋๋ค.
$ python -m unittest discover -s project_directory -p "*_test.py"
3.4 Test Case ์คํํ์ง ์๊ธฐ(Skip)
@unittest.skipIf( )๋ฅผ ์ฌ์ฉํ์ฌ ํน์ ์กฐ๊ฑด์์๋ Test case๋ฅผ ์คํํ์ง ์์ ์ ์์ต๋๋ค.
@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
3.5 Test Case ์กฐ๊ฑด๋ฌธ
unittest API | Description | version |
assertEqual(a, b) | a == b | |
assertNotEqual(a, b) | a != b | |
assertTrue(x) | bool(x) is True | |
assertFalse(x) | bool(x) is False | |
assertIs(a, b) | a is b | 3.1 ์ด์ |
assertIsNot(a, b) | a is not b | 3.1 ์ด์ |
assertIsNone(x) | x is None | 3.1 ์ด์ |
assertIsNotNone(x) | x is not None | 3.1 ์ด์ |
assertIn(a, b) | a in b | 3.1 ์ด์ |
assertNotIn(a, b) | a not in b | 3.1 ์ด์ |
assertIsInstance(a, b) | isinstance(a, b) | 3.2 ์ด์ |
assertNotIsInstance(a, b) | not isinstance(a, b) | 3.2 ์ด์ |
4. Sample Code
Test case ๋ฅผ ๊ตฌํํ๋ ๋ด์ฉ์ test_***() ์ด๋ฆ์ ํจ์๋ช ์ผ๋ก ์์ฑํ๊ณ assert ๊ตฌ๋ฌธ์ผ๋ก ์ฑ๊ณต๊ณผ ์คํจ๋ฅผ ํ๋จํฉ๋๋ค. ์์ค ์ฝ๋๋ GitHub์ ์ฌ๋ ธ์ต๋๋ค.
import unittest
import sys
class SampleTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
"Hook method for setting up class fixture before running tests in the class."
cls.driver = 'test'
cls.members = [1,2,3,4]
print (sys._getframe(0).f_code.co_name)
@classmethod
def tearDownClass(cls):
"Hook method for deconstructing the class fixture after running all tests in the class."
print (sys._getframe(0).f_code.co_name)
def setUp(self):
"Hook method for setting up the test fixture before exercising it."
print ('\t',sys._getframe(0).f_code.co_name)
def tearDown(self):
"Hook method for deconstructing the test fixture after testing it."
print ('\t', sys._getframe(0).f_code.co_name)
def test_runs_1(self):
print ('\t\t',sys._getframe(0).f_code.co_name, self.driver)
self.assertTrue(True)
def test_runs_2(self):
print ('\t\t',sys._getframe(0).f_code.co_name, self.members)
self.assertTrue(False)
def test_line_count(self):
print ('\t\t',sys._getframe(0).f_code.co_name)
self.assertTrue(1 == 1)
if __name__ == '__main__':
unittest.main()
์ฐธ๊ณ
- [Python] Python Official docs: https://docs.python.org/ko/3/library/unittest.html
- [Blog] Python ๋จ์ ํ ์คํธ(Unit Test)๋ฅผ ์ํ unittest ์ฌ์ฉ๋ฒ๊ณผ ์์ : https://kibua20.tistory.com/226