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