don't stop believing

Python Unittest 살펴보기 본문

Python/unittest

Python Unittest 살펴보기

Tongchun 2017. 11. 6. 10:10

Python에서 method, property등의 검증을 위해 사용된다.

[https://docs.python.org/3/library/unittest.html]


간단하게 알아봅시다.

우선 코드부터 바로 들어갑니다.

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를 사용할 class는 unittest.TestCase를 상속해야 합니다.

그리고 각각의 method들의 이름은 test_로 시작되어야 합니다.


위 코드를 실행하면 아래와 같이 출력됩니다.

$ python basic_example_1.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

좀더 자세한 결과를 보고 싶다면 -v 옵션을 추가 합니다.

$ python basic_example_1.py -v
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK


unittest.TestCase를 상속한 class의 구조는 setUp(), 'test_'로 시작하는 method들 그리고 tearDown()으로 되어 있습니다.

setUp()은 testcase가 시작할때의 property 할당 등의 설정을 할 수 있으며 가장 먼저 호출됩니다.

'test_'로 시작하는 method들은 각각의 테스트 항목이고 tearDown()은 testcase를 마칠때 호출됩니다.

import unittest

class Widget():

    def __init__(self, name):
        self.name = name
        self.width = 0
        self.high = 0

    def size(self):
        return (self.width, self.high)

    def resize(self, width, high):
        self.width = width

        # high는 100을 넘을 수 없다.
        if high > 100:
            self.high = 100
        else:
            self.high = high

        return (self.width, self.high)

    def dispose(self):
        self.name = ""
        self.width = 0
        self.high = 0
        return True


class WidgetTestCase(unittest.TestCase):
    def setUp(self):
        self.widget = Widget('The widget')

    def test_default_widget_size(self):
        self.assertEqual(self.widget.size(), (50,50), 'incorrect default size')

    def test_widget_resize(self):
        self.widget.resize(100,150)
        self.assertEqual(self.widget.size(), (100,150), 'wrong size after resize')

    def tearDown(self):
        self.widget.dispose()

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

Widget이라는 클래스를 unittest로 확인하는 코드입니다.

위 코드에서 test_default_widget_size()와 test_widget_resize()는 모두 Fail될 것입니다.

이걸 실행하면 Fail에 대한 내용이 출력됩니다.

$ python basic_example_1.py -v
test_default_widget_size (__main__.WidgetTestCase) ... FAIL
test_widget_resize (__main__.WidgetTestCase) ... FAIL

======================================================================
FAIL: test_default_widget_size (__main__.WidgetTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "basic_example_1.py", line 36, in test_default_widget_size
    self.assertEqual(self.widget.size(), (50,50), 'incorrect default size')
AssertionError: Tuples differ: (0, 0) != (50, 50)

First differing element 0:
0
50

- (0, 0)
+ (50, 50)
?  +   +
 : incorrect default size

======================================================================
FAIL: test_widget_resize (__main__.WidgetTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "basic_example_1.py", line 40, in test_widget_resize
    self.assertEqual(self.widget.size(), (100,150), 'wrong size after resize')
AssertionError: Tuples differ: (100, 100) != (100, 150)

First differing element 1:
100
150

- (100, 100)
?        ^

+ (100, 150)
?        ^
 : wrong size after resize

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=2)


unittest의 test method 중 Fail이 확실하거나 예상되어 Skip해야 한다면 @unittest.skip() 등의 decorator을 사용할 수 있다.


@unittest.skip(reason)

해당 테스트에 대해 무조건 skip 합니다. skip 이유를 작성할 수 있습니다.


@unittest.skipIf(condition, reason)

조건이 True라면 skip 합니다.


@unittest.skipUnless(condition, reason)

조건이 True가 아니라면 skip 합니다.


@unittest.expectedFailure

fail이 예상될때 사용합니다. 예상대로 fail이라면 count하지 않습니다.

import unittest

class Widget():

    def __init__(self, name):
        self.name = name
        self.width = 0
        self.high = 0

    def size(self):
        return (self.width, self.high)

    def resize(self, width, high):
        self.width = width

        # high는 100을 넘을 수 없다.
        if high > 100: self.high = 100
        else: self.high = high

        return (self.width, self.high)

    def dispose(self):
        self.name = ""
        self.width = 0
        self.high = 0
        return True


class WidgetTestCase(unittest.TestCase):

    def setUp(self):
        self.widget = Widget('The widget')

    @unittest.skip("demonstrating skipping")
    def test_default_widget_size(self):
        self.assertEqual(self.widget.size(), (50,50), 'incorrect default size')

    @unittest.expectedFailure
    def test_widget_resize(self):
        self.widget.resize(100,150)
        self.assertEqual(self.widget.size(), (100,150), 'wrong size after resize')

    def tearDown(self):
        self.widget.dispose()

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

실행하면 skipped로 표시됩니다.

$ python basic_example_1.py -v
test_default_widget_size (__main__.WidgetTestCase) ... skipped 'demonstrating skipping'
test_widget_resize (__main__.WidgetTestCase) ... expected failure

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK (skipped=1, expected failures=1)

실제 (리턴)값과 기대값을 확인하는 assert method들은 아래 표와 같습니다.


Method 

Checks that 

 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

 assertIsNot(a, b)

 a is not b 

 assertIsNone(x)

 x is None

 assertIsNotNone(x)

 x is not None 

 assertIn(a, b)

 a in b 

 assertNotIn(a, b)

 a not in b

 assertIsInstance(a, b)

 isinstance(a, b)

 assertNotIsInstance(a, b)

 not isinstance(a, b) 

 assertRaises(exc, fun, *args, **kwds)

 fun(*args, **kwds) raises exc 

 assertRaisesRegex(exc, r, fun, *args, **kwds)

 fun(*args, **kwds) raises exc and the message matches

 regex r

 assertWarns(warn, fun, *args, **kwds)

 fun(*args, **kwds) raises warn

 assertWarnsRegex(warn, r, fun, *args, **kwds)

 fun(*args, **kwds) raises warn and the message matches

 regex r

 assertLogs(logger, level)

 The with block logs on logger with minimum level

 assertAlmostEqual(a, b)

 round(a-b, 7) == 0 

 assertNotAlmostEqual(a, b)

 round(a-b, 7) != 0 

 assertGreater(a, b)

 a > b

 assertGreaterEqual(a, b)

 a >= b 

 assertLess(a, b) 

 a < b

 assertLessEqual(a, b)

 a <= b

 assertRegex(s, r)

 r.search(s)

 assertNotRegex(s, r)

 not r.search(s)

 assertCountEqual(a, b)

 a and b have the same elements in the same number, regardless of their order

 assertMultiLineEqual(a, b)

 strings

 assertSequenceEqual(a, b)

 sequences

 assertListEqual(a, b)

 lists

 assertTupleEqual(a, b)

 tuples

 assertSetEqual(a, b)

 sets or frozensets

 assertDictEqual(a, b)

 dicts





Comments