1. 程式人生 > >[Python unittest] 3-Organizing test code

[Python unittest] 3-Organizing test code

沒有 import 註意 代碼 correct 執行 函數 true stl

  • 組織測試代碼

    前面已經了解到測試的原理和步驟,但只是默認類string的測試,如果是我們自己寫的類改怎麽測試呢?

    如下

    class Widget(object):
        def __init__(self,name,width=50,height=50):
            self.name = name
            self.width = width
            self.height = height
    
        def __repr__(self):
            return "Widget({0})".format(self.name)
        
        # 返回大小
        def size(self):
            return (self.width, self.height)
        
        #重設大小
        def resize(self, *args):
            try:
                self.width = args[0]
                self.height = args[1]
            except:
                pass
            return self.size()
        
        #其他方法
        def dispose(self):
            print ‘這裏是tearDown‘
            pass
    

    測試用例可以這樣寫

    import unittest
    
    class DefaultWidgetSizeTestCase(unittest.TestCase):
        def runTest(self):
            widget = Widget(‘The widget‘)
            self.assertEqual(widget.size(), (50, 50), ‘incorrect default size‘)
    

    結果

    python -m unittest users.tests.DefaultWidgetSizeTestCase
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    

    實際情況中,如果對該類我們有一百個測試用例需要寫,難道要寫100次widget = Widget(‘The widget‘)嗎?我們猿類的服務宗旨是什麽?從不寫重復的代碼
    所以unittest給我們提供了setup,setup是每一個測試用例runTest之前都會執行的函數,相當於給我們提供了一個準備環境的方法

    import unittest
    
    class SimpleWidgetTestCase(unittest.TestCase):
        def setUp(self):
            self.widget = Widget(‘The widget‘)

    class DefaultWidgetSizeTestCase(SimpleWidgetTestCase): def runTest(self): self.assertEqual(self.widget.size(), (50,50), ‘incorrect default size‘) class WidgetResizeTestCase(SimpleWidgetTestCase): def runTest(self): self.widget.resize(100,150) self.assertEqual(self.widget.size(), (100,150), ‘wrong size after resize‘)

    python -m unittest users.tests
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK
    

    兩個有 runTest 的測試用例都自動執行了父類的setup函數,當setup出錯後,測試用例就不會執行了

    相似的,unittest提供了在測試用例執行之後可以自動執行的方法tearDown,可以讓我們做一些自己需要的事

    class SimpleWidgetTestCase(unittest.TestCase):
        def setUp(self):
            self.widget = Widget(‘The widget‘)
    
        def tearDown(self):
            self.widget.dispose()
            self.widget = None
    

    python -m unittest users.tests
    這裏是tearDown
    .這裏是tearDown
    .
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK
    

    只要setup成功,不論測試用例是否正確,tearDown都會執行
    現在每一個測試用例都要繼承相同的測試夾具,這讓人感覺很不舒服,unittest提供了如下辦法,很像java的JUnit

    class WidgetTestCase(unittest.TestCase):
        def setUp(self):
            self.widget = Widget(‘The widget‘)
    
        def tearDown(self):
            self.widget.dispose()
            self.widget = None
    
        def test_default_size(self):
            self.assertEqual(self.widget.size(), (50,50),
                             ‘incorrect default size‘)
    
        def test_resize(self):
            self.widget.resize(100,150)
            self.assertEqual(self.widget.size(), (100,150),
                             ‘wrong size after resize‘)
    

    python -m unittest users.tests.WidgetTestCase
    這裏是tearDown
    .這裏是tearDown
    .
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK
    

    這裏沒有runTest函數,而是用test_開頭的方法代替,每一個test_開頭的方法都會被當作一個測試獨立運行,包括setup和tearDown也是獨立的
    如果只想運行部分測試用例改怎麽辦呢?

    if __name__ == ‘__main__‘:
        widgetTestSuite = unittest.TestSuite()
        widgetTestSuite.addTest(WidgetTestCase(‘test_default_size‘))
        widgetTestSuite.addTest(WidgetTestCase(‘test_resize‘))
    
        #suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase)
    
        unittest.TextTestRunner(verbosity=2).run(widgetTestSuite)
    

    將你想運行的測試用例加入TestSuite

    python */users/tests.py
    test_default_size (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    test_resize (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    OK
    

    或者將你想測試的類加載為suite,可以測試整個類的所有用例

    if __name__ == ‘__main__‘:
        #widgetTestSuite = unittest.TestSuite()
        #widgetTestSuite.addTest(WidgetTestCase(‘test_default_size‘))
        #widgetTestSuite.addTest(WidgetTestCase(‘test_resize‘))
    
        suite = unittest.TestLoader().loadTestsFromTestCase(WidgetTestCase)
    
        unittest.TextTestRunner(verbosity=2).run(suite)
    

    python */users/tests.py
    test_default_size (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    test_resize (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    OK
    

    更美觀的做法是

    if __name__ == ‘__main__‘:
        def suite():
            suite = unittest.TestSuite()
            suite.addTest(WidgetTestCase(‘test_default_size‘))
            suite.addTest(WidgetTestCase(‘test_resize‘))
            return suite
    
    
        def suite_map():
            tests = [‘test_default_size‘, ‘test_resize‘]
            return unittest.TestSuite(map(WidgetTestCase, tests))
    
    
        unittest.TextTestRunner(verbosity=2).run(suite())
    

    python */users/tests.py
    test_default_size (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    test_resize (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.000s
    
    OK
    

    有時候需要阻止各個測試用例,很簡單測試套件TestSuite像TestCase一樣被加入TestSuite

    if __name__ == ‘__main__‘:
        def suite():
            suite = unittest.TestSuite()
            suite.addTest(WidgetTestCase(‘test_default_size‘))
            suite.addTest(WidgetTestCase(‘test_resize‘))
            return suite
    
    
        def suite_map():
            tests = [‘test_default_size‘, ‘test_resize‘]
            return unittest.TestSuite(map(WidgetTestCase, tests))
    
        alltest = unittest.TestSuite([suite(), suite_map()])
    
        unittest.TextTestRunner(verbosity=2).run(alltest)
    

    python */users/tests.py
    test_default_size (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    test_resize (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    test_default_size (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    test_resize (__main__.WidgetTestCase) ... 這裏是tearDown
    ok
    
    ----------------------------------------------------------------------
    Ran 4 tests in 0.001s
    
    OK
    

  測試代碼可以放在任何地方,不過有幾個原則需要註意:

  •   測試模塊可以獨立在命令行執行
  •   測試代碼可以很容易的從項目中分離出來 
  • 沒有合理的理由不要改變測試代碼來適應它所測試的代碼
  • 代碼的修改應該比測試代碼的修改頻繁的多
  • 被測試代碼重構更容易
  • 用C編寫的模塊的測試無論如何必須是獨立的模塊,那麽為什麽不一致呢?
  • 測試策略發生變化,不需要更改源代碼

[Python unittest] 3-Organizing test code