Как получить нос для обнаружения динамически генерируемых тестовых случаев?

StackOverflow https://stackoverflow.com/questions/347574

  •  19-08-2019
  •  | 
  •  

Вопрос

Это продолжение мой предыдущий вопрос .

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

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

Как получить нос, чтобы обнаружить и запустить такой TestCase? Пожалуйста, обратитесь здесь для примера рассматриваемого TestCase.

Это было полезно?

Решение

Nose имеет " тестовый генератор " особенность для таких вещей, как это. Вы пишете функцию генератора, которая возвращает каждый & Quot; test case & Quot; Функция, которую вы хотите, чтобы он работал, наряду с его аргументами. Следуя вашему предыдущему примеру, можно проверить каждую из функций в отдельном тесте:

import unittest
import numpy

from somewhere import the_functions

def test_matrix_functions():
    for function in the_functions:
        yield check_matrix_function, function

def check_matrix_function(function)
    matrix1 = numpy.ones((5,10))
    matrix2 = numpy.identity(5)
    output = function(matrix1, matrix2)
    assert matrix1.shape == output.shape, \
           "%s produces output of the wrong shape" % str(function)

Другие советы

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

Сложность состоит в том, что стандартные методы метакласса не устанавливают атрибут func_name правильно, что и ищет Нос при проверке, являются ли методы в вашем классе тестами.

Вот простой метакласс. Он просматривает func dict и добавляет новый метод для каждого найденного метода, утверждая, что найденный метод имеет строку документации. Этим новым синтетическим методам присвоены имена "test_%d" %i.

import new
from inspect import isfunction, getdoc

class Meta(type):
    def __new__(cls, name, bases, dct):

        newdct = dct.copy()
        for i, (k, v) in enumerate(filter(lambda e: isfunction(e[1]), dct.items())):
            def m(self, func):
                assert getdoc(func) is not None

            fname = 'test_%d' % i
            newdct[fname] = new.function(m.func_code, globals(), fname,
                (v,), m.func_closure)

        return super(Meta, cls).__new__(cls, 'Test_'+name, bases, newdct)

Теперь давайте создадим новый класс, который использует этот метакласс

class Foo(object):
    __metaclass__ = Meta

    def greeter(self):
        "sdf"
        print 'Hello World'

    def greeter_no_docstring(self):
        pass

Во время выполнения Foo на самом деле будет иметь имя Test_Foo и будет иметь методы greeter, greeter_no_docstring, test_1 и test_2 в качестве методов. Когда я запускаю nosetests для этого файла, вот вывод:

$ nosetests -v test.py
test.Test_Foo.test_0 ... FAIL
test.Test_Foo.test_1 ... ok

======================================================================
FAIL: test.Test_Foo.test_0
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Library/Frameworks/EPD64.framework/Versions/7.3/lib/python2.7/site-packages/nose/case.py", line 197, in runTest
    self.test(*self.arg)
  File "/Users/rmcgibbo/Desktop/test.py", line 10, in m
    assert getdoc(func) is not None
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.002s

FAILED (failures=1)

Этот метакласс на самом деле не очень полезен, но если вместо этого вы используете Meta не как правильный метакласс, а как функциональный метакласс (т.е. принимает класс в качестве аргумента и возвращает новый класс, один это переименовано, чтобы нос нашел его), тогда это полезно . Я использовал этот подход, чтобы автоматически проверить, что строки документации соответствуют стандарту Numpy как часть пакета носового тестирования.

Кроме того, у меня было много проблем с настройкой правильного закрытия при работе с new.function, поэтому этот код использует m(self, func), где func задано в качестве аргумента по умолчанию. Было бы более естественно использовать замыкание над value, но это не сработало.

Вы можете попытаться сгенерировать тестовые классы с помощью type ()

class UnderTest_MixIn(object):

    def f1(self, i):
        return i + 1

    def f2(self, i):
        return i + 2

SomeDynamicTestcase = type(
    "SomeDynamicTestcase", 
    (UnderTest_MixIn, unittest.TestCase), 
    {"even_more_dynamic":"attributes .."}
)

# or even:

name = 'SomeDynamicTestcase'
globals()[name] = type(
    name, 
    (UnderTest_MixIn, unittest.TestCase), 
    {"even_more_dynamic":"attributes .."}
)

Это должно быть создано, когда нос пытается импортировать ваш test_module, чтобы он работал.

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top