Модульный тест Python с базовым и подклассом - PullRequest
124 голосов
/ 24 августа 2009

В настоящее время у меня есть несколько модульных тестов, которые используют общий набор тестов. Вот пример:

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 не вызывался?

EDIT: Вместо запуска 5 тестов, указанных выше, я хочу, чтобы он выполнял только 4 теста, 2 из SubTest1 и еще 2 из SubTest2. Кажется, что Python unittest запускает оригинальный BaseTest сам по себе, и мне нужен механизм, чтобы предотвратить это.

Ответы [ 14 ]

141 голосов
/ 24 августа 2009

Используйте множественное наследование, поэтому ваш класс с общими тестами сам по себе не наследуется от 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()
113 голосов
/ 06 сентября 2014

Не используйте множественное наследование, оно укусит вас позже .

Вместо этого вы можете просто переместить ваш базовый класс в отдельный модуль или обернуть его пустым классом:

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
30 голосов
/ 03 апреля 2014

Вы можете решить эту проблему с помощью одной команды:

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()
21 голосов
/ 17 июля 2013

Ответ Мэтью Маршалла великолепен, но он требует, чтобы вы унаследовали от двух классов в каждом из ваших тестовых случаев, что подвержено ошибкам. Вместо этого я использую это (python> = 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()
8 голосов
/ 24 августа 2009

Чего ты пытаешься достичь? Если у вас есть общий тестовый код (утверждения, шаблонные тесты и т. Д.), Поместите их в методы, для которых нет префикса 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()
6 голосов
/ 23 июня 2011

Ответ Мэтью - тот, который мне нужно было использовать, так как я все еще на 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
4 голосов
/ 17 сентября 2014

Другой вариант не выполнить

unittest.main()

Вместо этого вы можете использовать

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

Таким образом, вы выполняете тесты только в классе TestClass

4 голосов
/ 24 июля 2014

Я решил решить эту проблему, скрыв методы тестирования, если используется базовый класс. Таким образом, тесты не пропускаются, поэтому результаты теста могут быть зелеными вместо желтых во многих инструментах отчетов о тестировании.

По сравнению с методом mixin, ide вроде PyCharm не будет жаловаться на то, что в базовом классе отсутствуют методы модульного тестирования.

Если базовый класс наследует от этого класса, ему необходимо переопределить методы setUpClass и tearDownClass.

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 = []
1 голос
/ 19 июня 2018

Вы можете добавить __test_ = False в классе BaseTest, но если вы добавите его, помните, что вы должны добавить __test__ = True в производные классы, чтобы иметь возможность запускать тесты.

import unittest

class BaseTest(unittest.TestCase):
    __test__ = False

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

class SubTest1(BaseTest):
    __test__ = True

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


class SubTest2(BaseTest):
    __test__ = True

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

if __name__ == '__main__':
    unittest.main()
1 голос
/ 17 мая 2016

Я сделал примерно так же, как @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())

и мы идем.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...