Come si generano unit test dinamici (con parametri) in Python?


234

Ho una specie di dati di test e voglio creare un test unitario per ogni articolo. La mia prima idea era di farlo in questo modo:

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

L'aspetto negativo di questo è che gestisce tutti i dati in un test. Vorrei generare un test per ogni articolo al volo. Eventuali suggerimenti?



2
Un buon link che potrebbe fornire una risposta: eli.thegreenplace.net/2014/04/02/…
gaborous

Risposte:


173

Questo si chiama "parametrizzazione".

Esistono diversi strumenti che supportano questo approccio. Per esempio:

Il codice risultante è simile al seguente:

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)

Che genererà i test:

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'

Per motivi storici, lascerò la risposta originale intorno al 2008):

Uso qualcosa del genere:

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

24
In realtà, bignose, questo codice genera un nome diverso per ogni test (in realtà non funzionerebbe diversamente). Nell'esempio dato, i test eseguiti verranno rispettivamente denominati "test_foo", "test_bar" e "test_lee". Quindi il vantaggio che menzioni (ed è grande) è preservato fintanto che generi nomi sensibili.
Toji,

1
Come afferma la risposta data da @codeape, nose la gestisce. Tuttavia, il naso non sembra gestire Unicode; quindi per me questa è una soluzione preferibile. +1
Keith Pinson,

5
Quindi, nota che la risposta più corretta è data nella domanda duplicata : stackoverflow.com/a/2799009/322020 - devi usare .__name__ =per abilitare i .exact_methodtest
Nakilon,

7
Perché il codice che modifica la classe appare nel if __name__ == '__main__'condizionale? Sicuramente dovrebbe andare al di fuori di questo per essere eseguito al momento dell'importazione (ricordando che i moduli Python vengono importati una sola volta anche se importati da diversi luoghi)
SpoonMeiser,

4
Non credo sia una buona soluzione. Il codice di unittest non dovrebbe dipendere dal modo in cui viene chiamato. Il TestCase dovrebbe essere utilizzabile nel naso o nel pitone o in un ambiente di test diverso.
Guettli,

146

Utilizzo di unittest (dal 3.4)

Da Python 3.4, il unittestpacchetto di libreria standard ha il subTestgestore di contesto.

Vedi la documentazione:

Esempio:

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)

È inoltre possibile specificare un messaggio personalizzato e i valori dei parametri per subTest():

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

Usando il naso

Il framework di test del naso supporta questo .

Esempio (il codice seguente è l'intero contenuto del file contenente il test):

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

L'output del 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)

3
Questo è un modo molto pulito per generare dinamicamente casi di test.
gaborous

Ma attenzione, 'setup ()' non saprà quali variabili vengono utilizzate come argomenti per produrre. In realtà setup () non saprà quale test è in esecuzione o come impostato in test_generator (). Questo complica il controllo della sanità all'interno di setup (), ed è uno dei motivi per cui alcune persone preferiscono py.test.
Scott Prive,

1
Eseguito l'upgrade per la sezione di aggiornamento. Esattamente quello di cui avevo bisogno. :)
Saurabh Shrivastava,

1
C'è un modo per eseguire la versione unittest con pytest, in modo che esegua tutti i casi e non si fermi sul primo parametro fallito?
kakk11,

1
Come menzionato da @ kakk11, questa risposta (e sottotest in generale) non funziona con pytest. Questo è un problema noto. Esiste un plug-in sviluppato attivamente per farlo funzionare: github.com/pytest-dev/pytest-subtests
Jérémie,

76

Questo può essere risolto 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()

1
Questo ha funzionato GRANDE per me con il selenio. Come nota, nella classe TestSequence, puoi definire metodi "statici" come setUp (self), is_element_present (self, how, what), ... tearDown (self). Metterli DOPO che l' istruzione " metaclass = TestSequenceMeta" sembra funzionare.
Amore e pace - Joe Codeswell,

5
Questa soluzione è migliore di quella selezionata come IMHO accettato.
petroslamb,

2
@petroslamb Il __new__metodo nella metaclasse viene chiamato quando viene definita la classe stessa, non quando viene creata la prima istanza. Immagino che questo metodo di creazione dinamica di metodi di test sia più compatibile con l'introspezione utilizzata unittestper determinare quanti test sono in una classe (cioè potrebbe compilare l'elenco dei test prima di creare un'istanza di quella classe).
BillyBBone,

11
Nota: in Python 3, class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
cambialo

3
Potresti per favore usare dctinvece di dict? L'uso delle parole chiave come nomi di variabili è confuso e soggetto a errori.
npfoss,

49

A partire da Python 3.4 sono stati introdotti dei sottotest unittest per questo scopo. Vedere la documentazione per i dettagli. TestCase.subTest è un gestore di contesto che consente di isolare le asserzioni in un test in modo che un errore venga segnalato con le informazioni sui parametri ma non interrompe l'esecuzione del test. Ecco l'esempio dalla documentazione:

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)

L'output di un test sarebbe:

======================================================================
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

Anche questo fa parte di unittest2 , quindi è disponibile per le versioni precedenti di Python.


1
La migliore soluzione se usi Python 3.4 e versioni successive.
Max Malysh,

4
Usando unittest2, questo è disponibile anche per Python 2.7.
Bernhard,

11
Una delle principali differenze tra questo approccio e l'esecuzione di test separati è che lo stato del test non viene ripristinato ogni volta. (Cioè, setUp()e tearDown()non vengono eseguiti tra i sotto-test.)
Kevin Christopher Henry,

1
@KevinChristopherHenry Sì, ma self.setUp()in teoria può essere chiamato manualmente dall'interno del sottotest. Per quanto riguarda tearDown, averlo chiamato automaticamente alla fine potrebbe essere sufficiente.
Acumenus

Penso che questo potrebbe essere potente se usato in combinazione con l'approccio della metaclasse sopra.
Nathan Chappell il

36

load_tests è un meccanismo poco conosciuto introdotto in 2.7 per creare dinamicamente un TestSuite. Con esso, puoi facilmente creare test parametrizzati.

Per esempio:

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

Tale codice eseguirà tutti i TestCase in TestSuite restituiti da load_tests. Nessun altro test viene eseguito automaticamente dal meccanismo di rilevamento.

In alternativa, puoi anche utilizzare l'ereditarietà come mostrato in questo ticket: http://bugs.python.org/msg151444


1
Il codice sopra fallisce: TypeError: __init __ () accetta al massimo 2 argomenti (4 dati)
massimo

2
Aggiunti valori predefiniti null ai parametri extra del costruttore.
Javier,

Preferisco il codice nose-parameterize nella risposta di @ mojo , ma per i miei clienti è troppo utile per evitare una dipendenza aggiuntiva, quindi userò questo per loro.
saggio

1
Questa soluzione è stata la mia preferita in questa pagina. Entrambi Nose , suggerito nella risposta attuale, e il suo fork Nose2 sono solo di manutenzione, e quest'ultimo suggerisce invece agli utenti di provare Pytest . Che casino - mi atterrò ad un approccio nativo come questo!
Sean,

1
bonus: possibilità di ridefinire il metodo shortDescription per l'output passato in params
fun_vit

33

Può essere fatto usando pytest . Basta scrivere il file test_me.pycon il contenuto:

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

Ed esegui il test con comando py.test --tb=short test_me.py. Quindi l'output sarà simile 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 ====================

È semplice !. Anche pytest ha più caratteristiche come fixtures, mark, assert, ecc ...


1
Stavo cercando un esempio semplice e diretto di come parametrizzare i casi di test con py.test. Grazie mille!
timbro

@timgeb Sono felice di aiutarti. Controlla il tag py.test , per altri esempi. Inoltre suggerisco di usare hamcrest per aggiungere un po 'di zucchero nei tuoi asserzioni con mutcher leggibili dall'uomo, che possono essere modificati, combinati o creati a modo tuo. Inoltre abbiamo allure-python , una bella generazione di report perpy.test
Sergey Voronezhskiy

Grazie. Ho appena iniziato a passare da unittestpy.test. Avevo TestCaseclassi di base che erano in grado di creare dinamicamente bambini con argomenti diversi che avrebbero archiviato come variabili di classe ... il che era un po 'ingombrante.
timbro

1
@timgeb Sì, hai ragione. La caratteristica più killer di py.testè yield_fixtures . Quale può fare l' installazione , restituire alcuni dati utili in prova e dopo che la fine del test fa smontare . Le partite possono anche essere parametirizzate .
Sergey Voronezhskiy,

12

Usa la libreria ddt . Aggiunge semplici decoratori per i metodi di prova:

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

Questa libreria può essere installata con pip. Non richiede nosee funziona perfettamente con il unittestmodulo libreria standard .


6

Potresti trarre vantaggio dal provare la libreria TestScenarios .

testscenarios fornisce un'iniezione di dipendenza pulita per i test di stile unittest di Python. Questo può essere utilizzato per test di interfaccia (test di molte implementazioni tramite una singola suite di test) o per l'iniezione di dipendenza classica (fornire test con dipendenze esternamente al codice di test stesso, consentendo test facili in diverse situazioni).



4

Puoi usare il plugin nose-ittr ( pip install nose-ittr).

È molto facile da integrare con i test esistenti, sono necessarie minime modifiche (se presenti). Supporta anche il plug-in multiprocessore del naso .

Non che puoi anche avere una setupfunzione personalizzata per test.

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

È anche possibile passare nosetestparametri come con il loro plug-in integrato attrib, in questo modo è possibile eseguire solo un test specifico con parametro specifico:

nosetest -a number=2

Mi piace questo approccio, in particolare il livello per metodo supportato.
Matt

3

Uso metaclassi e decoratori per generare test. Puoi controllare la mia implementazione python_wrap_cases . Questa libreria non richiede alcun framework di test.

Il tuo esempio:

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)

Uscita 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

Inoltre è possibile utilizzare generatori . Ad esempio questo codice genera tutte le possibili combinazioni di test con argomenti 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)

Uscita 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

2

Mi sono imbattuto in ParamUnittest l'altro giorno mentre guardavo il codice sorgente per radon ( esempio di utilizzo sul repository github ). Dovrebbe funzionare con altri framework che estendono TestCase (come Nose).

Ecco un esempio:

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)

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)

RISULTATO:

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

1
Piccolo problema con la tua def add_test_methodsfunzione. Dovrei essere def _add_test_methods io
Raychaser,

@Raychaser ... Hai ragione..ho risolto questo problema ma non l'ho aggiornato qui .... Grazie per averlo scoperto.
Arindam Roychowdhury,

1

Usa solo i metaclassi, come mostrato qui;

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

Produzione:

test_sample (ExampleTestCase) ... OK

1

È possibile utilizzare TestSuitee TestCaseclassi personalizzate .

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)

Mentre TestSuite funziona, gli argomenti non vengono passati alla __init__funzione.
jadelord,

1

Ho scoperto che questo funziona bene per i miei scopi, soprattutto se ho bisogno di generare test che differiscono leggermente i processi su una raccolta di dati.

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

La TestGeneratorclasse può essere utilizzata per generare diversi set di casi di test come TestCluster.

TestClusterpuò essere pensato come un'implementazione TestGeneratordell'interfaccia.


1

Questa soluzione funziona con unitteste noseper 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()

Grazie @ guillaume-jacquenot per la versione aggiornata <3!
scopa il

0

Avevo avuto problemi con uno stile molto particolare di test con parametri. Tutti i nostri test al selenio possono essere eseguiti localmente, ma dovrebbero anche essere in grado di essere eseguiti in remoto su diverse piattaforme su SauceLabs. Fondamentalmente, volevo prendere una grande quantità di casi di test già scritti e parametrizzarli con il minor numero possibile di modifiche al codice. Inoltre, dovevo essere in grado di passare i parametri nel metodo setUp, qualcosa che non ho visto soluzioni per altrove.

Ecco cosa mi è venuto in mente:

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

Con ciò, tutto quello che dovevo fare era aggiungere un semplice decoratore @sauce_labs () a ogni vecchio TestCase normale, e ora quando li esegui vengono raggruppati e riscritti, in modo che tutti i metodi di test vengano parametrizzati e rinominati. LoginTests.test_login (self) viene eseguito come 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 ognuno ha il parametro self.plat / decidere piattaforma su cui eseguire, anche in LoginTests.setUp, che è cruciale per il mio compito poiché è qui che viene inizializzata la connessione a SauceLabs.

Ad ogni modo, spero che questo possa essere d'aiuto a qualcuno che cerca di fare una parametrizzazione "globale" simile dei suoi test!


0

Le risposte basate su metaclasse funzionano ancora in Python3, ma invece __metaclass__dell'attributo è necessario utilizzare il metaclassparametro, come in:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

0

La meta-programmazione è divertente, ma può iniziare. La maggior parte delle soluzioni qui rende difficile:

  • avviare selettivamente un test
  • tornare al codice dato il nome del test

Quindi, il mio primo suggerimento è di seguire il percorso semplice / esplicito (funziona con qualsiasi test runner):

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

Dal momento che non dovremmo ripeterci, il mio secondo suggerimento si basa sulla risposta di @ Javier: abbracciare i test basati sulle proprietà. Biblioteca di ipotesi:

  • è "più incessantemente subdolo nella generazione di casi di test di noi semplici umani"
  • fornirà semplici esempi di conteggio
  • funziona con qualsiasi test runner
  • ha molte altre funzionalità interessanti (statistiche, output di test aggiuntivo, ...)

    class TestSequence (unittest.TestCase):

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

Per testare i tuoi esempi specifici, basta aggiungere:

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

Per eseguire solo un esempio particolare, è possibile commentare gli altri esempi (a condizione che l'esempio verrà eseguito per primo). Potresti voler usare @given(st.nothing()). Un'altra opzione è quella di sostituire l'intero blocco con:

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

Ok, non hai nomi di test distinti. Ma forse hai solo bisogno di:

  • un nome descrittivo della proprietà sottoposta a test.
  • quale input porta al fallimento (esempio di falsificazione).

Esempio più divertente


0

Super tardi alla festa, ma ho avuto problemi a far funzionare questi setUpClass.

Ecco una versione della risposta di @ Javier che dà setUpClassaccesso ad attributi allocati 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

Uscite

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

OK

0

Giusto per lanciare un'altra soluzione nel mix;)

Questo è effettivamente lo stesso parameterizeddi cui sopra, ma specifico per 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

Esempio di utilizzo:

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

-1

Oltre a usare setattr, possiamo usare load_tests da python 3.2. Fare riferimento al post del 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()

-1

Di seguito è la mia soluzione. Lo trovo utile quando: 1. Dovrebbe funzionare per unittest.Testcase e unittest discover 2. Avere una serie di test da eseguire per diverse impostazioni dei parametri. 3. Molto semplice nessuna dipendenza da altri pacchetti importati 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

Questo non risponde alla domanda, che riguarda la generazione di test al volo.
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)

sembra che tu abbia perso la formattazione lì. è davvero difficile da leggere così com'è
Arturo il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.