Sta avendo un interruttore per accendere o deridere un odore di codice?

7

Ho un metodo che assomiglia a questo:

def foobar(mock=False, **kwargs):
    # ... snipped

foobar effettua effettivamente diverse chiamate ad Amazon S3 e restituisce un risultato composto. Per rendere questo testabile, ho introdotto il parametro mock per disattivare le connessioni di rete attive. Mi sembra un odore di codice per me, ma anche la testabilità è molto importante. Cos'altro posso fare se voglio eliminare il parametro?

    
posta Bon Ami 01.08.2011 - 04:13
fonte

3 risposte

29

Perché non hai una classe di connessione, invece?

class Connection(object):
    def retrieve(self, resource):
        return something_from_s3()

class MockConnection(Connection):
    def retrieve(self, resource):
        return 42

def foobar(connection = Connection(), **kwargs):
    whatever = connection.retrieve("foobar")

Non solo è più pulito, ma puoi banalmente provarlo con varie connessioni finte. Se decidi di supportare un diverso tipo di servizio anziché S3, puoi estendere facilmente il tuo prodotto per supportarlo.

    
risposta data 01.08.2011 - 04:20
fonte
10

Sì, è un enorme, enorme odore di codice.

Se vuoi testare qualcosa che è supportato da S3, stubare o prendere in giro le chiamate che fai all'API S3 e farlo in questo modo.

Per un semplice esempio, dai un'occhiata a Paperclip: link

    
risposta data 01.08.2011 - 04:18
fonte
0

A volte, è più semplice passare semplicemente un "motore s3" come parametro e passare qualche semplice simulazione -object quando invece è un test (e fornire i suoi valori di ritorno). A volte è necessario chiamare molte funzioni casuali che causano effetti collaterali (ad esempio, se si lavora con s3, ottenere l'ora corrente, fare alcune query SQL, aprire alcuni file e altro), e qui non sarebbe bello per passare tutto come parametri di funzione (poiché non è necessario ad eccezione della testabilità), ti suggerisco di utilizzare mockstar per descrivere in modo dichiarativo i tuoi effetti collaterali. Il tuo codice sarebbe quindi simile a questo:

def foobar():
    foo = get_from_database()
    bar = read_from_file()
    baz = read_from_s3()

    if foo > 10:
        return foo + bar + baz
    else:
        return foo - bar - baz

# And your test would look something like this:

from mockstar import prefixed_p
from nose.tools import assert_equal

ppatch = prefixed_p('module.with.foobar.func')


class TestFoobar(BaseTestCase):
    @ppatch('get_from_database')
    @ppatch('read_from_file')
    @ppatch('read_from_s3')
    def side_effects(self, se):
        se.read_from_file.return_value = 10  # default behaviour
        se.read_from_s3.return_value = 0  # also default behaviour
        return self.invoke(se)

    def test_should_get_30(self, se):
        se.get_from_database.return_value = 20

        # do
        result = foobar()

        assert_equal(result, 30)

    def test_should_get_minus5(self, se):
        se.get_from_database.return_value = 5

        # do
        result = foobar()

        assert_equal(result, -5)
    
risposta data 31.01.2013 - 17:11
fonte

Leggi altre domande sui tag