Como faço para obter o nariz para descobrir casos de teste geradas dinamicamente?
-
19-08-2019 - |
Pergunta
Este é um seguimento a um pergunta anterior meu.
Na questão anterior, os métodos foram explorados para implementar o que era essencialmente o mesmo teste ao longo de toda uma família de funções, garantindo testes não parar na primeira função que falhou.
Meu solução preferida utilizado um metaclass para inserção dinamicamente os testes em um unittest.TestCase. Infelizmente, nariz não pegar isso porque nariz verifica estaticamente para casos de teste.
Como faço para obter o nariz para descobrir e executar tal um TestCase? Por favor, consulte aqui para um exemplo do TestCase em questão.
Solução
Nose tem um recurso "gerador de teste" para coisas como esta. Você escreve uma função de gerador que produz cada função "caso de teste" você quer que ele seja executado, juntamente com seus argumentos. Seguindo seu exemplo anterior, isso poderia verificar cada uma das funções em um teste em separado:
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)
Outras dicas
Nose não verifica para testes estaticamente, assim você pode magia uso metaclass para fazer testes que encontra nariz.
A parte mais difícil é que as técnicas metaclasse padrão não definir o atributo func_name corretamente, que é o que parece nariz para quando verificar se os métodos em sua classe são testes.
Aqui está uma metaclass simples. Ele olha através do dict func e adiciona um novo método para cada método que encontra, afirmando que o método que encontrei tem uma docstring. Estes novos métodos sintéticos são dadas a "test_%d" %i
nomes.
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)
Agora, vamos criar uma nova classe que usa esse metaclass
class Foo(object):
__metaclass__ = Meta
def greeter(self):
"sdf"
print 'Hello World'
def greeter_no_docstring(self):
pass
No momento da execução, Foo
vai realmente ser chamado Test_Foo
e terá greeter
, greeter_no_docstring
, test_1
e test_2
como seus métodos. Quando eu executo nosetests
neste arquivo, aqui está a saída:
$ 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)
Esta metaclass não é realmente útil como é, mas se você em vez usar o Meta
não como uma metaclasse adequada, mas como mais de um metaclass funcional (ie leva uma classe como um argumento e retorna uma nova classe, que é renomeado de modo que o nariz vai encontrá-lo), então é útil. Eu tenho usado essa abordagem para automaticamente teste que as docstrings aderir ao padrão Numpy como parte de um conjunto de testes nariz.
Além disso, eu tinha um monte de problemas para obter trabalho fechamento adequado com new.function, razão pela qual este usa código m(self, func)
onde func
é feito para ser um argumento padrão. Seria mais natural para usar um fecho sobre value
, mas que não parecem funcionar.
Você poderia tentar gerar as classes testcase com 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 .."}
)
Isto deve ser criado quando tenta nariz para importar o test_module assim que deve funcionar.
A vantagem dessa abordagem é que você pode criar muitas combinações de testes dinamicamente.