Como você gera testes de unidade dinâmicos (parametrizados) em python?

234

Eu tenho algum tipo de dados de teste e quero criar um teste de unidade para cada item. Minha primeira ideia foi fazer assim:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequence(unittest.TestCase):
    def testsample(self):
        for name, a,b in l:
            print "test", name
            self.assertEqual(a,b)

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

A desvantagem disso é que ele lida com todos os dados em um teste. Gostaria de gerar um teste para cada item em tempo real. Alguma sugestão?

Peter Hoffmann
fonte
2
Um bom link que pode fornecer uma resposta: eli.thegreenplace.net/2014/04/02/…
gaborous

Respostas:

173

Isso é chamado de "parametrização".

Existem várias ferramentas que suportam essa abordagem. Por exemplo:

O código resultante é assim:

from parameterized import parameterized

class TestSequence(unittest.TestCase):
    @parameterized.expand([
        ["foo", "a", "a",],
        ["bar", "a", "b"],
        ["lee", "b", "b"],
    ])
    def test_sequence(self, name, a, b):
        self.assertEqual(a,b)

O que gerará os testes:

test_sequence_0_foo (__main__.TestSequence) ... ok
test_sequence_1_bar (__main__.TestSequence) ... FAIL
test_sequence_2_lee (__main__.TestSequence) ... ok

======================================================================
FAIL: test_sequence_1_bar (__main__.TestSequence)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/parameterized/parameterized.py", line 233, in <lambda>
    standalone_func = lambda *a: func(*(a + p.args), **p.kwargs)
  File "x.py", line 12, in test_sequence
    self.assertEqual(a,b)
AssertionError: 'a' != 'b'

Por razões históricas, deixarei a resposta original por volta de 2008):

Eu uso algo como isto:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequense(unittest.TestCase):
    pass

def test_generator(a, b):
    def test(self):
        self.assertEqual(a,b)
    return test

if __name__ == '__main__':
    for t in l:
        test_name = 'test_%s' % t[0]
        test = test_generator(t[1], t[2])
        setattr(TestSequense, test_name, test)
    unittest.main()
Dmitry Mukhin
fonte
24
Na verdade, bignose, esse código gera um nome diferente para cada teste (na verdade não funcionaria de outra maneira). No exemplo dado, os testes executados serão nomeados "test_foo", "test_bar" e "test_lee", respectivamente. Assim, o benefício que você menciona (e é grande) é preservado desde que você gere nomes sensatos.
Toji 23/02
1
Como afirma a resposta de @codeape, o nariz lida com isso. No entanto, o nariz não parece lidar com Unicode; portanto, para mim, esta é uma solução preferível. 1
Keith Pinson
5
Assim, nota, que a resposta mais adequada é dada no duplicado pergunta: stackoverflow.com/a/2799009/322020 - você tem uso para .__name__ =habilitar o .exact_methodteste
Nakilon
7
Por que o código que modifica a classe aparece no if __name__ == '__main__'condicional? Certamente ele deve ir fora desta para executar em tempo de importação (lembrando que módulos python, apenas são importados, uma vez, mesmo se importado de vários lugares diferentes)
SpoonMeiser
4
Não acho que seja uma boa solução. O código de um unittest não deve depender da maneira como é chamado. O TestCase deve ser utilizável no nariz ou pytest ou em um ambiente de teste diferente.
guettli
146

Usando unittest (desde 3.4)

Desde o Python 3.4, o unittestpacote de biblioteca padrão possui o subTestgerenciador de contexto.

Veja a documentação:

Exemplo:

from unittest import TestCase

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

class TestDemonstrateSubtest(TestCase):
    def test_works_as_expected(self):
        for p1, p2 in param_list:
            with self.subTest():
                self.assertEqual(p1, p2)

Você também pode especificar uma mensagem personalizada e valores de parâmetro para subTest():

with self.subTest(msg="Checking if p1 equals p2", p1=p1, p2=p2):

Usando nariz

A estrutura de teste do nariz suporta isso .

Exemplo (o código abaixo é todo o conteúdo do arquivo que contém o teste):

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

def test_generator():
    for params in param_list:
        yield check_em, params[0], params[1]

def check_em(a, b):
    assert a == b

A saída do comando nosetests:

> nosetests -v
testgen.test_generator('a', 'a') ... ok
testgen.test_generator('a', 'b') ... FAIL
testgen.test_generator('b', 'b') ... ok

======================================================================
FAIL: testgen.test_generator('a', 'b')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.5/site-packages/nose-0.10.1-py2.5.egg/nose/case.py", line 203, in runTest
    self.test(*self.arg)
  File "testgen.py", line 7, in check_em
    assert a == b
AssertionError

----------------------------------------------------------------------
Ran 3 tests in 0.006s

FAILED (failures=1)
codeape
fonte
3
Essa é uma maneira muito limpa de gerar dinamicamente casos de teste.
gaborous
Mas esteja ciente, 'setup ()' não saberá quais variáveis ​​estão sendo usadas como argumentos para gerar. Na verdade, o setup () não sabe qual teste está sendo executado ou os vars configurados dentro de test_generator (). Isso complica a verificação de integridade dentro de setup (), e é uma das razões pelas quais algumas pessoas preferem o py.test.
Scott Prive
1
Voto positivo para a seção de atualização. Exatamente o que eu precisava. :)
Saurabh Shrivastava
1
Existe uma maneira de executar a versão mais unida com pytest, para que ele execute todos os casos e não pare no primeiro parâmetro com falha?
precisa saber é o seguinte
1
Conforme mencionado por @ kakk11, esta resposta (e sub-teste em geral) não funciona com pytest. Este é um problema conhecido. Há um plug-in desenvolvido ativamente para fazer esse trabalho: github.com/pytest-dev/pytest-subtests
Jérémie
76

Isso pode ser resolvido com elegância usando Metaclasses:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequenceMeta(type):
    def __new__(mcs, name, bases, dict):

        def gen_test(a, b):
            def test(self):
                self.assertEqual(a, b)
            return test

        for tname, a, b in l:
            test_name = "test_%s" % tname
            dict[test_name] = gen_test(a,b)
        return type.__new__(mcs, name, bases, dict)

class TestSequence(unittest.TestCase):
    __metaclass__ = TestSequenceMeta

if __name__ == '__main__':
    unittest.main()
Cara
fonte
1
Isso funcionou muito bem para mim com o Selenium. Como observação, na classe TestSequence, você pode definir métodos "estáticos" como setUp (self), is_element_present (self, como, o que), ... tearDown (self). Colocá-los APÓS a instrução " metaclass = TestSequenceMeta" parece funcionar.
Amor e paz - Joe Codeswell
5
Esta solução é melhor que a selecionada como IMHO aceito.
precisa saber é
2
@petroslamb O __new__método na metaclasse é chamado quando a própria classe é definida, não quando a primeira instância é criada. Eu imagino que esse método de criação dinâmica de métodos de teste seja mais compatível com a introspecção usada unittestpara determinar quantos testes há em uma classe (ou seja, ele pode compilar a lista de testes antes de criar uma instância dessa classe).
BillyBBone 23/02
11
Nota: no python 3, altere para:class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
Mathieu_Du 30/12/16
3
Você poderia usar em dctvez de dict? Usar palavras-chave como nomes de variáveis ​​é confuso e propenso a erros.
Npfoss
49

A partir do Python 3.4, os subtestes foram introduzidos como unittest para esse fim. Veja a documentação para detalhes. TestCase.subTest é um gerenciador de contexto que permite isolar afirmações em um teste para que uma falha seja relatada com informações de parâmetro, mas não interrompa a execução do teste. Aqui está o exemplo da documentação:

class NumbersTest(unittest.TestCase):

def test_even(self):
    """
    Test that numbers between 0 and 5 are all even.
    """
    for i in range(0, 6):
        with self.subTest(i=i):
            self.assertEqual(i % 2, 0)

A saída de uma execução de teste seria:

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

Isso também faz parte do unittest2 , portanto está disponível para versões anteriores do Python.

Bernhard
fonte
1
A melhor solução se você usa python 3.4 e superior.
Max Malysh
4
Usando o unittest2, isso também está disponível para o Python 2.7.
Bernhard
11
Uma grande diferença entre essa abordagem e ter testes separados é que o estado do teste não é redefinido a cada vez. (Isto é, setUp()e tearDown()não são executados entre os sub-testes.)
Kevin Christopher Henry
1
@KevinChristopherHenry Sim, mas self.setUp()em teoria pode ser chamado manualmente a partir do subteste. Quanto a tearDown, tê-lo chamado automaticamente no final pode ser suficiente.
Acumenus
Eu acho que isso pode ser poderoso quando usado em conjunto com a abordagem de metaclasse acima.
Nathan Chappell
36

load_tests é um mecanismo pouco conhecido introduzido no 2.7 para criar dinamicamente um TestSuite. Com ele, você pode criar facilmente testes parametrizados.

Por exemplo:

import unittest

class GeneralTestCase(unittest.TestCase):
    def __init__(self, methodName, param1=None, param2=None):
        super(GeneralTestCase, self).__init__(methodName)

        self.param1 = param1
        self.param2 = param2

    def runTest(self):
        pass  # Test that depends on param 1 and 2.


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        test_cases.addTest(GeneralTestCase('runTest', p1, p2))
    return test_cases

Esse código executará todos os TestCases no TestSuite retornados por load_tests. Nenhum outro teste é executado automaticamente pelo mecanismo de descoberta.

Como alternativa, você também pode usar a herança como mostrado neste ticket: http://bugs.python.org/msg151444

Javier
fonte
1
O código acima falha: TypeError: __init __ () leva no máximo 2 argumentos (4 dados)
max
2
Padrões nulos adicionados aos parâmetros extras do construtor.
Javier
Eu prefiro o código de parametrização do nariz na resposta do @ mojo , mas para meus clientes é muito útil evitar uma dependência extra, portanto usarei isso para eles.
sábio
1
Esta solução foi a minha favorita nesta página. O Nose , sugerido na atual resposta superior, e o fork Nose2 são apenas para manutenção, e o último sugere que os usuários experimentem o pytest . Que bagunça - vou seguir uma abordagem nativa como esta!
Sean
1
bonus: capacidade de redefinir o método shortDescription para a saída passada nos parâmetros
fun_vit 14/02
33

Isso pode ser feito usando pytest . Basta escrever o arquivo test_me.pycom o conteúdo:

import pytest

@pytest.mark.parametrize('name, left, right', [['foo', 'a', 'a'],
                                               ['bar', 'a', 'b'],
                                               ['baz', 'b', 'b']])
def test_me(name, left, right):
    assert left == right, name

E executar o teste com o comando py.test --tb=short test_me.py. Em seguida, a saída será semelhante a:

=========================== test session starts ============================
platform darwin -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 3 items

test_me.py .F.

================================= FAILURES =================================
_____________________________ test_me[bar-a-b] _____________________________
test_me.py:8: in test_me
    assert left == right, name
E   AssertionError: bar
==================== 1 failed, 2 passed in 0.01 seconds ====================

É simples! Também pytest tem mais recursos, como fixtures, mark, assert, etc ...

Sergey Voronezhskiy
fonte
1
Eu estava procurando um exemplo simples e direto de como parametrizar casos de teste com py.test. Muito obrigado!
timgeb
@timgeb Estou feliz em ajudá-lo. Verifique a tag py.test , para mais exemplos. Também sugiro usar o hamcrest para adicionar um pouco de açúcar em suas afirmações com mutchers legíveis por humanos, que podem ser modificados, combinados ou criados à sua maneira. Além disso, temos o allure-python , uma geração de relatório interessante parapy.test
Sergey Voronezhskiy
Obrigado. Comecei a passar unittestpara py.test. Eu costumava ter TestCaseclasses base que eram capazes de criar dinamicamente crianças com argumentos diferentes que eles armazenavam como variáveis ​​de classe ... o que era um pouco pesado.
timgeb
1
@timgeb Sim, você está certo. O mais característica do assassino de py.testé yield_fixtures . O que pode fazer a configuração , retornar alguns dados úteis para o teste e, após o término do teste, desmontar . As luminárias também podem ser parametrizadas .
Sergey Voronezhskiy
12

Use a biblioteca ddt . Ele adiciona decoradores simples para os métodos de teste:

import unittest
from ddt import ddt, data
from mycode import larger_than_two

@ddt
class FooTestCase(unittest.TestCase):

    @data(3, 4, 12, 23)
    def test_larger_than_two(self, value):
        self.assertTrue(larger_than_two(value))

    @data(1, -3, 2, 0)
    def test_not_larger_than_two(self, value):
        self.assertFalse(larger_than_two(value))

Esta biblioteca pode ser instalada com pip. Não requer nosee funciona excelente com o unittestmódulo de biblioteca padrão .

Mykhaylo Kopytonenko
fonte
6

Você se beneficiaria de experimentar a biblioteca TestScenarios .

testscenarios fornece injeção de dependência limpa para testes de estilo unittest do python. Isso pode ser usado para testes de interface (testando muitas implementações por meio de um único conjunto de testes) ou para injeção de dependência clássica (forneça testes com dependências externamente ao próprio código de teste, permitindo testes fáceis em diferentes situações).

nariz grande
fonte
4

Você pode usar o plugin nose-ittr ( pip install nose-ittr).

É muito fácil integrar-se aos testes existentes, são necessárias alterações mínimas (se houver). Ele também suporta plug-in de multiprocessamento do nariz .

Não que você também possa ter uma setupfunção de customização por teste.

@ittr(number=[1, 2, 3, 4])   
def test_even(self):   
    assert_equal(self.number % 2, 0)

Também é possível passar nosetestparâmetros como com seu plug-in embutido attrib; dessa forma, você pode executar apenas um teste específico com um parâmetro específico:

nosetest -a number=2
Maroun
fonte
Eu gosto dessa abordagem, especialmente o nível por método que ela suporta.
Matt
3

Uso metaclasses e decoradores para gerar testes. Você pode verificar minha implementação python_wrap_cases . Esta biblioteca não requer nenhuma estrutura de teste.

Seu exemplo:

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case("foo", "a", "a")
    @wrap_case("bar", "a", "b")
    @wrap_case("lee", "b", "b")
    def testsample(self, name, a, b):
        print "test", name
        self.assertEqual(a, b)

Saída do console:

testsample_u'bar'_u'a'_u'b' (tests.example.test_stackoverflow.TestSequence) ... test bar
FAIL
testsample_u'foo'_u'a'_u'a' (tests.example.test_stackoverflow.TestSequence) ... test foo
ok
testsample_u'lee'_u'b'_u'b' (tests.example.test_stackoverflow.TestSequence) ... test lee
ok

Além disso, você pode usar geradores . Por exemplo, esse código gera todas as combinações possíveis de testes com argumentos a__listeb__list

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case(a__list=["a", "b"], b__list=["a", "b"])
    def testsample(self, a, b):
        self.assertEqual(a, b)

Saída do console:

testsample_a(u'a')_b(u'a') (tests.example.test_stackoverflow.TestSequence) ... ok
testsample_a(u'a')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'a') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... ok
Kirill Ermolov
fonte
2

Me deparei com o ParamUnittest outro dia, quando olhamos o código fonte para o radônio ( exemplo de uso no repositório do github ). Ele deve funcionar com outras estruturas que estendem o TestCase (como o Nariz).

Aqui está um exemplo:

import unittest
import paramunittest


@paramunittest.parametrized(
    ('1', '2'),
    #(4, 3),    <---- uncomment to have a failing test
    ('2', '3'),
    (('4', ), {'b': '5'}),
    ((), {'a': 5, 'b': 6}),
    {'a': 5, 'b': 6},
)
class TestBar(TestCase):
    def setParameters(self, a, b):
        self.a = a
        self.b = b

    def testLess(self):
        self.assertLess(self.a, self.b)
Matt
fonte
2
import unittest

def generator(test_class, a, b):
    def test(self):
        self.assertEqual(a, b)
    return test

def add_test_methods(test_class):
    #First element of list is variable "a", then variable "b", then name of test case that will be used as suffix.
    test_list = [[2,3, 'one'], [5,5, 'two'], [0,0, 'three']]
    for case in test_list:
        test = generator(test_class, case[0], case[1])
        setattr(test_class, "test_%s" % case[2], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print 'Setup'
        pass

    def tearDown(self):
        print 'TearDown'
        pass

_add_test_methods(TestAuto)  # It's better to start with underscore so it is not detected as a test itself

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

RESULTADO:

>>> 
Setup
FTearDown
Setup
TearDown
.Setup
TearDown
.
======================================================================
FAIL: test_one (__main__.TestAuto)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/inchowar/Desktop/PyTrash/test_auto_3.py", line 5, in test
    self.assertEqual(a, b)
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 3 tests in 0.019s

FAILED (failures=1)
Arindam Roychowdhury
fonte
1
Problema menor com sua def add_test_methodsfunção. Deve ser def _add_test_methods eu penso
Raychaser
@ Raychaser ... Você está correto .. Eu consertei isso, mas não o atualizei aqui .... Obrigado por capturar isso.
Arindam Roychowdhury
1

Basta usar metaclasses, como visto aqui;

class DocTestMeta(type):
    """
    Test functions are generated in metaclass due to the way some
    test loaders work. For example, setupClass() won't get called
    unless there are other existing test methods, and will also
    prevent unit test loader logic being called before the test
    methods have been defined.
    """
    def __init__(self, name, bases, attrs):
        super(DocTestMeta, self).__init__(name, bases, attrs)

    def __new__(cls, name, bases, attrs):
        def func(self):
            """Inner test method goes here"""
            self.assertTrue(1)

        func.__name__ = 'test_sample'
        attrs[func.__name__] = func
        return super(DocTestMeta, cls).__new__(cls, name, bases, attrs)

class ExampleTestCase(TestCase):
    """Our example test case, with no methods defined"""
    __metaclass__ = DocTestMeta

Resultado:

test_sample (ExampleTestCase) ... OK
sonolento
fonte
1

Você pode usar TestSuitee customizar TestCaseclasses.

import unittest

class CustomTest(unittest.TestCase):
    def __init__(self, name, a, b):
        super().__init__()
        self.name = name
        self.a = a
        self.b = b

    def runTest(self):
        print("test", self.name)
        self.assertEqual(self.a, self.b)

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(CustomTest("Foo", 1337, 1337))
    suite.addTest(CustomTest("Bar", 0xDEAD, 0xC0DE))
    unittest.TextTestRunner().run(suite)
Max Malysh
fonte
Enquanto o TestSuite funciona, os argumentos não são transmitidos para a __init__função.
jadelord
1

Eu descobri que isso funciona bem para meus propósitos, especialmente se eu precisar gerar testes que diferenciem ligeiramente os processos em uma coleta de dados.

import unittest

def rename(newName):
    def renamingFunc(func):
        func.__name__ == newName
        return func
    return renamingFunc

class TestGenerator(unittest.TestCase):

    TEST_DATA = {}

    @classmethod
    def generateTests(cls):
        for dataName, dataValue in TestGenerator.TEST_DATA:
            for func in cls.getTests(dataName, dataValue):
                setattr(cls, "test_{:s}_{:s}".format(func.__name__, dataName), func)

    @classmethod
    def getTests(cls):
        raise(NotImplementedError("This must be implemented"))

class TestCluster(TestGenerator):

    TEST_CASES = []

    @staticmethod
    def getTests(dataName, dataValue):

        def makeTest(case):

            @rename("{:s}".format(case["name"]))
            def test(self):
                # Do things with self, case, data
                pass

            return test

        return [makeTest(c) for c in TestCluster.TEST_CASES]

TestCluster.generateTests()

A TestGeneratorclasse pode ser usada para gerar diferentes conjuntos de casos de teste, como TestCluster.

TestClusterpode ser pensado como uma implementação da TestGeneratorinterface.

bcdan
fonte
1

Esta solução funciona com unitteste nosepara Python 2 e Python 3:

#!/usr/bin/env python
import unittest

def make_function(description, a, b):
    def ghost(self):
        self.assertEqual(a, b, description)
    print(description)
    ghost.__name__ = 'test_{0}'.format(description)
    return ghost


class TestsContainer(unittest.TestCase):
    pass

testsmap = {
    'foo': [1, 1],
    'bar': [1, 2],
    'baz': [5, 5]}

def generator():
    for name, params in testsmap.iteritems():
        test_func = make_function(name, params[0], params[1])
        setattr(TestsContainer, 'test_{0}'.format(name), test_func)

generator()

if __name__ == '__main__':
    unittest.main()
esfregão
fonte
Obrigado @ guillaume-jacquenot pela versão atualizada <3!
mop
0

Eu estava tendo problemas com um estilo muito particular de testes parametrizados. Todos os nossos testes do Selenium podem ser executados localmente, mas também devem ser executados remotamente em várias plataformas no SauceLabs. Basicamente, eu queria pegar uma grande quantidade de casos de teste já escritos e parametrizá-los com o menor número possível de alterações no código. Além disso, eu precisava ser capaz de passar os parâmetros para o método setUp, algo que não vi soluções para outro lugar.

Aqui está o que eu vim com:

import inspect
import types

test_platforms = [
    {'browserName': "internet explorer", 'platform': "Windows 7", 'version': "10.0"},
    {'browserName': "internet explorer", 'platform': "Windows 7", 'version': "11.0"},
    {'browserName': "firefox", 'platform': "Linux", 'version': "43.0"},
]


def sauce_labs():
    def wrapper(cls):
        return test_on_platforms(cls)
    return wrapper


def test_on_platforms(base_class):
    for name, function in inspect.getmembers(base_class, inspect.isfunction):
        if name.startswith('test_'):
            for platform in test_platforms:
                new_name = '_'.join(list([name, ''.join(platform['browserName'].title().split()), platform['version']]))
                new_function = types.FunctionType(function.__code__, function.__globals__, new_name,
                                                  function.__defaults__, function.__closure__)
                setattr(new_function, 'platform', platform)
                setattr(base_class, new_name, new_function)
            delattr(base_class, name)

    return base_class

Com isso, tudo que eu precisava fazer era adicionar um decorador simples @sauce_labs () a cada TestCase antigo e agora, quando executá-los, eles são embrulhados e reescritos, para que todos os métodos de teste sejam parametrizados e renomeados. LoginTests.test_login (self) é executado como LoginTests.test_login_internet_explorer_10.0 (self), LoginTests.test_login_internet_explorer_11.0 (self) e LoginTests.test_login_firefox_43.0 (self) e cada um tem o parâmetro self.platform para decidir qual navegador / plataforma a ser executada, mesmo no LoginTests.setUp, que é crucial para a minha tarefa, pois é aí que a conexão com o SauceLabs é inicializada.

De qualquer forma, espero que isso ajude alguém que deseja fazer uma parametrização "global" semelhante de seus testes!

Danielle Weisz
fonte
0

As respostas baseadas em metaclasse ainda funcionam no Python3, mas, em vez do __metaclass__atributo, é necessário usar o metaclassparâmetro, como em:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass
Patrick Ohly
fonte
0

A metaprogramação é divertida, mas pode continuar. A maioria das soluções aqui dificulta:

  • iniciar seletivamente um teste
  • aponte de volta para o código com o nome do teste

Portanto, minha primeira sugestão é seguir o caminho simples / explícito (funciona com qualquer executor de teste):

import unittest

class TestSequence(unittest.TestCase):

    def _test_complex_property(self, a, b):
        self.assertEqual(a,b)

    def test_foo(self):
        self._test_complex_property("a", "a")
    def test_bar(self):
        self._test_complex_property("a", "b")
    def test_lee(self):
        self._test_complex_property("b", "b")

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

Como não devemos nos repetir, minha segunda sugestão baseia-se na resposta de @ Javier: adotar testes baseados em propriedades. Biblioteca de hipóteses:

  • é "mais implacavelmente desonesto quanto à geração de casos de teste do que nós, seres humanos"
  • fornecerá exemplos simples de contagem
  • trabalha com qualquer corredor de teste
  • possui muitos recursos mais interessantes (estatísticas, resultados adicionais de testes, ...)

    classe TestSequence (unittest.TestCase):

    @given(st.text(), st.text())
    def test_complex_property(self, a, b):
        self.assertEqual(a,b)

Para testar seus exemplos específicos, basta adicionar:

    @example("a", "a")
    @example("a", "b")
    @example("b", "b")

Para executar apenas um exemplo em particular, você pode comentar os outros exemplos (o exemplo fornecido será executado primeiro). Você pode querer usar @given(st.nothing()). Outra opção é substituir o bloco inteiro por:

    @given(st.just("a"), st.just("b"))

Ok, você não tem nomes de teste distintos. Mas talvez você só precise:

  • um nome descritivo da propriedade em teste.
  • qual entrada leva à falha (exemplo falsificado).

Exemplo mais engraçado

YvesgereY
fonte
0

Tarde demais para a festa, mas tive problemas para fazer isso funcionar setUpClass.

Aqui está uma versão da resposta de @ Javier que dá setUpClassacesso a atributos alocados dinamicamente.

import unittest


class GeneralTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print ''
        print cls.p1
        print cls.p2

    def runTest1(self):
        self.assertTrue((self.p2 - self.p1) == 1)

    def runTest2(self):
        self.assertFalse((self.p2 - self.p1) == 2)


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        clsname = 'TestCase_{}_{}'.format(p1, p2)
        dct = {
            'p1': p1,
            'p2': p2,
        }
        cls = type(clsname, (GeneralTestCase,), dct)
        test_cases.addTest(cls('runTest1'))
        test_cases.addTest(cls('runTest2'))
    return test_cases

Saídas

1
2
..
3
4
..
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK
hhquark
fonte
0

Apenas para jogar outra solução na mistura;)

É efetivamente o mesmo que parameterizedo mencionado acima, mas específico para unittest:

def sub_test(param_list):
    """Decorates a test case to run it as a set of subtests."""

    def decorator(f):

        @functools.wraps(f)
        def wrapped(self):
            for param in param_list:
                with self.subTest(**param):
                    f(self, **param)

        return wrapped

    return decorator

Exemplo de uso:

class TestStuff(unittest.TestCase):
    @sub_test([
        dict(arg1='a', arg2='b'),
        dict(arg1='x', arg2='y'),
    ])
    def test_stuff(self, a, b):
        ...
Eric Cousineau
fonte
-1

Além de usar o setattr, podemos usar load_tests desde o python 3.2. Consulte a postagem do blog blog.livreuro.com/en/coding/python/how-to-generate-discoverable-unit-tests-in-python-dynamically/

class Test(unittest.TestCase):
    pass

def _test(self, file_name):
    open(file_name, 'r') as f:
        self.assertEqual('test result',f.read())

def _generate_test(file_name):
    def test(self):
        _test(self, file_name)
    return test

def _generate_tests():
    for file in files:
        file_name = os.path.splitext(os.path.basename(file))[0]
        setattr(Test, 'test_%s' % file_name, _generate_test(file))

test_cases = (Test,)

def load_tests(loader, tests, pattern):
    _generate_tests()
    suite = TestSuite()
    for test_class in test_cases:
        tests = loader.loadTestsFromTestCase(test_class)
        suite.addTests(tests)
    return suite

if __name__ == '__main__':
    _generate_tests()
    unittest.main()
pptime
fonte
-1

A seguir está a minha solução. Acho isso útil quando: 1. Deve funcionar para unittest.Testcase e unittest discover 2. Tenha um conjunto de testes a serem executados para diferentes configurações de parâmetros. 3. Muito simples, sem dependência de outros pacotes importam unittest

    class BaseClass(unittest.TestCase):
        def setUp(self):
            self.param = 2
            self.base = 2

        def test_me(self):
            self.assertGreaterEqual(5, self.param+self.base)

        def test_me_too(self):
            self.assertLessEqual(3, self.param+self.base)



     class Child_One(BaseClass):
        def setUp(self):
            BaseClass.setUp(self)
            self.param = 4


     class Child_Two(BaseClass):
        def setUp(self):
            BaseClass.setUp(self)
            self.param = 1
S.Arora
fonte
Isso não responde à pergunta, que consiste em gerar testes em tempo real.
lenz
-1
import unittest

def generator(test_class, a, b,c,d,name):
    def test(self):
        print('Testexecution=',name)
        print('a=',a)
        print('b=',b)
        print('c=',c)
        print('d=',d)

    return test

def add_test_methods(test_class):
    test_list = [[3,3,5,6, 'one'], [5,5,8,9, 'two'], [0,0,5,6, 'three'],[0,0,2,3,'Four']]
    for case in test_list:
        print('case=',case[0], case[1],case[2],case[3],case[4])
        test = generator(test_class, case[0], case[1],case[2],case[3],case[4])
        setattr(test_class, "test_%s" % case[4], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print ('Setup')
        pass

    def tearDown(self):
        print ('TearDown')
        pass

add_test_methods(TestAuto)

if __name__ == '__main__':
    unittest.main(verbosity=1)
thangaraj1980
fonte
parece que você perdeu a formatação lá. é realmente difícil de ler, tal como está
Arturo