2012-07-08 2 views
5

Sono classi di test delle unità in Python utilizzando unittest. A quanto ho capito, unittest chiama la funzione setUp prima di ogni test in modo che lo stato degli oggetti di test dell'unità siano gli stessi e l'ordine in cui viene eseguito il test non abbia importanza.Oggetti di test delle unità in Python - L'oggetto non è sovrascritto nell'impostazione

ora ho questa classe sto testando ...

#! usr/bin/python2 

class SpamTest(object): 

    def __init__(self, numlist = []): 
     self.__numlist = numlist 

    @property 
    def numlist(self): 
     return self.__numlist 

    @numlist.setter 
    def numlist(self, numlist): 
     self.__numlist = numlist 

    def add_num(self, num): 
     self.__numlist.append(num) 

    def incr(self, delta): 
     self.numlist = map(lambda x: x + 1, self.numlist) 

    def __eq__(self, st2): 
     i = 0 
     limit = len(self.numlist) 

     if limit != len(st2.numlist): 
      return False 

     while i < limit: 
      if self.numlist[i] != st2.numlist[i]: 
       return False 

      i += 1 

     return True 

con i seguenti test di unità ...

#! usr/bin/python2 

from test import SpamTest 

import unittest 

class Spammer(unittest.TestCase): 

    def setUp(self): 
     self.st = SpamTest() 
     #self.st.numlist = [] <--TAKE NOTE OF ME! 
     self.st.add_num(1) 
     self.st.add_num(2) 
     self.st.add_num(3) 
     self.st.add_num(4) 

    def test_translate(self): 
     eggs = SpamTest([2, 3, 4, 5]) 
     self.st.incr(1) 
     self.assertTrue(self.st.__eq__(eggs)) 

    def test_set(self): 
     nl = [1, 4, 1, 5, 9] 
     self.st.numlist = nl 
     self.assertEqual(self.st.numlist, nl) 

if __name__ == "__main__": 
    tests = unittest.TestLoader().loadTestsFromTestCase(Spammer) 
    unittest.TextTestRunner(verbosity = 2).run(tests) 

Questo test non riesce per test_translate.

posso fare due cose per rendere i test di successo:

(1) Rimuovere il commento dalla seconda linea nella funzione. Oppure,

(2) Modificare i nomi dei test in modo tale che translate si verifichi per primo. Ho notato che unittest esegue i test in ordine alfabetico. Cambiare translate in, diciamo, atranslate in modo che venga eseguito per primo rende tutti i test riusciti.

Per (1), non riesco a immaginare come questo influisce sui test poiché nella prima riga di setUp, creiamo un nuovo oggetto per self.st. Per quanto riguarda (2), il mio reclamo è simile poiché, hey, su setUp assegno un nuovo oggetto a self.st quindi qualsiasi cosa faccia a self.st in test_set non dovrebbe influire sul risultato di test_translate.

Quindi, cosa mi manca qui?

risposta

10

Senza studiare i dettagli della soluzione, è necessario leggere lo Default Parameter Values in Python di Fredrik Lundh.

È probabile che spieghi il problema con l'elenco vuoto come argomento predefinito. Il motivo è che la lista è vuota solo per la prima volta, a meno che non la rendi vuota in seguito esplicitamente.L'elenco predefinito vuoto iniziale è la singola istanza del tipo di lista che viene riutilizzata quando non viene passato alcun argomento esplicito.

È una buona idea leggere l'articolo precedente per fissare il tuo pensiero sugli argomenti predefiniti. Le ragioni sono logiche, ma potrebbero essere inaspettate.

La correzione generalmente consigliato è quello di utilizzare None come valore di default del __init__ e impostare la lista vuota all'interno del corpo se l'argomento non è passato, in questo modo:

class SpamTest(object): 

    def __init__(self, numlist=None): 
     if numlist is None: 
      numlist = []   # this is the new instance -- the empty list 
     self.__numlist = numlist 
+0

Quindi ... educami un po 'di più. Qual è la differenza tra 'is None' e' == None'? Ho sempre usato 'is None'; non sapevo che '== None' funzionasse. – skytreader

+1

@skytreader: l'operatore 'is' verifica l'identità dell'oggetto. Il valore 'None' è rappresentato dalla singola istanza della classe NoneType. Avere il valore 'None' significa che stai condividendo il riferimento all'oggetto identico. Il 'numlist è None' significa che stai testando se l'oggetto identico è condiviso. L'operatore '==' è più complicato. Se 'numlist' fosse l'istanza (riferimento a) di una classe che definisce il proprio metodo' .__ eq__', '==' potrebbe produrre un valore booleano imprevisto. Tuttavia, è lo stesso in casi semplici. – pepr

4

Ciò è dovuto al modo in cui i parametri predefiniti si comportano in Python quando si utilizzano oggetti Mutabili come gli elenchi: Default Parameter Values in Python.

Nella linea:

def __init__(self, numlist = []): 

Il parametro predefinito per numlist viene valutata solo una volta in modo da avere una sola istanza della lista che è condiviso in tutti istanza della classe SpamTest.

Quindi, anche se il test setUp viene chiamato per ogni test, non crea mai una nuova lista vuota, e i test che funzionano su quell'istanza dell'elenco finiscono per calpestare l'un l'altro le dita dei piedi.

La correzione è di avere qualcosa di simile, invece, utilizzando un oggetto non mutabile come None:

def __init__(self, numlist = None): 
    if numlist is None: 
     numlist = [] 
    self.__numlist = numlist 

Il motivo per cui funziona quando si imposta la proprietà è che si fornisce un nuovo elenco vuoto lì, sostituendo l'elenco creato nel costruttore.

+0

Oh, merda. Il mio +1> :) Sembra che copio/incollato la tua soluzione. Ma è davvero solo la coincidenza. Probabilmente solo 'numlist è None' sarebbe meglio. – pepr

+0

@pepr nessun problema +1 da parte mia anche a te per una buona forma, e anche la risposta giusta :) L'articolo di Fredrik Lundh è davvero grandioso quindi non c'è da sorprenderci se lo abbiamo menzionato entrambi. –