Подтвердить что ты не робот

Как модуль Python unittest обнаруживает тестовые примеры?

Мне было интересно, когда мы запускаем unittest.main(), как Python знает, какие подклассы unittest.Testcase имеют?

Например, если я добавляю класс FromRomanBadInput(unittest.TestCase), как известно unittest для запуска этого?

4b9b3361

Ответ 1

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

Ответ 2

функция "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. Три индивидуальные тесты определяются методами, имена которых начинаются с письмо тест. Это соглашение об именах информирует тестировщика о какие методы представляют тесты.

Ответ 3

Я написал код, который пытается вести себя аналогично 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