it-swarm.com.ru

Модульный тест Python с базовым и подклассом

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

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 __== '__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?

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

113
Thierry Lam

Используйте множественное наследование, поэтому ваш класс с общими тестами сам по себе не наследуется от 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 __== '__main__':
    unittest.main()
136
Matthew Marshall

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

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

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 __== '__main__':
    unittest.main()

Результат:

Calling BaseTest:testCommon
.Calling SubTest1:testSub1
.Calling BaseTest:testCommon
.Calling SubTest2:testSub2
.
----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK
105
Vadim P.

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

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 __== '__main__':
    unittest.main()
29
Wojciech B.

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

Чего ты пытаешься достичь? Если у вас есть общий тестовый код (утверждения, тесты шаблонов и т.д.), Поместите их в методы, для которых нет префикса 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 __== '__main__':
    unittest.main()
8
John Millikin

Ответ Мэтью - тот, который мне нужно было использовать, так как я все еще на 2.5 . Но с 2.7 вы можете использовать декоратор @ unittest.skip () для любых методов тестирования, которые вы хотите пропустить.

http://docs.python.org/library/unittest.html#skipping-tests-and-expected-failures

Вам нужно будет реализовать собственный пропускающий декоратор, чтобы проверить базовый тип. Раньше я не пользовался этой функцией, но в верхней части моей головы вы можете использовать BaseTest в качестве типа marker для условия пропуска:

def skipBaseTest(obj):
    if type(obj) is BaseTest:
        return unittest.skip("BaseTest tests skipped")
    return lambda func: func
6
Jason A

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

unittest.main()

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

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

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

4
Angel

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

По сравнению с методом 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 = []
4
simonzack

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

и там мы идем.

1
gst

Так что это своего рода старая тема, но я столкнулся с этой проблемой сегодня и подумал о своем собственном взломе. Он использует декоратор, который делает значения функций None при обращении через базовый класс. Не нужно беспокоиться о настройке и настройке класса, потому что если в базовом классе нет тестов, они не запустятся.

import types
import unittest


class FunctionValueOverride(object):
    def __init__(self, cls, default, override=None):
        self.cls = cls
        self.default = default
        self.override = override

    def __get__(self, obj, klass):
        if klass == self.cls:
            return self.override
        else:
            if obj:
                return types.MethodType(self.default, obj)
            else:
                return self.default


def fixture(cls):
    for t in vars(cls):
        if not callable(getattr(cls, t)) or t[:4] != "test":
            continue
        setattr(cls, t, FunctionValueOverride(cls, getattr(cls, t)))
    return cls


@fixture
class BaseTest(unittest.TestCase):
    def testCommon(self):
        print('Calling BaseTest:testCommon')
        value = 5
        self.assertEqual(value, 5)


class SubTest1(BaseTest):
    def testSub1(self):
        print('Calling SubTest1:testSub1')
        sub = 3
        self.assertEqual(sub, 3)


class SubTest2(BaseTest):

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

if __== '__main__':
    unittest.main()
0
Nathan Buckner

Просто переименуйте метод testCommon во что-то другое. Unittest (обычно) пропускает все, что не имеет «test».

Быстро и просто 

  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 __== '__main__':
      unittest.main()`
0
Kashif Siddiqui

Вы можете добавить __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 __== '__main__':
    unittest.main()
0
peja

Начиная с Python 3.2, вы можете добавить функцию test_loader в модуль, чтобы контролировать, какие тесты (если таковые имеются) обнаруживаются механизмом обнаружения тестов.

Например, следующее будет загружать только тестовые примеры исходного SubTest1 и SubTest2 постера, игнорируя Base:

def load_tests(loader, standard_tests, pattern):
    suite = TestSuite()
    suite.addTests([SubTest1, SubTest2])
    return suite

Должна быть возможность перебирать standard_tests (TestSuite, содержащую тесты, найденный загрузчик по умолчанию) и вместо этого копировать все, кроме Base, в suite, но вложенная природа TestSuite.__iter__ делает это намного сложнее.

0
jbosch