Como faço para obter o nariz para descobrir casos de teste geradas dinamicamente?

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

  •  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.

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top