Study: Software(SW)/SW: Language

[Python] Python ๋‹จ์œ„ ํ…Œ์ŠคํŠธ(Unit Test): unittest ์‚ฌ์šฉ๋ฒ•

DrawingProcess 2022. 12. 8. 00:47
๋ฐ˜์‘ํ˜•
๐Ÿ’ก ๋ณธ ๋ฌธ์„œ๋Š” '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๋‹จ๊ณ„๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

  1. ์ฝ”๋“œ ์ตœ์ƒ๋‹จ์— unittest lib๋ฅผ import ํ•ฉ๋‹ˆ๋‹ค.
  2. TestClass ๋งŒ๋“ค๊ณ  unittest.TestCase class์—์„œ ์ƒ์†์„ ๋ฐ›์Šต๋‹ˆ๋‹ค.
  3. @classmethod decorator์™€ ํ•จ๊ป˜  setUpClass(cls)์™€ tearDownClass(cls)๋ฅผ ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค. setUpClass์™€ tearDownClass์€ Class ์ƒ์„ฑ๊ณผ  ์†Œ๋ฉธ ์‹œ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.  @classmethod๋กœ ๊ตฌํ˜„ํ•˜๊ธฐ ๋•Œ๋ฌธ์— cls๋ฅผ ํ†ตํ•ด์„œ class๋ณ€์ˆ˜๋ฅผ ์ƒˆ๋กœ ๋งŒ๋“ค๊ฑฐ๋‚˜ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.  setUpClass(cls)์™€ tearDownClass(cls)๋Š” ์ƒ๋žตํ•ด๋„ ๋ฌด๋ฐฉํ•ฉ๋‹ˆ๋‹ค. 
  4. Testcast์— ๋Œ€ํ•œ Fixture ํ•จ์ˆ˜์ธ setUp(self)์™€ tearDown(self)๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.  setUp(self)์™€ tearDown(self) ํ•จ์ˆ˜๋Š” ๊ฐ์ฒด์˜ ๊ฐ’์„ self๋กœ ํ†ตํ•ด์„œ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๊ฐ  TestCase ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰ํ•  ๋•Œ๋งˆ๋‹ค ์•ž๋’ค๋กœ ํ˜ธ์ถœ๋ฉ๋‹ˆ๋‹ค. 
  5. Testcase ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.  ํ•จ์ˆ˜ ์ด๋ฆ„์ด test_ ๋กœ ์‹œ์ž‘ํ•˜๋ฉด TestRunner๊ฐ€ ์•Œ์•„์„œ ์‹คํ–‰ํ•ด์ค๋‹ˆ๋‹ค. ์‹คํ–‰ ์ˆœ์„œ๋Š” ๊ตฌํ˜„ ์ˆœ์„œ์— ์ƒ๊ด€์—†์ด ํ•จ์ˆ˜ ์ด๋ฆ„ ์ˆœ์„œ๋กœ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.  ๊ฐ๊ฐ์˜ Testcase ํ•จ์ˆ˜๋Š” ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ฐœ๋ฐœํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.  ๊ฐ TestCaseํ•จ์ˆ˜์—์„œ๋Š”  self.assertEqual(), self.assertInl() ๋“ฑ์˜ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  Test ๊ฒฐ๊ณผ์˜ Pass์™€ Fail์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.  
  6. ๋งˆ์ง€๋ง‰์œผ๋กœ  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()

์ฐธ๊ณ 

๋ฐ˜์‘ํ˜•