Python unit test с базовым и подклассом

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

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 сам по себе, и мне нужен механизм, чтобы это не происходило.

Ответ 1

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

Ответ 2

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

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

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

Ответ 3

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

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()

Ответ 4

Ответ Мэтью Маршалла велик, но для этого требуется, чтобы вы наследовали два класса в каждом из ваших тестовых случаев, что подвержено ошибкам. Вместо этого я использую это (python >= 2.7):

class BaseTest(unittest.TestCase):

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

Ответ 5

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

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

Ответ 7

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

unittest.main()

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

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

Итак, вы выполняете только тесты в классе TestClass

Ответ 8

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

По сравнению с методом mixin, такой как PyCharm, не будет жаловаться на отсутствие методов unit test в базовом классе.

Если базовый класс наследуется от этого класса, он должен будет переопределить методы 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 = []

Ответ 9

Просто переименуйте метод 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()`

Ответ 10

Я сделал примерно то же, что @Vladim P. (fooobar.com/questions/59545/...), но слегка изменил:

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())

и там мы идем.

Ответ 11

Измените имя метода BaseTest на setUp:

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)

Выход:

Ran 2 тесты в 0,000 с

Вызов BaseTest: testCommon Calling
SubTest1: testSub1 Вызов
BaseTest: testCommon Calling
SubTest2: testSub2

Из документация:

TestCase.setUp()
Метод, призванный подготовить тестовое крепление. Это вызывается непосредственно перед вызовом Метод испытания; любое исключение, вызванное этот метод будет рассматриваться как а не с ошибкой теста. реализация по умолчанию ничего не делает.