2009-08-24 16 views
101

我目前有幾個單元測試共享一組通用測試。這裏有一個例子:基本和子類的Python單元測試

import unittest 

class BaseTest(unittest.TestCase): 

    def testCommon(self): 
     print 'Calling BaseTest:testCommon' 
     value = 5 
     self.assertEquals(value, 5) 

class SubTest1(BaseTest): 

    def testSub1(self): 
     print 'Calling SubTest1:testSub1' 
     sub = 3 
     self.assertEquals(sub, 3) 


class SubTest2(BaseTest): 

    def testSub2(self): 
     print 'Calling SubTest2:testSub2' 
     sub = 4 
     self.assertEquals(sub, 4) 

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

上面的輸出是:

Calling BaseTest:testCommon 
.Calling BaseTest:testCommon 
.Calling SubTest1:testSub1 
.Calling BaseTest:testCommon 
.Calling SubTest2:testSub2 
. 
---------------------------------------------------------------------- 
Ran 5 tests in 0.000s 

OK 

有沒有辦法改寫的上方,正好是第一testCommon不叫?

編輯: 而是上面跑5次測試,我希望它只能運行4次測試,2從SubTest1另有2從SubTest2。 Python單元測試似乎是自己運行原始的BaseTest,我需要一種機制來防止這種情況的發生。

+0

我看到沒有人提到它,但是您是否可以選擇更改主要部分並運行包含BaseTest所有子類的測試套件? – 2015-09-27 04:28:49

回答

125

使用多重繼承,所以你的類與普通測試本身不會從TestCase繼承。

import unittest 

class CommonTests(object): 
    def testCommon(self): 
     print 'Calling BaseTest:testCommon' 
     value = 5 
     self.assertEquals(value, 5) 

class SubTest1(unittest.TestCase, CommonTests): 

    def testSub1(self): 
     print 'Calling SubTest1:testSub1' 
     sub = 3 
     self.assertEquals(sub, 3) 


class SubTest2(unittest.TestCase, CommonTests): 

    def testSub2(self): 
     print 'Calling SubTest2:testSub2' 
     sub = 4 
     self.assertEquals(sub, 4) 

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

這是迄今爲止最優雅的解決方案。 – 2009-08-24 17:23:13

+0

這甚至適用於setUp和tearDown :) – 2009-12-17 18:14:43

+20

如果您顛倒基類的順序,此方法僅適用於setUp和tearDown方法。因爲這些方法是在unittest.TestCase中定義的,並且它們不調用super(),所以CommonTests中的任何setUp和tearDown方法都必須在MRO中處於第一位,否則它們將不會被調用。 – 2010-10-11 16:35:50

-2

更改BaseTest方法名設置:

class BaseTest(unittest.TestCase): 
    def setUp(self): 
     print 'Calling BaseTest:testCommon' 
     value = 5 
     self.assertEquals(value, 5) 


class SubTest1(BaseTest): 
    def testSub1(self): 
     print 'Calling SubTest1:testSub1' 
     sub = 3 
     self.assertEquals(sub, 3) 


class SubTest2(BaseTest): 
    def testSub2(self): 
     print 'Calling SubTest2:testSub2' 
     sub = 4 
     self.assertEquals(sub, 4) 

輸出:

冉2個試驗在0.000s

調用BaseTest:testCommon調用
SubTest1:testSub1調用
BaseTest:testCommon調用
SubTest2:testSub 2

documentation

TestCase.setUp()
方法調用來 製備測試夾具。在調用 測試方法之前立即調用 ;由 引發的任何異常將被視爲 錯誤,而不是測試失敗。 默認實現什麼都不做。

+0

這會工作,如果我有n testCommon,我應該把它們放在'setUp'下? – 2009-08-24 16:50:02

+1

是的,你應該把所有不是實際測試用例的代碼置於setUp之下。 – 2009-08-24 16:56:18

+0

但是如果一個子類有多個'test ...'方法,一次又一次地執行'setUp',每個這樣的方法一次;所以在這裏進行測試並不是一個好主意! – 2009-08-24 18:14:51

7

你想達到什麼目的?如果你有共同的測試代碼(斷言,模板測試等),然後將它們放在沒有前綴test的方法中,所以unittest將不會加載它們。

import unittest 

class CommonTests(unittest.TestCase): 
     def common_assertion(self, foo, bar, baz): 
      # whatever common code 
      self.assertEqual(foo(bar), baz) 

class BaseTest(CommonTests): 

    def testCommon(self): 
     print 'Calling BaseTest:testCommon' 
     value = 5 
     self.assertEquals(value, 5) 

class SubTest1(CommonTests): 

    def testSub1(self): 
     print 'Calling SubTest1:testSub1' 
     sub = 3 
     self.assertEquals(sub, 3) 

class SubTest2(CommonTests): 

    def testSub2(self): 
     print 'Calling SubTest2:testSub2' 
     sub = 4 
     self.assertEquals(sub, 4) 

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

根據你的建議,在測試子類時,common_assertion()會自動運行嗎? – Stewart 2015-04-23 12:12:16

6

馬修的答案是我需要使用的答案,因爲我仍然在2.5。 但是從2.7開始,你可以在你想要跳過的任何測試方法上使用@ unittest.skip()裝飾器。

http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures

你需要實現自己的跳躍裝飾檢查基本類型。以前沒有使用過這個功能,但是從我的頭頂,你可以使用BaseTest爲標記型調理跳躍:

def skipBaseTest(obj): 
    if type(obj) is BaseTest: 
     return unittest.skip("BaseTest tests skipped") 
    return lambda func: func 
18

馬修·馬歇爾的回答是偉大的,但它需要你繼承每個測試用例都有兩個類,這很容易出錯。相反,我用這個(蟒蛇> = 2.7):

class BaseTest(unittest.TestCase): 

    @classmethod 
    def setUpClass(cls): 
     if cls is BaseTest: 
      raise unittest.SkipTest("Skip BaseTest tests, it's a base class") 
     super(BaseTest, cls).setUpClass() 
+1

這很整齊。有沒有辦法避免使用跳過?對我而言,跳過是不希望的,並用於指示當前測試計劃中的問題(使用代碼還是測試)? – 2014-02-05 19:29:54

+0

@ZacharyYoung我不知道,也許其他答案可以幫助。 – 2014-02-06 08:49:18

+0

@ZacharyYoung我試着解決這個問題,看我的答案。 – simonzack 2014-07-24 02:16:36

27

您可以用一條命令解決這個問題:

del(BaseTest) 

因此,代碼是這樣的:

import unittest 

class BaseTest(unittest.TestCase): 

    def testCommon(self): 
     print 'Calling BaseTest:testCommon' 
     value = 5 
     self.assertEquals(value, 5) 

class SubTest1(BaseTest): 

    def testSub1(self): 
     print 'Calling SubTest1:testSub1' 
     sub = 3 
     self.assertEquals(sub, 3) 


class SubTest2(BaseTest): 

    def testSub2(self): 
     print 'Calling SubTest2:testSub2' 
     sub = 4 
     self.assertEquals(sub, 4) 

del(BaseTest) 

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

有趣。你能解釋爲什麼這個工作嗎? – mrucci 2014-06-29 19:32:51

+3

BaseTest是模塊在定義時的成員,所以它可以用作SubTest的基類。在定義完成之前,del()將它作爲一個成員移除,因此unittest框架在模塊中搜索TestCase子類時找不到它。 – mhsmith 2015-05-05 13:51:46

+1

這是一個很棒的答案!我比@MatthewMarshall更喜歡它,因爲在他的解決方案中,你會從pylint中得到語法錯誤,因爲'self.assert *'方法不存在於標準對象中。 – SimplyKnownAsG 2015-05-07 17:59:29

4

我想過解決這個問題的一種方法是在使用基類時隱藏測試方法。這樣,測試不會被跳過,因此在許多測試報告工具中,測試結果可以是綠色而不是黃色。

與mixin方法相比,IDE的PyCharm不會抱怨單元測試方法從基類中丟失。

如果基類繼承自該類,則需要覆蓋setUpClasstearDownClass方法。

class BaseTest(unittest.TestCase): 
    @classmethod 
    def setUpClass(cls): 
     cls._test_methods = [] 
     if cls is BaseTest: 
      for name in dir(cls): 
       if name.startswith('test') and callable(getattr(cls, name)): 
        cls._test_methods.append((name, getattr(cls, name))) 
        setattr(cls, name, lambda self: None) 

    @classmethod 
    def tearDownClass(cls): 
     if cls is BaseTest: 
      for name, method in cls._test_methods: 
       setattr(cls, name, method) 
      cls._test_methods = [] 
78

不要使用多重繼承,它會咬你later

相反,你可以將你的基類成單獨的模塊或與空白類包裝它:

import unittest 

class BaseTestCases: 

    class BaseTest(unittest.TestCase): 

     def testCommon(self): 
      print 'Calling BaseTest:testCommon' 
      value = 5 
      self.assertEquals(value, 5) 


class SubTest1(BaseTestCases.BaseTest): 

    def testSub1(self): 
     print 'Calling SubTest1:testSub1' 
     sub = 3 
     self.assertEquals(sub, 3) 


class SubTest2(BaseTestCases.BaseTest): 

    def testSub2(self): 
     print 'Calling SubTest2:testSub2' 
     sub = 4 
     self.assertEquals(sub, 4) 

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

輸出:

Calling BaseTest:testCommon 
.Calling SubTest1:testSub1 
.Calling BaseTest:testCommon 
.Calling SubTest2:testSub2 
. 
---------------------------------------------------------------------- 
Ran 4 tests in 0.001s 

OK 
+4

這是我的最愛。這是最簡單的手段,不會影響重寫方法,不會改變MRO,並允許我在基類中定義setUp,setUpClass等。 – Hannes 2015-05-27 22:18:24

+4

我真的不明白它(魔術從哪裏來的?),但是根據我的說法,這絕對是最好的解決方案:)來自Java,我討厭多重繼承...... – Edouardb 2016-02-24 05:46:50

+0

你讓我的一天,謝謝! – 2016-03-17 19:21:17

4

另一種選擇是不執行

unittest.main() 

而不是你可以使用

suite = unittest.TestLoader().loadTestsFromTestCase(TestClass) 
unittest.TextTestRunner(verbosity=2).run(suite) 

所以你只能在類執行測試TestClass

+0

這是最簡單的解決方案。不用修改'unittest.main()'收集到默認套件中,而是形成顯式套件並運行測試。 – zgoda 2017-05-26 09:55:41

0

只需重命名testCommon方法到別的東西。 Unittest(通常)會跳過任何沒有「測試」的東西。

快速和簡單的

import unittest 

    class BaseTest(unittest.TestCase): 

    def methodCommon(self): 
     print 'Calling BaseTest:testCommon' 
     value = 5 
     self.assertEquals(value, 5) 

    class SubTest1(BaseTest): 

     def testSub1(self): 
      print 'Calling SubTest1:testSub1' 
      sub = 3 
      self.assertEquals(sub, 3) 


    class SubTest2(BaseTest): 

     def testSub2(self): 
      print 'Calling SubTest2:testSub2' 
      sub = 4 
      self.assertEquals(sub, 4) 

    if __name__ == '__main__': 
     unittest.main()` 
+0

這可能會導致在任一SubTest中未運行methodCommon測試。 – 2015-11-17 22:31:42

0

我做了大約比@Vladim P.(https://stackoverflow.com/a/25695512/2451329)相同,但略作修改:

import unittest2 


from some_module import func1, func2 


def make_base_class(func): 

    class Base(unittest2.TestCase): 

     def test_common1(self): 
      print("in test_common1") 
      self.assertTrue(func()) 

     def test_common2(self): 
      print("in test_common1") 
      self.assertFalse(func(42)) 

    return Base 



class A(make_base_class(func1)): 
    pass 


class B(make_base_class(func2)): 

    def test_func2_with_no_arg_return_bar(self): 
     self.assertEqual("bar", func2()) 

和我們走吧。