从unittest.TestCase继承非测试功能

时间:2013-11-05 16:07:59

标签: python unit-testing inheritance

我想编写一个类来检查集合,使用unittest.TestCase.assertEqual展示的用于测试set相等性的行为。它会自动打印一条好消息,说明哪些元素仅在第一组中,哪些元素仅在第二组中。

我意识到我可以实现类似的行为,但由于它已经与unittest.TestCase.assertEqual很好地完成了,我更愿意只使用它(所以请不要回答说无益且已经很明显(但不适用于此) case)建议“不要用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语句来查看{{1}或testCaseClass此时某些testCaseNames失败。

但我在代码中看不到任何地方,我没有为任何__init__方法提供所需的参数。我想知道这是否与某些幕后魔术有关,这些魔法包含从__init__继承的类,以及我正在导入和实例化类(unittest)的事实。要为单元测试执行的文件。

也许它会检查从SetChecker继承的现有命名空间中的所有类?如果是这样,你如何对单元测试进行单元测试?

我还试图首先从TestCase继承SetChecker并试图像混合使用object一样,但这会产生很多MRO错误并且似乎比它更令人头痛值得。

我已经尝试过搜索这个但是搜索是一个很难的错误(因为TestCase参数似乎不是一个简单的问题)。

1 个答案:

答案 0 :(得分:0)

我能够通过SetChecker仅从object继承,然后在SetChecker内部提供继承自unittest.TestCase的内部类来解决此问题。

问题是unittest.main检查它运行的模块的整个命名空间。它在该模块中找到的任何继承自unittest.TestCase的类将获得完整的测试套件处理(它将尝试为它可以找到的每个test_方法构建类的实例,或仅用于{{ 1}}如果找不到runTest方法)。

在我的情况下,由于set参数是必需的,无论test_正在做什么,它都会传递一些参数(可能是函数的名称作为测试,在本例中是字符串{{ 1}})但未能传递第二个必需参数。即使这与我班级的签名一起工作(例如,假设我用两组unittest.main替换了两个不同的参数"runTest"set1,那么它会立即失败试图用该字符串进行集合操作。

似乎没有一种简单的方法可以告诉set2忽略某个或某些类。因此,通过使tuple只是中包含 unittest.main的对象,SetChecker不再找到TestCase而不再关心。

还有一个错误:在我的unittest.main函数中,我使用TestCase期望可调用,但从未向test_init类授予assertRaises函数。< / p>

以下是修复此问题的SetChecker类的修改:

__call__