2014-09-15 8 views
1

я хочу добавить несколько тестов, чтобы мои тестовые примеры, вместо того, чтобы делать следующее:Динамического добавления тестов для моих тестовых случаев

class TestRestart(unittest.TestCase): 

    ... 

    def test_modes(self): 
     for mode in modes: 
     machine.set_mode(mode) 
     self.assertTrue(machine.restart()) 

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

import unittest 

def build_name_1(a): 
    return "test " + str(a[0]) + "_" + str(a[1]) 

def build_name_2(a): 
    return "test " + str(a[0]) + "_" + str(a[1]) + "_" + str(a[2]) 

def add_tests(cls, args, name_builder): 
    for a in args: 
     def tb(a): 
     return lambda self: self.test_body(*a) 
     setattr(cls, name_builder(a), tb(a)) 

class TestEqual(unittest.TestCase): 
    def test_body(self, i, j): 
     self.assertNotEquals(0, i-j) 

args = ((0,0),(1,1)) 
add_tests(TestEqual, args, build_name_1) 

class TestBetween(unittest.TestCase): 
    def test_body(self, i, j, k): 
     self.assertTrue(i<j<k) 

args = ((2,1,2),(2,2,3)) 
add_tests(TestBetween, args, build_name_2) 

if __name__ == '__main__': 
    unittest.main() 

Можно ли переместить add_tests вызов внутри классов TestEqual и TestBetween вместо того, чтобы их вне классов, как я выше? Разве это не было бы лучше?

Какие улучшения я могу сделать для add_tests? Как я могу заставить его обрабатывать именованные аргументы?

+0

Вам необходимо более подробно ознакомиться с вашими данными. Похоже, вы хотите проверить что-то в функции, а затем, если оно соответствует критериям, выполните больше тестов или, возможно, продолжите работу? –

+0

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

ответ

1

Вы можете сделать свою add_tests функцию класса декоратором, так что он вызывается прямо во время создания класса, а не по отдельности. Если вы не возражаете против глубоко вложенных функций, вы можете передать и различные аргументы.

Вот версия, что это эквивалентно тому, что вы делаете сейчас, только в форме декоратор-завод:

def add_tests(args, name_builder): 
    def decorator(cls): 
     for a in args: 
      def tb(a): 
       return lambda self: self.test_body(*a) 
      setattr(cls, name_builder(a), tb(a)) 
     return cls 

Вы назвали бы его:

@add_tests(args, build_name1) 
class TestEquals(unittest.TestCase): 
    ... 

Вы могли бы также обрабатывать ключевого слова путем принятия последовательности kwargs dicts в дополнение к последовательности args кортежей. Затем вы просто передаете их на тестовую функцию, которая может быть передана как вызываемая, или если вы имеете дело с методом, по имени (поскольку класс декоратор вызывается до того, как класс привязан к его имени). Попробуйте это (но обратите внимание, что функция name_builder получает передается дополнительный аргумент, так что нужно будет обновляться, поэтому он может иметь дело с kwargs тоже!):

def add_tests(args_sequence, kwargs_sequence, name_builder, test_func=None): 
    def decorator(cls): 
     if test_func is None: 
      test_func = cls.test_body  # fall back on your current behavior 
     elif isinstance(test_func, str): 
      test_func = getattr(cls, test_func) 
     for args, kwargs in zip(args_sequence, kwargs_sequence): 
      def tb(a, kw): 
       return lambda self: test_func(self, *a, **kw) 
      setattr(cls, name_builder(args, kwargs), tb(args, kwargs)) 
     return cls 

Вы могли бы сделать свои окончательные примеры с:

@add_tests([(mode.SILENT,), (mode.NOISY,)], [{}, {}], build_name_mode, "test_mode_flow") 
@add_tests([(1,),()], [{"speed":33}, {"speed":22, "gear":4}], # can mix args and kwargs 
      build_gear_mode, "test_gear_flow") 
class TestRestart(unittest.TestCase): 
    ... 
1
import unittest 

def outer(i, j): 
    """Saves i and j in the inner scope, will be passed an 
    instance of Foo on call to inner. 
    """ 
    def inner(inst): 
     inst.assertNotEqual(i, j) 

    return inner 

class Meta(type): 
    """Builds test functions before unittest does its discovery. 
    """ 
    def __init__(self, *args, **kwargs): 
     for i, j in self.tests: 
      setattr(self, self.namebuilder(i, j), outer(i, j)) 
     super().__init__(*args, **kwargs)   

class Foo(unittest.TestCase, metaclass=Meta): 
    def namebuilder(*args): 
     return "test_{}_{}".format(*args) 

    tests = [(i, j) for i in range(10) for j in range(i+1, 10)] 

class Bar(unittest.TestCase, metaclass=Meta): 
    def namebuilder(*args): 
     return "test_labeled_differently_{}_{}".format(*args) 

    tests= [(i, i) for i in range(10)] 

if __name__ == '__main__': 
    unittest.main() 

Это один из способов сделать это с использованием метакласса. unittest обнаруживает тестовые функции перед тем, как ваш экземпляр будет создан. Подумайте о классе как экземпляре-строителе и мета-классе в качестве класса-строителя. Мета-класс создаст все ваши тестовые функции, так что когда загрузчик unittest по умолчанию в loader.py https://hg.python.org/cpython/file/322ee2f2e922/Lib/unittest/loader.py вызовет функцию getTestCaseNames, он найдет динамически созданные тесты. Я включил функцию loader.py, чтобы вы могли сами убедиться, как она работает.

def getTestCaseNames(self, testCaseClass): 
    """Return a sorted sequence of method names found within testCaseClass 
    """ 
    def isTestMethod(attrname, testCaseClass=testCaseClass, 
        prefix=self.testMethodPrefix): 
     return attrname.startswith(prefix) and \ 
      callable(getattr(testCaseClass, attrname)) 
    testFnNames = list(filter(isTestMethod, dir(testCaseClass))) 
    if self.sortTestMethodsUsing: 
     testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing)) 
    return testFnNames 
+0

Если процесс обнаружения 'unittest' отличается от того, что я думаю, я не думаю, что это что-то изменит. Методы тестирования уже устанавливались во время импорта метода (поскольку вызовы 'add_tests' находятся на верхнем уровне), а не во время создания. – Blckknght

+0

@Blckknght Вопрос OP состоял в том, как выполнить то же самое, что они делали, но с add_tests, происходящим внутри классов. Я уверен, что есть другие способы сделать это, но таким образом избежать повторения. –

Смежные вопросы