簡體   English   中英

從unittest.TestCase繼承以實現非測試功能

[英]Inheriting from unittest.TestCase for non-test functionality

我想編寫一個類來准確地使用unittest.TestCase.assertEqual為測試set相等性而表現出的行為來檢查集。 它會自動打印一條漂亮的消息,說明哪些元素僅在第一組中,哪些僅在第二組中。

我意識到我可以實現類似的行為,但是由於已經使用unittest.TestCase.assertEqual很好地完成了操作,所以我只想利用它(所以請不要回答那些無用且已經很明顯的建議(但不適用於這種情況)) “不要用unittest.TestCase解決此問題”)

這是我的SetChecker類的代碼:

import unittest
class SetChecker(unittest.TestCase):
    """
    SetChecker(set1, set2) creates a set checker from the two passed Python set
    objects. Printing the SetChecker uses unittest.TestCase.assertEqual to test
    if the sets are equal and automatically reveal the elements that are in one
    set but not the other if they are unequal. This provides an efficient way
    to detect differences in possibly large set objects. Note that this is not
    a unittest object, just a wrapper to gain access to the helpful behavior of
    unittest.TestCase.assertEqual when used on sets.
    """
    EQUAL_MSG = "The two sets are equivalent."

    def __init__(self, set1, set2, *args, **kwargs):
        assert isinstance(set1, set)
        assert isinstance(set2, set)
        super(self.__class__, self).__init__(*args, **kwargs)
        try:
            self.assertEqual(set1, set2)
            self._str = self.EQUAL_MSG
            self._str_lines = [self._str]
            self._indxs = None
        except AssertionError, e:
            self._str = str(e)
            self._str_lines = self._str.split('\n')

            # Find the locations where a line starts with 'Items '.
            # This is the fixed behavior of unittest.TestCase.
            self._indxs = [i for i,y in enumerate(self._str_lines) 
                           if y.startswith('Items ')]

    def __repr__(self):
        """
        Convert SetChecker object into a string to be printed.
        """
        return self._str

    __str__ = __repr__ # Ensure that `print` and __repr__ do the same thing.

    def runTest(self):
        """
        Required by any sub-class of unittest.TestCase. Solely used to inherit
        from TestCase and is not implemented for any behavior.
        """
        pass

    def in_first_set_only(self):
        """
        Return a list of the items reported to exist only in the first set. If
        the sets are equivalent, returns a string saying so.
        """
        return (set(self._str_lines[1:self._indxs[1]]) 
                if self._indxs is not None else self.EQUAL_MSG)

    def in_second_set_only(self):
        """
        Return a list of the items reported to exist only in the second set. If
        the sets are equivalent, returns a string saying so.
        """
        return (set(self._str_lines[1+self._indxs[1]:]) 
                if self._indxs is not None else self.EQUAL_MSG)

當我在IPython中使用它時,它工作正常:

In [1]: from util.SetChecker import SetChecker

In [2]: sc = SetChecker(set([1,2,3, 'a']), set([2,3,4, 'b']))

In [3]: sc
Out[3]:
Items in the first set but not the second:
'a'
1
Items in the second set but not the first:
'b'
4

In [4]: print sc
Items in the first set but not the second:
'a'
1
Items in the second set but not the first:
'b'
4

In [5]: sc.in_first_set_only()
Out[5]: set(["'a'", '1'])

In [6]: sc.in_second_set_only()
Out[6]: set(["'b'", '4'])

但是現在我也想為此類編寫單元測試。 所以我做了一個TestSetChecker類。 這是該代碼:

from util.SetChecker import SetChecker
class TestSetChecker(unittest.TestCase):
    """
    Test class for providing efficient comparison and printing of
    the difference between to sets.
    """
    def setUp(self):
        """
        Create examples for testing.
        """
        self.set1 = set([1, 2, 3, 'a'])
        self.set2 = set([2, 3, 4, 'b'])
        self.set3 = set([1,2])
        self.set4 = set([1,2])
        self.bad_arg = [1,2]
        self.expected_first = set(['1', 'a'])
        self.expected_second = set(['4', 'b'])
        self.expected_equal_message = SetChecker.EQUAL_MSG
        self.expected_print_string = (
            "Items in the first set but not the second:\n'a'\n1\n"
            "Items in the second set but not the first:\n'b'\n4")

    def test_init(self):
        """
        Test constructor, assertions on args, and that instance is of proper
        type and has expected attrs.
        """
        s = SetChecker(self.set1, self.set2)
        self.assertIsInstance(s, SetChecker)
        self.assertTrue(hasattr(s, "_str")) 
        self.assertTrue(hasattr(s, "_str_lines"))
        self.assertTrue(hasattr(s, "_indxs"))
        self.assertEqual(s.__repr__, s.__str__)
        self.assertRaises(AssertionError, s, *(self.bad_arg, self.set1))

    def test_repr(self):
        """
        Test that self-printing is correct.
        """
        s1 = SetChecker(self.set1, self.set2)
        s2 = SetChecker(self.set3, self.set4)
        self.assertEqual(str(s1), self.expected_print_string)
        self.assertEqual(str(s2), self.expected_equal_message)

    def test_print(self):
        """
        Test that calling `print` on SetChecker is correct.
        """
        s1 = SetChecker(self.set1, self.set2)
        s2 = SetChecker(self.set3, self.set4)
        s1_print_output = s1.__str__()
        s2_print_output = s2.__str__()
        self.assertEqual(s1_print_output, self.expected_print_string)
        self.assertEqual(s2_print_output, self.expected_equal_message)

    def test_in_first_set_only(self):
        """
        Test that method gives list of set elements found only in first set.
        """
        s1 = SetChecker(self.set1, self.set2)
        s2 = SetChecker(self.set3, self.set4)
        fs1 = s1.in_first_set_only()
        fs2 = s2.in_first_set_only()
        self.assertEqual(fs1, self.expected_first)
        self.assertEqual(fs2, self.expected_equal_message)

    def test_in_second_set_only(self):
        """
        Test that method gives list of set elements found only in second set.
        """
        s1 = SetChecker(self.set1, self.set2)
        s2 = SetChecker(self.set3, self.set4)
        ss1 = s1.in_second_set_only()
        ss2 = s2.in_second_set_only()
        self.assertEqual(ss1, self.expected_second)
        self.assertEqual(ss2, self.expected_equal_message)        

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

據我所知, TestSetChecker與我編寫的許多其他單元測試類沒有區別(除了要測試的特定功能之外)。

但是,當我嘗試執行包含單元測試的文件時,看到一個非常不尋常的__init__錯誤:

EMS@computer ~/project_dir/test $ python TestSetChecker.py
Traceback (most recent call last):
  File "TestSetChecker.py", line 84, in <module>
    unittest.main()
  File "/opt/python2.7/lib/python2.7/unittest/main.py", line 94, in __init__
    self.parseArgs(argv)
  File "/opt/python2.7/lib/python2.7/unittest/main.py", line 149, in parseArgs
    self.createTests()
  File "/opt/python2.7/lib/python2.7/unittest/main.py", line 155, in createTests
    self.test = self.testLoader.loadTestsFromModule(self.module)
  File "/opt/python2.7/lib/python2.7/unittest/loader.py", line 65, in loadTestsFromModule
    tests.append(self.loadTestsFromTestCase(obj))
  File "/opt/python2.7/lib/python2.7/unittest/loader.py", line 56, in loadTestsFromTestCase
    loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames))
TypeError: __init__() takes at least 3 arguments (2 given)

在我的環境中,帶有Python unittest源代碼的目錄是只讀的,因此我無法在其中添加pdb或什至print語句,以查看此時__init__失敗的testCaseClasstestCaseNames是什么。

但是我看不到代碼中沒有為任何__init__方法提供所需參數的地方。 我想知道這是否與某些繼承自unittest類的幕后魔術有關,以及與我正在導入並實例化要為unit執行的文件中的類( SetChecker )有關的事實試驗。

也許它會檢查現有名稱空間中繼承自TestCase所有類? 如果是這樣,如何對單元測試進行單元測試?

我還嘗試首先使SetCheckerobject繼承,並嘗試像混入一樣使用TestCase ,但是這產生了許多MRO錯誤,並且似乎比它值得的更頭痛。

我嘗試搜索此內容,但搜索起來很困難(因為__init__參數似乎不是直接的問題)。

通過使SetChecker僅從object繼承,然后在SetChecker內部提供從unittest.TestCase繼承的內部類,我能夠解決此問題。

問題是unittest.main檢查其運行模塊的整個名稱空間。 它發現模塊,從繼承任何類unittest.TestCase將得到充分的測試套件處理(它會嘗試構建類的實例每個test_方法,它可以找到,或者只是為了runTest ,如果發現沒有test_方法) 。

在我的情況下,由於set參數是必需的,無論unittest.main在做什么,它都傳遞了一些參數(可能是要當作測試的函數的名稱,在這種情況下為字符串"runTest" ),但是未能傳遞第二個必需的參數。 即使這與我的類的簽名一起起作用(例如,假設我用兩個set的tuple替換了兩個不同的參數set1set2 ),一旦嘗試對該字符串進行set操作,它將立即失敗。

似乎沒有一種簡單的方法可以告訴unittest.main忽略某個或某些類。 因此,通過使SetChecker只是其中具有 TestCase的對象, unittest.main不再查找該TestCase並且不再在乎。

還有一個錯誤:在我的test_init函數中,我使用assertRaises期望可調用,但從未給我的SetChecker類提供__call__函數。

這是對SetChecker類的修改,為我修復了此問題:

class SetChecker(object):
    """
    SetChecker(set1, set2) creates a set checker from the two passed Python set
    objects. Printing the SetChecker uses unittest.TestCase.assertEqual to test
    if the sets are equal and automatically reveal the elements that are in one
    set but not the other if they are unequal. This provides an efficient way
    to detect differences in possibly large set objects. Note that this is not
    a unittest object, just a wrapper to gain access to the helpful behavior of
    unittest.TestCase.assertEqual when used on sets.
    """
    EQUAL_MSG = "The two sets are equivalent."

    class InternalTest(unittest.TestCase):
        def runTest(self): pass

    def __init__(self, set1, set2):
        assert isinstance(set1, set)
        assert isinstance(set2, set)
        self.int_test = SetChecker.InternalTest()

        try:
            self.int_test.assertEqual(set1, set2)
            self._str = self.EQUAL_MSG
            self._str_lines = [self._str]
            self._indxs = None
        except AssertionError, e:
            self._str = str(e)
            self._str_lines = self._str.split('\n')

            # Find the locations where a line starts with 'Items '.
            # This is the fixed behavior of unittest.TestCase.
            self._indxs = [i for i,y in enumerate(self._str_lines) 
                           if y.startswith('Items ')]
    @classmethod
    def __call__(klass, *args, **kwargs):
        """
        Makes the class callable such that calling it like a function is the
        same as constructing a new instance.
        """
        return klass(*args, **kwargs)

    # Everything else below is the same...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM