unittest - Struttura per Automatizzare Test

Scopo: Struttura per Automatizzare Test

Il modulo unittest è basato sulla struttura XUnit progettata da Kent Beck ed Erich Gamma. Lo stesso modello viene ripetuto in molti altri linguaggi, compresi C, Perl, Java e Smalltalk. La struttura implementata da unittest supporta impianti, serie di test e un esecutore di test che consente l'automatizzazione dei test.

Struttura Base di Test

I test, definiti da unittest, hanno due parti: codice per gestire le dipendenze (detti fixtures) e il test stesso. Test individuali sono creati subclassando TestCase e sovrascrivendo o aggiungendo i metodi appropriati. Nell'esempio seguente, SimplisticTest ha un singolo metodo test(), che fallirà se il contenuto di a è diverso dal contenuto di b.

# unittest_simple.py

import unittest


class SimplisticTest(unittest.TestCase):

    def test(self):
        a = 'a'
        b = 'a'
        self.assertEqual(a, b)

Eseguire Test

Il modo più semplice per eseguire test di unittest è utilizzare il rilevamento automatico tramite l'interfaccia da riga di comando.

$ python3 -m unittest unittest_simple.py

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Questo risultato abbreviato comprende il tempo impiegato nell'esecuzione del test, con un indicatore di stato per ciascun test (Il valore "." nella prima riga del risultato significa che un test è stato passato). Per un risultato più dettagliato si includa l'opzione -v.

$ python3 -m unittest -v unittest_simple.py

test (unittest_simple.SimplisticTest) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Esiti del Test

Ci sono tre possibili esiti, come da tabella di seguito:

ESITO DESCRIZIONE
ok Test superato
FAIL Test non superato, ed ha sollevato una eccezione AssertionError
ERROR Il test ha sollevato una qualunque eccezione diversa da AssertionError

Non ci sono modi espliciti per forzare il superamento di un test, quindi lo stato di un test dipende dalla presenza o meno di una eccezione.

# unittest_outcomes.py

import unittest


class OutcomesTest(unittest.TestCase):

    def testPass(self):
        return

    def testFail(self):
        self.assertFalse(True)

    def testError(self):
        raise RuntimeError('Errore nel test!')

Quando un test fallisce o genera un errore, il traceback viene incluso nel risultato.

$ python3 -m unittest unittest_outcomes.py

EF.
======================================================================
ERROR: testError (unittest_outcomes.OutcomesTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_outcomes.py", line 15, in testError
    raise RuntimeError('Errore nel test!')
RuntimeError: Errore nel test!

======================================================================
FAIL: testFail (unittest_outcomes.OutcomesTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_outcomes.py", line 12, in testFail
    self.assertFalse(True)
AssertionError: True is not false

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1, errors=1)

Nell'esempio precedente, testFail() fallisce e il traceback mostra la riga di codice che ha provocato il fallimento. Spetta alla persona che legge il risultato del test scoprire il significato del fallimento.

# unittest_failwithmessage.py
import unittest


class FailureMessageTest(unittest.TestCase):

    def testFail(self):
        self.assertFalse(True, 'Il messaggio di fallimento va qui')

Per facilitare la comprensione della natura di un fallimento di test, i metodi fail*() ed assert*() accettano tutti un argomento msg che può essere usato per produrre un messaggio di errore più dettagliato.

$ python3 -m unittest -v unittest_failwithmessage.py

testFail (unittest_failwithmessage.FailureMessageTest) ... FAIL

======================================================================
FAIL: testFail (unittest_failwithmessage.FailureMessageTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_failwithmessage.py", line 8, in testFail
    self.assertFalse(True, 'Il messaggio di fallimento va qui')
AssertionError: True is not false : Il messaggio di fallimento va qui

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

Asserire la Verità

La maggior parte dei test asseriscono la veridicità di una qualche condizione. Ci sono due modi diversi per scrivere test che verifichino una condizione di vero, a seconda della prospettiva dell'autore del test e del risultato desiderato dal codice in fase di test.

# unittest_truth.py

import unittest


class TruthTest(unittest.TestCase):

    def testAssertTrue(self):
        self.assertTrue(True)

    def testAssertFalse(self):
        self.assertFalse(False)

Se il codice produce un valore che può essere valutato come vero, il metodo assertTrue() dovrebbe essere utilizzato. Se il codice produce un valore falso, avrebbe più senso utilizzare il metodo assertFalse().

$ python3 -m unittest -v unittest_truth.py

testAssertFalse (unittest_truth.TruthTest) ... ok
testAssertTrue (unittest_truth.TruthTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Verificare Uguaglianza

Come caso particolare, unittest include metodi per verificare l'uguaglianza di due valori.

# unittest_equality.py

import unittest


class EqualityTest(unittest.TestCase):

    def testExpectEqual(self):
        self.assertEqual(1, 3 - 2)

    def testExpectEqualFails(self):
        self.assertEqual(2, 3 - 2)

    def testExpectNotEqual(self):
        self.assertNotEqual(2, 3 - 2)

    def testExpectNotEqualFails(self):
        self.assertNotEqual(1, 3 - 2)

Quando falliscono, questi metodi particolari producono messaggi di errore che includono il valore utilizzato per il confronto.

$ python3 -m unittest -v unittest_equality.py

testExpectEqual (unittest_equality.EqualityTest) ... ok
testExpectEqualFails (unittest_equality.EqualityTest) ... FAIL
testExpectNotEqual (unittest_equality.EqualityTest) ... ok
testExpectNotEqualFails (unittest_equality.EqualityTest) ... FAIL

======================================================================
FAIL: testExpectEqualFails (unittest_equality.EqualityTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_equality.py", line 12, in testExpectEqualFails
    self.assertEqual(2, 3 - 2)
AssertionError: 2 != 1

======================================================================
FAIL: testExpectNotEqualFails (unittest_equality.EqualityTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_equality.py", line 18, in testExpectNotEqualFails
    self.assertNotEqual(1, 3 - 2)
AssertionError: 1 == 1

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=2)

Quasi Uguali?

Oltre alla corrispondenza stretta, è anche possibile verificare una corrispondenza relativa per valori a virgola mobile usando assertAlmostEqual() e assertAlmostNotEqual().

# unittest_almostequal.py

import unittest


class AlmostEqualTest(unittest.TestCase):

    def testEqual(self):
        self.assertEqual(1.1, 3.3 - 2.2)

    def testAlmostEqual(self):
        self.assertAlmostEqual(1.1, 3.3 - 2.2, places=1)

    def testNotAlmostEqual(self):
        self.assertNotAlmostEqual(1.1, 3.3 - 2.0, places=1)

Gli argomenti sono i valori da confrontare e il numero di cifre decimali da usare per il confronto.

$ python3 -m unittest -v unittest_almostequal.py

testAlmostEqual (unittest_almostequal.AlmostEqualTest) ... ok
testEqual (unittest_almostequal.AlmostEqualTest) ... FAIL
testNotAlmostEqual (unittest_almostequal.AlmostEqualTest) ... ok

======================================================================
FAIL: testEqual (unittest_almostequal.AlmostEqualTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_almostequal.py", line 9, in testEqual
    self.assertEqual(1.1, 3.3 - 2.2)
AssertionError: 1.1 != 1.0999999999999996

----------------------------------------------------------------------
Ran 3 tests in 0.000s

FAILED (failures=1)

Contenitori

Oltre ai generici assertEqual() e assertNotEqual(), ci sono metodi speciali per confrontare oggetti contenitore come list, dict e set.

# unittest_equality_container.py

import textwrap
import unittest


class ContainerEqualityTest(unittest.TestCase):

    def testCount(self):
        self.assertCountEqual(
            [1, 2, 3, 2],
            [1, 3, 2, 3],
        )

    def testDict(self):
        self.assertDictEqual(
            {'a': 1, 'b': 2},
            {'a': 1, 'b': 3},
        )

    def testList(self):
        self.assertListEqual(
            [1, 2, 3],
            [1, 3, 2],
        )

    def testMultiLineString(self):
        self.assertMultiLineEqual(
            textwrap.dedent("""
            Questa stringa
            ha più di una
            riga
            """),
            textwrap.dedent("""
            Questa stringa ha
            più di due
            righe.
            """),
        )

    def testSequence(self):
        self.assertSequenceEqual(
            [1, 2, 3],
            [1, 3, 2],
        )

    def testSet(self):
        self.assertSetEqual(
            set([1, 2, 3]),
            set([1, 3, 2, 4]),
        )

    def testTuple(self):
        self.assertTupleEqual(
            (1, 'a'),
            (1, 'b'),
        )

Ciascun metodo rileva la non uguaglianza utilizzando un formato che ha senso rispetto al tipo in input, rendendo il fallimento del test più facile da comprendere e correggere.

$ python3 -m unittest unittest_equality_container.py

FFFFFFF
======================================================================
FAIL: testCount (unittest_equality_container.ContainerEqualityTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_equality_container.py", line 10, in testCount
    self.assertCountEqual(
AssertionError: Element counts were not equal:
First has 2, Second has 1:  2
First has 1, Second has 2:  3

======================================================================
FAIL: testDict (unittest_equality_container.ContainerEqualityTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_equality_container.py", line 16, in testDict
    self.assertDictEqual(
AssertionError: {'a': 1, 'b': 2} != {'a': 1, 'b': 3}
- {'a': 1, 'b': 2}
?               ^

+ {'a': 1, 'b': 3}
?               ^


======================================================================
FAIL: testList (unittest_equality_container.ContainerEqualityTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_equality_container.py", line 22, in testList
    self.assertListEqual(
AssertionError: Lists differ: [1, 2, 3] != [1, 3, 2]

First differing element 1:
2
3

- [1, 2, 3]
+ [1, 3, 2]

======================================================================
FAIL: testMultiLineString (unittest_equality_container.ContainerEqualityTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_equality_container.py", line 28, in testMultiLineString
    self.assertMultiLineEqual(
AssertionError: '\nQuesta stringa\nha più di una\nriga\n' != '\nQuesta stringa ha\npiù di due\nrighe.\n'
  
- Questa stringa
+ Questa stringa ha
?               +++
- ha più di una
- riga
+ più di due
+ righe.


======================================================================
FAIL: testSequence (unittest_equality_container.ContainerEqualityTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_equality_container.py", line 42, in testSequence
    self.assertSequenceEqual(
AssertionError: Sequences differ: [1, 2, 3] != [1, 3, 2]

First differing element 1:
2
3

- [1, 2, 3]
+ [1, 3, 2]

======================================================================
FAIL: testSet (unittest_equality_container.ContainerEqualityTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_equality_container.py", line 48, in testSet
    self.assertSetEqual(
AssertionError: Items in the second set but not the first:
4

======================================================================
FAIL: testTuple (unittest_equality_container.ContainerEqualityTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_equality_container.py", line 54, in testTuple
    self.assertTupleEqual(
AssertionError: Tuples differ: (1, 'a') != (1, 'b')

First differing element 1:
'a'
'b'

- (1, 'a')
?      ^

+ (1, 'b')
?      ^


----------------------------------------------------------------------
Ran 7 tests in 0.002s

FAILED (failures=7)

Si usi assertIn() per verificare l'appartenenza a un contenitore.

# unittest_in.py

import unittest


class ContainerMembershipTest(unittest.TestCase):

    def testDict(self):
        self.assertIn(4, {1: 'a', 2: 'b', 3: 'c'})

    def testList(self):
        self.assertIn(4, [1, 2, 3])

    def testSet(self):
        self.assertIn(4, set([1, 2, 3]))

Qualsiasi oggetto che supporta l'operatore in o le API dei contenitori possono essere usati con assertIn().

$ python3 -m unittest unittest_in.py

FFF
======================================================================
FAIL: testDict (unittest_in.ContainerMembershipTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_in.py", line 9, in testDict
    self.assertIn(4, {1: 'a', 2: 'b', 3: 'c'})
AssertionError: 4 not found in {1: 'a', 2: 'b', 3: 'c'}

======================================================================
FAIL: testList (unittest_in.ContainerMembershipTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_in.py", line 12, in testList
    self.assertIn(4, [1, 2, 3])
AssertionError: 4 not found in [1, 2, 3]

======================================================================
FAIL: testSet (unittest_in.ContainerMembershipTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_in.py", line 15, in testSet
    self.assertIn(4, set([1, 2, 3]))
AssertionError: 4 not found in {1, 2, 3}

----------------------------------------------------------------------
Ran 3 tests in 0.000s

FAILED (failures=3)

Verificare Eccezioni

Come accennato in precedenza, se un test solleva una eccezione diversa da AssertionError viene considerato come un errore. Questo è molto utile per scoprire errori mentre si sta modificando codice che ha una copertura di test esistente. Ci sono circostanze nelle quali, comunque, il test dovrebbe verificare che un certo codice produce una eccezione. Ad esempio, se un valore non valido viene dato a un attributo di un oggetto. In questi casi, assertRaises() rende il codice più pulito rispetto al catturare l'eccezione nel test. Si confrontino questi due test:

# unittest_exception.py

import unittest


def raises_error(*args, **kwds):
    raise ValueError('Valore non valido: ' + str(args) + str(kwds))


class ExceptionTest(unittest.TestCase):

    def testTrapLocally(self):
        try:
            raises_error('a', b='c')
        except ValueError:
            pass
        else:
            self.fail('Non rilevato ValueError')

    def testAssertRaises(self):
        self.assertRaises(
            ValueError,
            raises_error,
            'a',
            b='c',
        )

Il risultato è lo stesso per entrambi, ma il secondo test, che usa assertRaises() è più conciso.

$ python3 -m unittest -v unittest_exception.py

testAssertRaises (unittest_exception.ExceptionTest) ... ok
testTrapLocally (unittest_exception.ExceptionTest) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Impianti di Test

Gli impianti (fixtures) sono risorse esterne necessarie a un test. Ad esempio per verificare una classe potrebbe servire una istanza di un'altra classe che fornisce impostazioni di configurazione o altre risorse condivise. Altri impianti di test potrebbero includere connessioni di database e file temporanei (molte persone potrebbero argomentare che l'utilizzo di risorse esterne rende i test "non unitari", ma sono comunque test e sono comunque utili).

unittest include agganci speciali per configurare e pulire un qualunque impianto serva ai test. Per attivare impianti per ciasun caso di test individuale, si sovrascriva setUp() su TestCase. Per pulirli si sovrascriva tearDown(). Per gestire un insieme di impianti per tutte le istanze di una classe di test si sovrascrivano i metodi della classe setUpClass() e tearDownClass(). Per gestire operazioni di impostazione particolarmente esose per tutti i test all'interno di un modulo, si usino le funzioni a livello di modulo setUpModule() e tearDownModule().

# unittest_fixtures.py

import random
import unittest


def setUpModule():
    print('In setUpModule()')


def tearDownModule():
    print('In tearDownModule()')


class FixturesTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print('In setUpClass()')
        cls.good_range = range(1, 10)

    @classmethod
    def tearDownClass(cls):
        print('In tearDownClass()')
        del cls.good_range

    def setUp(self):
        super().setUp()
        print('\nIn setUp()')
        # Scegliere un numero che si conosce essere nell'intervallo.
        # L'intervallo è definito com non comprendente il valore finale, quindi
        # assicurarsi che non sia incluso nell'insieme dei valori consentiti
        # per la scelta.
        self.value = random.randint(
            self.good_range.start,
            self.good_range.stop - 1,
        )

    def tearDown(self):
        print('In tearDown()')
        del self.value
        super().tearDown()

    def test1(self):
        print('In test1()')
        self.assertIn(self.value, self.good_range)

    def test2(self):
        print('In test2()')
        self.assertIn(self.value, self.good_range)

Quando questo test di esempio viene eseguito, l'ordine di esecuzione dell'impianto e dei metodi di test è evidente.

$ python3 -u -m unittest -v unittest_fixtures.py

In setUpModule()
In setUpClass()
test1 (unittest_fixtures.FixturesTest) ... 
In setUp()
In test1()
In tearDown()
ok
test2 (unittest_fixtures.FixturesTest) ... 
In setUp()
In test2()
In tearDown()
ok
In tearDownClass()
In tearDownModule()

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

Il metodi tearDown potrebbero non essere chiamati se si verificano errori nel processo di pulizia degli impianti. Per assicurarsi che un impianto venga sempre rilasciato correttamente si usi addCleanUp().

# unittest_addcleanup.py

import random
import shutil
import tempfile
import unittest


def remove_tmpdir(dirname):
    print('In remove_tmpdir()')
    shutil.rmtree(dirname)


class FixturesTest(unittest.TestCase):

    def setUp(self):
        super().setUp()
        self.tmpdir = tempfile.mkdtemp()
        self.addCleanup(remove_tmpdir, self.tmpdir)

    def test1(self):
        print('\nIn test1()')

    def test2(self):
        print('\nIn test2()')

Questo esempio di test crea una directory temporanea, quindi usa shutil per la pulizia una volta terminato il test.

$ python3 -u -m unittest -v unittest_addcleanup.py

test1 (unittest_addcleanup.FixturesTest) ... 
In test1()
In remove_tmpdir()
ok
test2 (unittest_addcleanup.FixturesTest) ... 
In test2()
In remove_tmpdir()
ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OK

Ripetere Test con Input Differenti

E' spesso utile eseguire la stessa logica di test con input diversi. Invece che definire un metodo di test separato per ciascun piccolo caso, un sistema comunemente adottato è di utilizzare un metodo di test che contiene diverse chiamate di asserzioni. Il problema con questo approccio è che non appena fallisce una asserzione, il resto viene saltato. Una soluzione migliore è l'utilizzo di subTest() per creare un contesto per un test all'interno del metodo di test. Se il test fallisce, il fallimento viene riportato e le verifiche rimanenti continuano.

# unittest_subtest.py

import unittest


class SubTest(unittest.TestCase):

    def test_combined(self):
        self.assertRegex('abc', 'a')
        self.assertRegex('abc', 'B')
        # The next assertions are not verified!
        self.assertRegex('abc', 'c')
        self.assertRegex('abc', 'd')

    def test_with_subtest(self):
        for pat in ['a', 'B', 'c', 'd']:
            with self.subTest(pattern=pat):
                self.assertRegex('abc', pat)

In questo esempio, il metodo test_combined() non esegue mail le asserzioni per i modelli 'c' e 'd'. Il metodo test_with_subtest() lo fa e riporta correttamente i fallimenti rimanenti. Si noti che l'esecutore del test considera che ci siano solo due casi di test, nonostante siano riportati tre fallimenti.

$ python3 -m unittest -v unittest_subtest.py

test_combined (unittest_subtest.SubTest) ... FAIL
test_with_subtest (unittest_subtest.SubTest) ... 
======================================================================
FAIL: test_combined (unittest_subtest.SubTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_subtest.py", line 10, in test_combined
    self.assertRegex('abc', 'B')
AssertionError: Regex didn't match: 'B' not found in 'abc'

======================================================================
FAIL: test_with_subtest (unittest_subtest.SubTest) (pattern='B')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_subtest.py", line 18, in test_with_subtest
    self.assertRegex('abc', pat)
AssertionError: Regex didn't match: 'B' not found in 'abc'

======================================================================
FAIL: test_with_subtest (unittest_subtest.SubTest) (pattern='d')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/dati/dev/python/pymotw3restyling/dumpscripts/unittest_subtest.py", line 18, in test_with_subtest
    self.assertRegex('abc', pat)
AssertionError: Regex didn't match: 'd' not found in 'abc'

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=3)

Saltare Test

E' spesso utile poter saltare un test se una qualche condizione esterna non viene corrisposta. Ad esempio, quando si scrivono test per verificare il comportamento di una libreria sotto una versione specifica di Python non c'è ragione di eseguire detti test sotto altre versioni di Pyhton. Le classi di test e i metodi possono essere decorati con skip() per saltare sempre i test. I decoratori skipIf() e skipUnless() possono essere utilizzati per verificare una condizione prima di saltare i test corrispondenti.

# unittest_skip.py

import sys
import unittest


class SkippingTest(unittest.TestCase):

    @unittest.skip('sempre saltati')
    def test(self):
        self.assertTrue(False)

    @unittest.skipIf(sys.version_info[0] > 2,
                     'eseguito solo su python 2')
    def test_python2_only(self):
        self.assertTrue(False)

    @unittest.skipUnless(sys.platform == 'Darwin',
                         'eseguito solo su macOS')
    def test_macos_only(self):
        self.assertTrue(True)

    def test_raise_skiptest(self):
        raise unittest.SkipTest('saltato tramite eccezione')

Per condizioni complesse che è difficile esprimere in una singola condizione passata a skip(f() o skipUnless(), un caso di test potrebbe sollevare direttamente skipTest per far sì che il test venga saltato.

$ python3 -m unittest -v unittest_skip.py

test (unittest_skip.SkippingTest) ... skipped 'sempre saltati'
test_macos_only (unittest_skip.SkippingTest) ... skipped 'eseguito solo su macOS'
test_python2_only (unittest_skip.SkippingTest) ... skipped 'eseguito solo su python 2'
test_raise_skiptest (unittest_skip.SkippingTest) ... skipped 'saltato tramite eccezione'

----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK (skipped=4)

Invece che eliminare test che sono persistentemente rotti, si possono marcare con il decoratore expectedFailure() in modo che il fallimento venga ignorato.

# unittest_expectedfailure.py

import unittest


class Test(unittest.TestCase):

    @unittest.expectedFailure
    def test_never_passes(self):
        self.assertTrue(False)

    @unittest.expectedFailure
    def test_always_passes(self):
        self.assertTrue(True)

Se un test che ci si attende fallisca viceversa venisse superato, quella condizione viene trattata come uno speciale tipo di fallimento e viene riportato come un unexpected success (successo inatteso - n.d.t.).

$ python3 -m unittest -v unittest_expectedfailure.py

test_always_passes (unittest_expectedfailure.Test) ... unexpected success
test_never_passes (unittest_expectedfailure.Test) ... expected failure

----------------------------------------------------------------------
Ran 2 tests in 0.000s

FAILED (expected failures=1, unexpected successes=1)

Vedere anche:

unittest
La documentazione della libreria standard per questo modulo.
doctest
Un modo alternativo di eseguire test incorporati in docstring o file esterni di documentazione.
nose
Un esecutore di test di terze parti con caratteristiche di scoperta sofisticate.
pytest
Un popolare esecutore di test di terze parti con supporto per l'esecuzione distribuita e un sistema di gestione di impianti alternativo.
testrepository
Un esecutore di test di terze parti usato dal progetto OpenStack, con supporto per l'esecuzione parallela e la tracciatura dei fallimenti.