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

547   14  

14 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

unittest.main()

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

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

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

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

и там мы идем.

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

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

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

вы можете добавить __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()

начиная с 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 (a TestSuite содержащий тесты, найденные загрузчиком по умолчанию) и скопируйте все, кроме Base до suite вместо этого, но вложенная природа TestSuite.__iter__ делает это намного сложнее.

измените имя метода 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)

выход:

выполнил 2 теста за 0,000 s

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

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

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

Comments

    Ничего не найдено.