Come testate un encoder?


9

Ho qualcosa del genere:

public byte[] EncodeMyObject(MyObject obj)

Sono stato test di unità in questo modo:

byte[] expectedResults = new byte[3]{ 0x01, 0x02, 0xFF };
Assert.IsEqual(expectedResults, EncodeMyObject(myObject));

EDIT: I due modi che ho visto proposti sono:

1) Utilizzo di valori previsti codificati, come nell'esempio sopra.

2) Utilizzo di un decodificatore per decodificare l'array di byte codificato e confrontare gli oggetti di input / output.

Il problema che vedo con il metodo 1 è che è molto fragile e richiede molti valori codificati.

Il problema con il metodo 2 è che il test dell'encoder dipende dal corretto funzionamento del decoder. Se l'encoder / decodificatore si rompono allo stesso modo (nello stesso posto), i test potrebbero produrre falsi positivi.

Questi potrebbero benissimo essere gli unici modi per testare questo tipo di metodo. Se è così, allora va bene. Sto ponendo la domanda per vedere se ci sono strategie migliori per questo tipo di test. Non riesco a rivelare gli interni dell'encoder particolare su cui sto lavorando. Sto chiedendo in generale come risolveresti questo tipo di problema e non credo che gli interni siano importanti. Supponiamo che un determinato oggetto di input produca sempre lo stesso array di byte di output.


4
Come myObjectva da myObjecta { 0x01, 0x02, 0xFF }? Tale algoritmo può essere scomposto e testato? Il motivo per cui ti chiedo è attualmente, sembra che tu abbia un test che prova che una cosa magica produce un'altra cosa magica. La tua unica certezza è che l'unico input produce l'unico output. Se riesci a scomporre l'algoritmo, puoi acquisire ulteriore fiducia nell'algoritmo ed essere meno dipendente da input e output magici.
Anthony Pegram,

3
@Codismo Cosa succede se l'encoder e il decodificatore si rompono nello stesso posto?
ConditionRacer

2
I test, per definizione, stanno facendo qualcosa e controllando se hai ottenuto i risultati attesi, che è ciò che fa il tuo test. Ovviamente, dovresti assicurarti di fare abbastanza test del genere per assicurarti di esercitare tutto il codice, i casi limite e altre stranezze.
Blrfl,

1
@ Justin984, beh, ora stiamo andando più in profondità. Non esporrei quegli interni privati ​​come membri dell'API dell'Encoder, certamente no. Li rimuoverei completamente dall'encoder. O meglio, l'Encoder avrebbe delegato altrove, una dipendenza . Se è una battaglia tra un metodo di mostri non verificabile o un gruppo di classi di aiuto, scelgo le classi di aiuto ogni volta. Ma di nuovo, sto facendo inferenze non informate al tuo codice a questo punto, perché non riesco a vederlo. Ma se vuoi acquisire fiducia nei tuoi test, avere metodi più piccoli che fanno meno cose è un modo per arrivarci.
Anthony Pegram,

1
@ Justin984 Se le specifiche cambiano, si modifica l'output previsto nel test e ora fallisce. Quindi, si modifica la logica dell'encoder da passare. Sembra esattamente come dovrebbe funzionare TDD e fallirà solo quando dovrebbe. Non vedo come questo lo renda fragile.
Daniel Kaplan,

Risposte:


1

Ti trovi in ​​una situazione un po 'odiosa lì. Se avessi un formato statico in cui stavi codificando, il tuo primo metodo sarebbe la strada da percorrere. Se fosse solo il tuo formato, e nessun altro doveva decodificare oltre al secondo metodo sarebbe la strada da percorrere. Ma non rientri in nessuna di queste categorie.

Quello che farei è cercare di scomporre le cose in base al livello di astrazione.

Quindi inizierei con qualcosa a livello di bit, che testerei qualcosa di simile

bitWriter = new BitWriter();
bitWriter.writeInt(42, bits = 7);
assertEqual( bitWriter.data(), {0x42} )

Quindi l'idea è che il bitwriter sappia scrivere i tipi di campi più primitivi, come gli ints.

Tipi più complessi verrebbero implementati usando e testati qualcosa del tipo:

bitWriter = new BitWriter();
writeDate(bitWriter, new Datetime(2001, 10, 4));

bitWriter2 = new BitWriter();
bitWriter2.writeInt(2001, 12)
bitWriter2.writeInt(10, 4)
bitWriter2.writeInt(4, 6)

assertEquals(bitWriter.data(), bitWriter2.data() )

Si noti che ciò evita qualsiasi conoscenza di come vengono impacchettati i bit effettivi. Questo è testato dal test precedente e per questo test supponiamo praticamente che funzioni.

Quindi al prossimo livello di astrazione avremmo

bitWriter = new BitWriter();
encodeObject(bitWriter, myObject);


bitWriter2 = new BitWriter();
bitWriter2.writeInt(42, 32)
writeDate(bitWriter2, new Datetime(2001, 10, 4));
writeVarString(bitWriter2, "alphanumeric");

assertEquals(bitWriter.data(), bitWriter2.data() )

quindi, ancora una volta, non proviamo a includere la conoscenza di come i varstrings o le date o i numeri siano effettivamente codificati. In questo test, siamo interessati solo alla codifica prodotta da encodeObject.

Il risultato finale è che se il formato per le date viene modificato, dovrai correggere i test che coinvolgono effettivamente le date, ma tutti gli altri codici e test non riguardano il modo in cui le date vengono effettivamente codificate e una volta aggiornato il codice per creare che funziona, tutti quei test passeranno bene.


Mi piace questo. Immagino che questo sia ciò che alcuni altri commentatori stavano dicendo sulla sua suddivisione in pezzi più piccoli. Non evita completamente il problema quando le specifiche cambiano, ma lo rende migliore.
ConditionRacer

6

Dipende. Se la codifica è qualcosa di completamente risolto, in cui ogni implementazione dovrebbe creare esattamente lo stesso output, non ha senso controllare altro che verificare che gli input di esempio siano mappati esattamente agli output previsti. Questo è il test più ovvio, e probabilmente anche il più semplice da scrivere.

Se esiste uno spazio di manovra con output alternativi, come nello standard MPEG (ad esempio ci sono alcuni operatori che è possibile applicare all'input, ma si è liberi di scambiare lo sforzo di codifica rispetto alla qualità dell'output o allo spazio di archiviazione), quindi è meglio applicare il ha definito la strategia di decodifica per l'output e verifica che sia uguale all'input o, se la codifica è in perdita, che sia ragionevolmente vicina all'input originale. È più difficile da programmare, ma ti protegge da eventuali miglioramenti futuri che potrebbero essere apportati al tuo codificatore.


2
Supponiamo di utilizzare il decodificatore e confrontare i valori. Cosa succede se l'encoder e il decoder sono entrambi rotti nello stesso posto? Il codificatore codifica in modo errato e il decodificatore si decodifica in modo errato, ma gli oggetti di input / output sono corretti perché il processo è stato eseguito in modo errato due volte.
ConditionRacer

@ Justin984 quindi usa i cosiddetti "vettori di test", conosci le coppie input / output che puoi usare esattamente per testare un encoder e un decoder
maniaco del cricchetto

@ratchet maniaco Questo mi riporta ai test con i valori previsti. Il che va bene, è quello che sto facendo attualmente, ma è un po 'fragile, quindi stavo cercando di vedere se ci sono modi migliori.
ConditionRacer,

1
Oltre a leggere attentamente lo standard e creare un test case per ogni regola, non c'è modo di evitare che sia un codificatore che un decodificatore contengano lo stesso bug. Ad esempio, supponiamo che "ABC" debba essere tradotto in "xyz" ma il codificatore non lo sa e anche il tuo decodificatore non capirà "xyz" se mai lo incontrasse. I testcase artigianali non contengono la sequenza "ABC", perché il programmatore non era a conoscenza di quella regola, e anche un test con codifica / decodifica di stringhe casuali passerebbe erroneamente perché sia ​​l'encoder che il decoder ignorano il problema.
user281377,

1
Per aiutare a rilevare i bug che interessano sia i decodificatori che gli encoder scritti da soli a causa della mancanza di conoscenza, fare uno sforzo per ottenere output dell'encoder da altri fornitori; e prova anche a testare l'output del tuo codificatore sui decodificatori di terze parti. Non esiste un'alternativa.
rwong

3

Prova questo encode(decode(coded_value)) == coded_valuee decode(encode(value)) == value. Se lo desideri, puoi dare un input casuale ai test.

È ancora possibile che sia l'encoder che il decoder siano rotti in modo complementare, ma ciò sembra abbastanza improbabile a meno che tu non abbia un equivoco concettuale dello standard di codifica. Fare test hardcoded dell'encoder e del decoder (come stai già facendo) dovrebbe evitare questo.

Se hai accesso a un'altra implementazione di questo che è noto per funzionare, puoi almeno usarla per avere fiducia che la tua implementazione sia buona anche se usarla nei test unitari sarebbe impossibile.


Concordo sul fatto che un errore encoder / decoder complementare è improbabile in generale. Nel mio caso specifico, il codice per le classi encoder / decoder è generato da un altro strumento basato sulle regole di un database. Quindi occasionalmente si verificano errori complementari.
ConditionRacer,

Come possono esserci "errori gratuiti"? Ciò implica che esiste una specifica esterna per la forma codificata, e quindi un decodificatore esterno.
Kevin Cline,

Non capisco il tuo uso della parola esterno. Ma c'è una specifica per come sono codificati i dati e anche un decodificatore. Un errore gratuito è in cui l'encoder e il decoder funzionano entrambi in modo complementare ma che si discosta dalle specifiche. Ho un esempio nei commenti sotto la domanda originale.
ConditionRacer

Se l'encoder doveva implementare ROT13 ma accidentalmente ROT14 e lo faceva anche il decoder, allora decodifica (encode ('a')) == 'a' ma l'encoder è ancora rotto. Per cose molto più complicate di così, probabilmente è molto meno probabile che accada quel genere di cose, ma teoricamente potrebbe.
Michael Shaw,

@MichaelShaw solo una curiosità, l'encoder e il decoder per ROT13 sono gli stessi; ROT13 è il suo contrario. Se hai implementato ROT14 per errore, allora decode(encode(char))non sarebbe uguale char(sarebbe uguale char+2).
Tom Marthenal,

2

Test per i requisiti .

Se i requisiti sono solo "codificare in un flusso di byte che quando viene decodificato produce un oggetto equivalente", testare il codificatore decodificandolo. Se stai scrivendo sia l'encoder che il decoder, testali insieme. Non possono avere "errori corrispondenti". Se lavorano insieme, il test ha esito positivo.

Se ci sono altri requisiti per il flusso di dati, dovrai verificarli esaminando i dati codificati.

Se il formato codificato è predefinito, allora dovrai verificare i dati codificati rispetto al risultato previsto, come hai fatto, o (meglio) ottenere un decodificatore di riferimento che può essere considerato attendibile per eseguire la verifica. L'uso di un decodificatore di riferimento elimina la possibilità di interpretare erroneamente le specifiche del formato.


1

A seconda del framework di test e del paradigma che stai usando, puoi comunque usare il modello Arrange Act Assert per questo come hai detto.

[TestMethod]
public void EncodeMyObject_ForValidInputs_Encodes()
{
    //Arrange object under test
    MyEncoder encoderUnderTest = new MyEncoder();
    MyObject validObject = new MyOjbect();
    //arrange object for condition under test

    //Act
    byte[] actual = encoderUnderTest.EncodeMyObject(myObject);

    //Assert
    byte[] expected = new byte[3]{ 0x01, 0x02, 0xFF };
    Assert.IsEqual(expected, actual);
}

Dovresti conoscere i requisiti EncodeMyObject()e puoi usare questo modello per testare ciascuno di essi per criteri validi e non validi, disponendo ciascuno di essi e codificando il risultato atteso expected, allo stesso modo per il decodificatore.

Dal momento che le aspettative sono codificate, queste saranno fragili se hai un cambiamento enorme.

Potresti essere in grado di automatizzare con qualcosa guidato da parametri (dai un'occhiata a Pex ) o se stai facendo DDD o BDD dai un'occhiata a gerkin / cucumber .


1

Puoi decidere cosa è importante per te.

È importante per te che un oggetto sopravviva al round trip e che il formato esatto del filo non sia davvero importante? Oppure il formato esatto del filo è una parte importante della funzionalità del tuo codificatore e decodificatore?

Se il primo, basta assicurarsi che gli oggetti sopravvivano al viaggio di andata e ritorno. Se l'encoder e il decoder sono entrambi rotti in modo esattamente complementare, non ti interessa davvero.

In quest'ultimo caso, è necessario verificare che il formato del filo sia come previsto per gli input forniti. Ciò significa testare direttamente il formato oppure utilizzare un'implementazione di riferimento. Ma avendo testato le basi, potresti ottenere valore da ulteriori test di andata e ritorno, che dovrebbero essere più facili da scrivere in volume.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.