1

等価性unittest.TestCase.assertEqualをテストするために示す動作を正確に使用して、セットをチェックするクラスを作成したいと考えています。setどの要素が最初のセットにのみ含まれ、どの要素が 2 番目のセットにのみ含まれるかを示す適切なメッセージが自動的に出力されます。

同様の動作を実装できることはわかっていますが、それはすでに でうまく行われているので、それ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)

unittest私の環境では、Python ソース コードを含むディレクトリは読み取り専用であるため、ステートメントを追加しpdbたり、printステートメントを追加したりして、この時点で何が失敗したtestCaseClassかを確認することはできません。testCaseNames__init__

しかし、どのメソッドにも必要な引数を提供できていないコード内の場所は見当たりません__init__。これは、ユニット テスト用に実行されるファイル内でunittestクラス ( ) をインポートしてインスタンス化するという事実と、継承するクラスを使用した舞台裏の魔法と関係があるのではないかと考えています。SetChecker

たぶん、から継承する既存の名前空間内のすべてのクラスをチェックしますTestCaseか? もしそうなら、ユニットテストをどのようにユニットテストしますか?

また、最初にSetChecker継承元を作成し、ミックスインのようobjectに使用しようとしTestCaseましたが、多くの MRO エラーが発生し、価値があるよりも頭痛の種に思えました。

__init__これを検索してみましたが、検索するのが難しいエラーです (引数に関する単純な問題ではないように見えるため)。

4

1 に答える 1

0

SetCheckerから継承するobjectだけでSetChecker、内部クラスから継承する内部クラスを提供することで、これを回避できましたunittest.TestCase

問題は、unittest.main実行元のモジュールの名前空間全体を検査することです。継承元のモジュールで見つかったクラスはすべてunittest.TestCase、完全なテスト スイートの処理を取得します (見つけたメソッドごとtest_に、またはメソッドが見つからrunTestない場合にのみ、クラスのインスタンスを構築しようとしtest_ます)。

私の場合、設定された引数が必要であるため、それが何をしてunittest.mainいても、いくつかの引数 (おそらくテストとして扱う関数の名前、この場合は string "runTest") を渡していますが、2 番目に必要な引数を渡すことができません。これが私のクラスの署名で機能したとしても (たとえば、2 つの異なる引数を 2 セットの に置き換えたset1set2tupleます)、その文字列でセット操作を実行しようとすると、すぐに失敗します。

unittest.main特定のクラスを無視するように指示する簡単な方法はないようです。そのため、内部にSetCheckerあるオブジェクトだけを作成することで、それを見つけたり気にしたりしなくなります。 TestCaseunittest.mainTestCase

もう 1 つのバグがありました。私のtest_init関数では、callable を期待する which を使用していますが、クラスに関数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...
于 2013-11-05T17:16:43.137 に答える