Devo utilizzare le astrazioni della mia stessa applicazione per l'impostazione del test?

6

Durante l'installazione di test, è meglio utilizzare API di basso livello per ottenere il mio sistema nello stato giusto o utilizzare le stesse astrazioni che la mia applicazione userebbe? Sono specificamente interessato a ciò che promuove la manutenzione a lungo termine in grandi basi di codici.

Esempio (in Ruby, ma la mia domanda è indipendente dalla lingua):

Un utente può acquistare un oggetto utilizzando la classe Sale:

class User
  attr_accessor :join_source, :status

  def web_join!
    @join_source = 'web'
    @status = 'rookie'
  end

  def promote!
    @status = 'veteran'
  end

  def web_rookie?
    join_source == 'web' && status == 'rookie'
  end
end

class Sale
  attr_reader :item, :user

  def initialize(item, user)
    @item = item
    @user = user
  end

  def finalize
    item.owner == user
    if user.web_rookie?
      Email.send_web_rookie_receipt(user)
    end
  end
end

Nel test di vendita, dovrò creare un utente in vari stati. Posso utilizzare le API a basso livello:

user = User.new
user.join_source = 'web'
user.status = 'rookie'
# test that the send_web_rookie_receipt goes out
user.status = 'veteran'
# test that the send_web_rookie_receipt does not goes out

O l'API che l'utente espone:

user = User.new
user.web_join!
# test that the send_web_rookie_receipt goes out
user.promote!
# test that the send_web_rookie_receipt does not goes out

Per chiarire, non sto chiedendo di testare User in questo scenario. Ti sto chiedendo dei miei test per Sale , e se l'impostazione per testare Sale dovrebbe utilizzare più metodi astratti su User (come 'promote!) O più concreti, metodi di basso livello (attributo attributo).

    
posta muirbot 21.09.2017 - 02:17
fonte

2 risposte

3

Dal mio punto di vista, questa domanda riguarda molto i trade-off. Ecco alcuni punti su cui riflettere quando si prende una decisione:

  • Se usi la tua API di alto livello e si interrompe, anche tutti i tuoi test si interrompono. Durante l'esecuzione dei test, verranno visualizzati numerosi test rossi e potrebbe essere più difficile identificare la causa principale.
  • L'utilizzo di un'API di basso livello porta a un codice più complesso nella configurazione di prova. Questo codice è anche soggetto a bug. Un grosso problema qui è che di solito non testate la configurazione di test in modo che questi errori siano molto difficili da individuare.
  • L'utilizzo dell'API di livello superiore autodidatta esegue test su test di integrazione, in quanto si utilizza (e quindi si verifica) l'API di alto livello. A seconda delle caratteristiche della tua API di alto livello, questo potrebbe rallentare notevolmente il test.

Dalla mia esperienza, cerco di utilizzare l'approccio API di basso livello il più spesso possibile per cercare di ridurre al minimo il numero di componenti sottoposti a test. Se ritieni che la configurazione del test sia molto complessa e fragile, questo potrebbe anche indicare un problema nella progettazione del tuo sistema (i componenti sono troppo strettamente accoppiati). Tuttavia, non è né una decisione né l'una né l'altra, e non nego anche l'altro approccio in quanto aumenta la produttività (almeno a breve termine) nei casi in cui l'impostazione di un test è troppo ingombrante e non può essere semplificata facilmente.

    
risposta data 21.09.2017 - 11:01
fonte
2

IMHO per il test delle unità, normalmente si dovrebbero usare solo parti di un'API che si prevede che un "client" di quella classe sia utilizzato. Quindi, in questo caso, se sono disponibili "API di basso livello" e "API di alto livello" per la classe User (ovvero "pubblico"), ed entrambi possono essere utilizzati per creare essenzialmente lo stesso test per Sale , usa quello che lo consente in un modo più facile, più semplice, meno soggetto a errori. Quale sia "più semplice" dipende dal caso, a volte può essere più semplice utilizzare l'API di basso livello per ottenere un oggetto in un determinato stato, a volte può essere l'API di alto livello.

Ma cosa succede se l'utilizzo dell'API di alto livello porta a un test di integrazione più complesso e l'utilizzo dell'API di basso livello è più un test di unità reale (quindi i test non sono essenzialmente gli stessi)? In questo caso, considera di implementare entrambi, come discusso in questo vecchio post SE.SE .

Se, tuttavia, solo la "API di alto livello" dovrebbe essere utilizzata principalmente dal codice "client" e un attributo come join_source è inteso solo per essere letto, ma non scritto, o si prevede che possa contenere solo un insieme fisso di valori, dovresti probabilmente mettere in discussione il fatto se è davvero un buon progetto avere qualcosa come join_source completamente pubblico. Rendere questo attributo "di sola lettura" per il codice client renderebbe l'API meno incline agli errori, e questo limiterebbe automaticamente i tuoi test all'API di alto livello.

Non sono un esperto di Ruby, ma credo che in un linguaggio di scripting in cui tutto sia pubblico per impostazione predefinita, non è probabilmente raro essere sciatto e lasciare alcuni attributi "pubblici", anche se non sono destinati a essere modificati da al di fuori. In un linguaggio come Java o C ++ o C #, in genere si rendono pubblici solo i "getter" per questi attributi e si lasciano i setter privati fin dall'inizio. AFAIK puoi farlo anche in Ruby.

    
risposta data 21.09.2017 - 08:00
fonte

Leggi altre domande sui tag