Isolando i test di unità in python

0

Come sviluppatore di software di 30 anni, principalmente in lingue OO, ma un principiante in Python, sto cercando di trovare la migliore procedura per isolare i test di unità in python.

Diciamo che ho il seguente, semi-pseudo-codice. So some_database_client non è un vero client di database:

from some_database_client import connection

connection.connect('server_id', 'username', 'password')

def function_under_test():
    return connection.get_value('some_value_reference') + 10

Se scrivo un test per function_under_test() , allora sto effettivamente testando la quantità potenzialmente enorme di logica nel collegare e recuperare i dati da connection .

Invece, voglio davvero testare che function_under_test() aggiunga dieci al valore recuperato.

Questo si riferisce a un esempio pratico reale in cui la variabile a livello di modulo connection (o il suo equivalente nel mondo reale) viene riferita pesantemente su tutto il codice.

Pertanto, comporterebbe una modifica ampia e quindi molto rischiosa per passare connection come argomento a tutte le funzioni che lo utilizzano. Senza fare ciò, non posso separare facilmente la variabile connection quando sto provando a testare le funzioni che lo usano. connection viene istanziato prima ancora che io possa eseguire il test unitario.

Esiste un modo consigliato per isolare funzioni come questa per i test? Posso certamente pensare a molti, molti modi, ma sospetto che alcuni abbiano più dello "Zen of Python" su di loro rispetto ad altri. per esempio. Avvolgere tutto nelle classi è una possibilità, ma forse è troppo lontano dal tenere semplici le cose, il che è un obiettivo di python.

    
posta user1738833 21.03.2014 - 17:39
fonte

3 risposte

1

This relates to a practical real-world example where the module-level variable connection (or it's real-world equivalent) is referenced heavily all over the code. Therefore, while it would involve a large and therefore very risky change to pass connection as an argument to all the functions that use it.

Questa è l'unica soluzione pulita. Troppo tardi ora, ma in futuro eviterà tali dipendenze nascoste se possibile. L'unica altra opzione che posso pensare è di cambiare il percorso Python in modo che punti a un some_database_client alternativo con uno stub connection quando si eseguono i test unitari.

Tuttavia ...

Instead, I really want to test that function_under_test() adds ten to the value retrieved.

Non hai davvero bisogno di testare questo. Puoi dimostrare la correttezza di una funzione così banale di ispezione. Scrittura di unit test per mostrare che aggiunge dieci indipendentemente dal fatto che il valore di ritorno della connessione sia 0, positivo, negativo, pari o pari sia più lento del semplice guardarlo e non garantisce ancora la correttezza. Sapere cosa non provare è tanto importante quanto sapere cosa testare.

    
risposta data 21.03.2014 - 17:58
fonte
1

In primo luogo, l'inizializzazione di una connessione globale a livello di modulo è una pratica davvero pessima. Piuttosto, se hai per farlo, dovresti avere qualcosa di simile

def connect():
   connection.connect('server_id', 'username', 'password')

if __name__ == '__main__':
   connect()

In modo che tu ti connetti solo nell'esecuzione principale.

Fortunatamente, dal tuo esempio di codice, anche se ti connetti al database all'inizio del modulo, puoi semplicemente ignorare quell'assegnazione e assegnare una nuova connessione simulata al test.

def my_test():
   connection = MockConnection()
   function_under_test()
   assert(...)

FWIW Ho trovato che Python è davvero facile da testare perché se ci sono alcuni artefatti, dipendenze o funzioni con cui è difficile lavorare, vorrei solo introdurre un'implementazione che mi consenta di concentrarmi sul test.

    
risposta data 21.03.2014 - 18:05
fonte
0

Ha intrinsecamente a che fare con un concetto OOP di base ma importante: Inversion of control .

Uno dei modi per raggiungerlo è Iniezione di dipendenza . In poche parole, invece di chiamare una determinata funzione specificata nel corpo della funzione, chiamerai una funzione data dal chiamante.

Introduzione al tuo problema:

def function_under_test(connection):
    return connection.get_value('some_value_reference') + 10

Ora, dobbiamo passare un oggetto che ha il metodo get_value. In produzione, passeresti il vero oggetto di connessione. Durante il test, tuttavia, si passa una simulazione (versione falsa) di esso.

Un esempio di come il test diventa facile:

class FakeConnection():
    def get_value(self, reference):
        return 30

def test_function():
   connection = FakeConnection()
   result = function_under_test(connection)

   assert result == 40
    
risposta data 22.03.2014 - 04:03
fonte

Leggi altre domande sui tag