Pergunta

Tenho algum tipo de dados de teste e quero criar um teste unitário 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 trata todos os dados em um teste.Gostaria de gerar um teste para cada item imediatamente.Alguma sugestão?

Foi útil?

Solução

eu uso algo assim:

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()

O parameterized pacote pode ser usado para automatizar este processo:

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 irá 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'

Outras dicas

Usando unittest (desde 3.4)

Desde Python 3.4, a biblioteca padrão unittest pacote tem o subTest gerenciador 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

O nariz estrutura de teste apoia 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)

Isso pode ser resolvido elegantemente 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()

A partir do Python 3.4, subtestes foram introduzidos no unittest para essa finalidade.Ver 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 teste unitário2, portanto, está disponível para versões anteriores do Python.

testes_de_carga é 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 retornado por load_tests.Nenhum outro teste é executado automaticamente pelo mecanismo de descoberta.

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

Isso pode ser feito usando pytest.Basta escrever o arquivo test_me.py com 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 execute seu teste com o comando py.test --tb=short test_me.py.Então 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...

Use o ddt biblioteca.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 nose, e funciona excelentemente com a biblioteca padrão unittest módulo.

Você se beneficiaria em tentar o Cenários de teste biblioteca.

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

Há também a hipótese que adiciona testes fuzz ou baseados em propriedades: https://pypi.python.org/pypi/hypothesis

Este é um método de teste muito poderoso.

Você pode usar nariz-ittr plugar (pip install nose-ittr).

É muito fácil integrar com testes existentes, sendo necessárias alterações mínimas (se houver).Também suporta nariz plugin de multiprocessamento.

Não que você também possa personalizar setup função por teste.

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

Também é possível passar nosetest parâmetros como com seu plugin integrado attrib, desta forma você pode executar apenas um teste específico com parâmetro específico:

nosetest -a number=2

eu me deparei ParamUnittest outro dia, ao olhar o código-fonte para radônio (exemplo de uso no repositório do github).Deve funcionar com outros frameworks que estendem o TestCase (como o Nose).

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)

Eu 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

Você também pode usar geradores.Por exemplo, este código gera todas as combinações possíveis de testes com argumentos a__list e b__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

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

Saída:

test_sample (ExampleTestCase) ... OK
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)

Você pode usar TestSuite e personalizado TestCase Aulas.

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)

Eu estava tendo problemas com um estilo muito particular de testes parametrizados.Todos os nossos testes Selenium podem ser executados localmente, mas também devem poder 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 para o qual não vi nenhuma solução em outro lugar.

Aqui está o que eu descobri:

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 o que tive que fazer foi adicionar um decorador simples @sauce_labs() a cada TestCase antigo normal, e agora, ao executá-los, eles são agrupados 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 possui o parâmetro self.platform para decidir qual navegador/ plataforma para execução, mesmo em LoginTests.setUp, o que é crucial para minha tarefa, pois é onde a conexão com o SauceLabs é inicializada.

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

Esta solução funciona com unittest e nose:

#!/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()

As respostas baseadas em metaclasse ainda funcionam em Python3, mas em vez do __metaclass__ atributo é preciso usar o metaclass parâmetro, como em:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

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

  • lançar seletivamente um teste
  • aponte para o código dado o nome do teste

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

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 na geração de casos de teste do que nós, meros humanos"
  • fornecerá exemplos de contagem simples
  • funciona com qualquer executor de testes
  • tem muitos outros recursos interessantes (estatísticas, saída de teste adicional, ...)

    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 específico, você pode comentar os outros exemplos (desde que o exemplo seja executado primeiro).Você pode querer usar @given(st.nothing()).Outra opção é substituir todo o bloco por:

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

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

  • um nome descritivo da propriedade em teste.
  • qual entrada leva ao fracasso (exemplo de falsificação).

Exemplo mais engraçado

Muito atrasado para a festa, mas tive dificuldade em fazer isso funcionar para setUpClass.

Aqui está uma versão de Resposta de @Javier isso dá setUpClass acesso 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

Resultados

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

OK

Além de usar setattr, podemos usar load_tests desde 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()

A seguir está minha solução.Acho isso útil quando:1.Deve trabalhar para o unittest.testascase e o 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 importantes 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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top