Unit Testing in Python

Pytest์™€ Unittest์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•„์š”

Before getting started

Unit testing์ด๋ž€?

: Module์ด๋‚˜ application ์•ˆ์— ์žˆ๋Š” ๊ฐœ๋ณ„์ ์ธ code ๋‹จ์œ„๊ฐ€ ์˜ˆ์ƒ๋Œ€๋กœ ์ž‘๋™ํ•˜๋Š”์ง€ ํ™•์ธํ•˜๋Š” ๋ฐ˜๋ณต์ ์ธ ํ–‰์œ„

Unit testing์ด ํ•„์š”ํ•œ ์ด์œ 

  • Code๋ฅผ ์–ด๋–ป๊ฒŒ ์ž‘์„ฑํ•˜๋Š”์ง€ ์ƒ๊ฐํ•˜๋Š”๋ฐ ๋„์›€์„ ์ค€๋‹ค

  • ๋ฌด์—‡์„ ํ•ด์•ผํ•˜๋Š”์ง€์— ์žˆ์–ด์„œ ๊ตฌํ˜„ ์„ ํƒ์„ ํ•  ๋•Œ, ๊ทธ ์„ ํƒ๋“ค์ด ์ ์ ˆํ•œ์ง€ ์•Œ์•„๋‚ด๋Š”๋ฐ ๋„์›€์„ ์ค€๋‹ค

  • Bug๋ฅผ ๋นจ๋ฆฌ ๋ฐœ๊ฒฌํ•˜๊ณ , ์‰ฝ๊ฒŒ ๊ณ ์น  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค€๋‹ค

  • Code ์˜ quality๋ฅผ ํ–ฅ์ƒ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค

  • ํ†ตํ•ฉ์„ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•˜๊ณ , ์„ค๊ณ„๋ฅผ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค

  • Debugging process๋ฅผ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ•ด์ค€๋‹ค

Python test frameworks

  • Unittest

  • Doctest

  • Pytest

Unittest ๋ž€?

  • Python ํ‘œ์ค€ library์™€ ํ•จ๊ป˜ ์ œ๊ณต๋˜๋Š” ๊ธฐ๋ณธ test framework

  • ๋‹ค๋ฅธ third-party test framework๋งŒํผ ๋ฒ”์œ„๊ฐ€ ๋„“์ง€ ์•Š๋‹ค

    • ๋Œ€๋ถ€๋ถ„์˜ project์— ๋งž๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ์„ ์ œ๊ณตํ•œ๋‹ค

  • Java์˜ JUnit testing famework๋กœ ๋ถ€ํ„ฐ ์˜ํ–ฅ์„ ๋ฐ›์•˜๋‹ค

    • ๊ทธ๋ž˜์„œ test๋ฅผ ์œ„ํ•ด class๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์œผ๋ฉด test function์„ ์ž‘์„ฑํ•  ์ˆ˜ ์—†๋‹ค

Unittest example

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)

if __name__ == '__main__':
    unittest.main()

Unittest Fixtures

: Test๊ฐ€ ์ˆ˜ํ–‰๋˜๊ธฐ ์ „ setup ํ˜น์€ ์ข…๋ฃŒ ํ›„์— clean-up ํ•˜๋Š” ๊ณผ์ •์„ ์˜๋ฏธํ•œ๋‹ค

  • setUp()

    • ๊ฐ method๋ฅผ ํ˜ธ์ถœํ•˜๊ธฐ ์ „์— ํ˜ธ์ถœ๋˜๋Š” method

  • setUpClass()

    • ํ•ด๋‹น class๊ฐ€ ์‹œ์ž‘๋˜๋ฉด ๋‹จ ํ•œ ๋ฒˆ ํ˜ธ์ถœ๋˜๋Š” method

    • method์— @classmethod decorator๋ฅผ ๋‹ฌ์•„์ค˜์•ผ ํ•˜๊ณ , argument๋กœ cls ๋ฅผ ๋„˜๊ฒจ์ค˜์•ผ ํ•œ๋‹ค

    • ex)

      @classmethod
      def setUpClass(cls):
          ...
  • tearDown()

    • ๊ฐ test๊ฐ€ ๋๋‚œ ์ดํ›„์— ํ˜ธ์ถœ๋˜๋Š” method

    • Test ๋„์ค‘ exception ์ด ๋ฐœ์ƒํ•ด๋„ ์‹คํ–‰๋œ๋‹ค

    • ๋‹จ, setUp() method๊ฐ€ ์„ฑ๊ณตํ–ˆ์„ ๊ฒฝ์šฐ์—๋งŒ ํ˜ธ์ถœ๋œ๋‹ค

  • tearDownClass()

    • ํ•ด๋‹น class๊ฐ€ ์ข…๋ฃŒ๋œ ์ดํ›„ ๋‹จ ํ•œ ๋ฒˆ ํ˜ธ์ถœ๋˜๋Š” method

    • ์—ญ์‹œ method์— @classmethod decorator๋ฅผ ๋‹ฌ์•„์ค˜์•ผ ํ•˜๊ณ , argument๋กœ cls ๋ฅผ ๋„˜๊ฒจ์ค˜์•ผ ํ•œ๋‹ค

    • ex)

      @classmethod
      def tearDownClass(cls):
          ...

Pytest ๋ž€?

  • Unittest์™€ ๋‹ฌ๋ฆฌ Class๊ฐ€ ์•„๋‹Œ module์˜ ํŠน์ • ๋ช…๋ช… ๊ทœ์น™์„ ๋”ฐ๋ฅด๋Š” ํ•จ์ˆ˜๋กœ test method ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค

Pytest Fixtures

  • ๋‹ค๋ฅธ testing framework ์™€๋Š” ๋‹ฌ๋ฆฌ ๋…ํŠนํ•œ ๋ฐฉ์‹์œผ๋กœ fixture๋ฅผ ์ œ๊ณตํ•œ๋‹ค

  • ์–ด๋–ค test๊ฐ€ ์–ด๋–ค fixture๋ฅผ ์‚ฌ์šฉํ•˜๋Š”์ง€ ์‰ฝ๊ฒŒ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค

  • ex)

    from handler import handler
    
    @pytest.fixture()
    def load_file():
        file = None
        try:
            file = open("sample_file.json", "r")
            content = eval(file.read())
        finally:
            if file:
                file.close()
    
        return content
     
    def test_handler(load_file):
       handler(load_file)

XFail

  • ํ•ด๋‹น test function์ด fail ํ•  ๊ฒƒ์ž„์„ ๋ช…์‹œํ•  ์ˆ˜ ์žˆ๋‹ค

    • ์‹คํŒจํ•ด์•ผ๋งŒ ํ•˜๋Š” test๋ฅผ ์ž‘์„ฑํ•  ๋•Œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค

      • ex) ์ž˜๋ชป๋œ ๋ฐ์ดํ„ฐ ํƒ€์ž…์„ ๋ง‰๊ธฐ์œ„ํ•œ test code

  • ex)

    from handler import handler
    
    @pytest.fixture()
    def load_wrong_format_file():
        file = None
        try:
            file = open("sample_file.txt", "r")
            content = eval(file.read())
        finally:
            if file:
                file.close()
    
        return content
      
    @pytest.mark.xfail
    def test_handler_with_wrong_format_file(self, load_wrong_format_file):
       handler(load_wrong_format_file)

Last updated

Was this helpful?