Мне было интересно, когда мы запускаем unittest.main()
, как Python знает, какие подклассы unittest.Testcase
имеют?
Например, если я добавляю класс FromRomanBadInput(unittest.TestCase)
, как известно unittest
для запуска этого?
Мне было интересно, когда мы запускаем unittest.main()
, как Python знает, какие подклассы unittest.Testcase
имеют?
Например, если я добавляю класс FromRomanBadInput(unittest.TestCase)
, как известно unittest
для запуска этого?
Итак, я огляделся в моем каталоге Python27/Lib
...
unittest.main
на самом деле является псевдонимом для класса, unittest.TestProgram
. Итак, что происходит, вы создаете экземпляр этого и его __init__
работает, который выполняет кучу проверки работоспособности и конфигурации, включая динамический импорт модуля, из которого вы его вызвали (он использует функцию __import__
, с __main__
как имя модуля для импорта по умолчанию). Итак, теперь он имеет атрибут self.module
, который содержит объект модуля, который представляет ваш источник.
В конце концов, он получает этот код:
self.test = self.testLoader.loadTestsFromModule(self.module)
где self.testLoader
- это экземпляр unittest.TestLoader
. Этот метод содержит, среди прочего:
for name in dir(module):
obj = getattr(module, name)
if isinstance(obj, type) and issubclass(obj, case.TestCase):
tests.append(self.loadTestsFromTestCase(obj))
Таким образом, он использует dir
вашего объекта модуля, чтобы получить имена всех глобальных переменных, которые вы определили (включая классы), фильтры, которые только для классов, которые происходят из unittest.TestCase
(локально, case.TestCase
является псевдоним для этого), а затем ищет методы тестирования внутри этих классов для добавления в список tests
. Этот поиск ведет себя аналогично:
def isTestMethod(attrname, testCaseClass=testCaseClass,
prefix=self.testMethodPrefix):
return attrname.startswith(prefix) and \
hasattr(getattr(testCaseClass, attrname), '__call__')
testFnNames = filter(isTestMethod, dir(testCaseClass))
поэтому он использует класс dir
для получения списка имен для поиска, ищет атрибуты с этими именами и выбирает те, которые начинаются с self.testMethodPrefix
('test'
по умолчанию) и которые вызываются (имеют, в свою очередь, атрибут __call__
). (Я действительно удивлен, что они не используют встроенную функцию callable
здесь. Я предполагаю, что это нужно, чтобы не собирать вложенные классы.)
функция "main" выполняет поиск всех классов, которые наследуют unittest.TestCase в импортированных модулях. и текущий путь, затем пытается запустить каждый метод, начинающийся с 'test'
из документ python:
import random
import unittest
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
self.seq = range(10)
def test_shuffle(self):
# make sure the shuffled sequence does not lose any elements
random.shuffle(self.seq)
self.seq.sort()
self.assertEqual(self.seq, range(10))
# should raise an exception for an immutable sequence
self.assertRaises(TypeError, random.shuffle, (1,2,3))
def test_choice(self):
element = random.choice(self.seq)
self.assertTrue(element in self.seq)
def test_sample(self):
with self.assertRaises(ValueError):
random.sample(self.seq, 20)
for element in random.sample(self.seq, 5):
self.assertTrue(element in self.seq)
if __name__ == '__main__':
unittest.main()
Тестовый файл создается путем подкласса unittest.TestCase. Три индивидуальные тесты определяются методами, имена которых начинаются с письмо тест. Это соглашение об именах информирует тестировщика о какие методы представляют тесты.
Я написал код, который пытается вести себя аналогично unittest.main() ниже. Итак, я повторяю модули, а для модулей, которые не начинаются с имени "unittest", я проверяю его членов. Тогда, если эти члены являются классом и являются подклассом unittest.TestCase, я анализирую членов этого класса. Тогда, если эти члены класса являются функцией или методом, который начинается с "теста", я добавляю его в список тестов. Объект класса __dict__
используется для интроспекции методов/функций, поскольку с помощью функции inspect.getmembers могут отображаться слишком много. Наконец, список тестов преобразуется в кортеж и завершается как набор. Затем пакет запускается с использованием бегуна на уровне детализации 2. Обратите внимание, что, конечно, удаление регулярного выражения, которое проверяет "тест" в начале имени функции/метода, можно удалить, включив bar_test() в список тестов если вы не хотите этого ограничения.
#!/usr/bin/env python
import unittest
import inspect
import sys
import re
class Foo(unittest.TestCase):
@staticmethod
def test_baz():
pass
@classmethod
def test_mu(cls):
pass
def test_foo(self):
self.assertEqual('foo', 'foo')
def bar_test(self):
self.assertEqual('bar', 'bar')
class Bar:
pass
if __name__ == '__main__':
runner = unittest.TextTestRunner(verbosity=2)
tests = []
is_member_valid_test_class = lambda member: inspect.isclass(member) and \
issubclass(member, unittest.TestCase)
for module_name, module_obj in sys.modules.items():
if not re.match(r'unittest', module_name):
for cls_name, cls in inspect.getmembers(
module_obj, is_member_valid_test_class):
for methname, methobj in cls.__dict__.items():
if inspect.isroutine(methobj) and re.match(r'test', methname):
tests.append(cls(methname))
suite = unittest.TestSuite(tests=tuple(tests))
runner.run(suite)
Результирующий результат:
test_foo (__main__.Foo) ... ok
test_baz (__main__.Foo) ... ok
test_mu (__main__.Foo) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK