Esercizio

Completato

In questo esercizio si userà pytest con parametrizza per testare una funzione. Si aggiornerà quindi una classe di test per usare una fixture anziché un metodo setup() e teardown(). Usare parametrize e lavorare con le fixture consente di essere più flessibili nella creazione o aggiornamento dei test.

Passaggio 1 - Aggiungere un file con test per questo esercizio

  1. Creare un nuovo file di test denominato test_advanced.py e aggiungere il codice seguente:

    def str_to_bool(string):
        if string.lower() in ['yes', 'y', '1']:
            return True
        elif string.lower() in ['no', 'n', '0']:
            return False
    
    

    La funzione str_to_bool() accetta una stringa come input e a seconda del relativo contenuto restituisce un valore True o False.

  2. Nello stesso file aggiungere i test per la funzione str_to_bool(). Usare pytest.mark.parametrize() per testare prima tutti i valori true:

    import pytest 
    
    @pytest.mark.parametrize("string", ['Y', 'y', '1', 'YES'])
    def test_str_to_bool_true(string):
        assert str_to_bool(string) is True
    
  3. Aggiungere quindi un altro test con i valori false:

    @pytest.mark.parametrize("string", ['N', 'n', '0', 'NO'])
    def test_str_to_bool_false(string):
        assert str_to_bool(string) is False
    

    Sono ora disponibili due test che coprono tutti gli input possibili per i valori restituiti True e False.

Nota

La presenza di test come codice effettivo nello stesso file non è comune. Per semplicità, tuttavia, negli esempi di questo esercizio sarà presente codice effettivo nello stesso file. Nei progetti Python reali i test sono separati da file e directory dal codice che esegue il test.

Passaggio 2: Eseguire i test ed esaminare il report

Dopo aver aggiunto i test, il passaggio successivo consiste nell'eseguire pytest e nell'esaminare l'output. Usare il flag di dettaglio aumentato (-v) in modo che sia possibile visualizzare tutti i valori di input trattati come test separati.

$ pytest -v test_avanced.py
============================= test session starts ==============================
Python 3.9.6, pytest-6.2.5, py-1.11.0, pluggy-1.0.0 
rootdir: /private
collected 8 items

test_advanced.py::test_str_to_bool_true[Y] PASSED                        [ 12%]
test_advanced.py::test_str_to_bool_true[y] PASSED                        [ 25%]
test_advanced.py::test_str_to_bool_true[1] PASSED                        [ 37%]
test_advanced.py::test_str_to_bool_true[YES] PASSED                      [ 50%]
test_advanced.py::test_str_to_bool_false[N] PASSED                       [ 62%]
test_advanced.py::test_str_to_bool_false[n] PASSED                       [ 75%]
test_advanced.py::test_str_to_bool_false[0] PASSED                       [ 87%]
test_advanced.py::test_str_to_bool_false[NO] PASSED                      [100%]

============================== 8 passed in 0.01s ===============================

Anche se sono state scritte solo due funzioni di test, pytest è riuscito a creare otto test in totale grazie alla funzione parametrize().

Passaggio 3 - Convertire un test esistente in una fixture

  1. Aggiungere un nuovo test basato su classe al file test_advanced.py . Questo test deve usare una funzione setup() e teardown() che crea un file temporaneo con testo sul file stesso. Dopo ogni test, il file viene rimosso. Avrà un aspetto simile al seguente:

    import os
    
    
    class TestFile:
    
        def setup(self):
            with open("/tmp/done", 'w') as _f:
                _f.write("1")
    
        def teardown(self):
            try:
                os.remove("/tmp/done")
            except OSError:
                pass
    
        def test_done_file(self):
            with open("/tmp/done") as _f:
                contents = _f.read()
            assert contents == "1"
    

    Questa classe di test crea un file, ma è problematica perché il percorso /tmp/ non è garantito che sia presente in ogni sistema.

  2. Creare una fixture che usa la fixture pytesttmpdir() per scrivere nel file e restituire il percorso:

    import pytest
    
    @pytest.fixture
    def tmpfile(tmpdir):
        def write():
            file = tmpdir.join("done")
            file.write("1")
            return file.strpath
        return write
    

    La fixture tmpfile() usa la fixture tmpdir() di Pytest, che garantisce un file temporaneo valido pulito dopo l'esecuzione dei test.

  3. Aggiornare la TestFile classe in modo che anziché i metodi di supporto usi la fixture .

    class TestFile:
    
        def test_f(self, tmpfile):
            path = tmpfile()
            with open(path) as _f:
                contents = _f.read()
            assert contents == "1"
    

    Questa classe di test può ora garantire che un file temporaneo venga creato e abbia il contenuto appropriato per il funzionamento dell'asserzione.

Eseguire il test di quanto fatto finora

A questo ora dovrebbe essere presente un file Python denominato test_advanced.py con il codice seguente:

  • Funzione str_to_bool() che accetta una stringa e restituisce un valore booleano in base al contenuto della stringa.
  • Due test parametrizzati per la funzione str_to_bool(), uno che testa i valori True e l'altro che testa i valori False.
  • Una fixture pytest personalizzata che usa la fixture tmpdir() per creare un file done temporaneo con alcuni contenuti.
  • Classe di test che usa la fixture personalizzata tmpfile() per creare il file.

Tutti i test devono essere superati quando vengono eseguiti nel terminale, senza alcun errore.