Come si testerebbe una funzione con un numero elevato di scenari?

4

Ho sentito che è ideale avere un assert (o expect o should , ecc.) per aspetto del comportamento di un componente che stai provando a testare. è così che funziona? Una funzione addOne verrebbe testata in questo modo, suppongo (l'esempio è scritto in javascript sintassi):

it("returns one plus its input", function() {
  assert(addOne(5) == 6);
});

E suppongo che sarebbe l'estensione completa dei test per la funzione addOne , poiché ogni singolo assert dovrebbe essere ortogonale.

Come si fa a testare qualcosa di più complesso, ma che ha ancora solo un comportamento definitivo?

Diciamo per esempio che abbiamo una classe OO per la memorizzazione di una scacchiera, e ad ogni piastrella della scheda è assegnato un numero ID compreso tra 0 e 63. Quindi diciamo che c'è un metodo getNeighbors che prende un numero intero da 0 a 63 e restituisce un elenco intero ordinato / matrice / set contenente gli ID di tutte le tessere adiacenti. Ad esempio, se i numeri fossero disposti partendo da 0 in alto a sinistra e incrementando su tutte le righe, allora getNeighbors(0) restituirebbe [1, 8, 9] suppongo.

Quanti casi di test verrebbero scritti per questo? Vedo due opzioni principali:

Prova l'intero comportamento una volta

it("throws an error when called with a number below 0", ...);
it("throws an error when called with a number above 63", ...);
it("returns a list of the IDs of the neighboring tiles", function() {
  assert(getNeighbors(0) == [1, 8, 9]);
});

Questo sembra troppo debole per un test.

Verifica ogni caso del comportamento una volta

it("throws an error when called with a number below 0", ...);
it("throws an error when called with a number above 63", ...);
it("returns [1, 8, 9] when called on 0", function() {
  assert(getNeighbors(0) == [1, 8, 9]);
});
it("returns [0, 2, 8, 9, 10] when called on 1", function() {
  assert(getNeighbors(1) == [0, 2, 8, 9, 10]);
});
... // and so on, for 62 more test cases

Questo sembra esagerato.

    
posta vijrox 24.06.2016 - 22:10
fonte

3 risposte

8

Puoi fare due cose:

In primo luogo, utilizza test parametrizzati per ridurre al minimo la duplicazione del codice di test:

   cases([
     [0, [1, 8, 9]],
     [1, [0, 2, 8, 9, 10]],
     // more testcases here
   ])
   .it('sample', function(n, expected) {
     expect(getNeighbors(n)).toEqual(expected);
   });

In secondo luogo, suddividi i tuoi test in classi di equivalenza dove ti aspetti che il codice si comporti esattamente lo stesso, e hanno un solo test per ogni classe. Nei tuoi casi, le classi sarebbero probabilmente:

  • quadrato in alto a sinistra
  • quadrato in alto a destra
  • angolo in basso a sinistra
  • angolo in basso a destra
  • quadrati di bordo non angolari
  • riquadri del bordo sinistro non angolo
  • riquadri non rettangolari a destra
  • quadrati di bordo inferiori non angolari
  • tutti i quadrati interni
risposta data 24.06.2016 - 22:35
fonte
2

Dì che è rotto. Di 'che stai pensando a qualcos'altro e non hai molta pazienza per questo bug. Quale test desideri essere stato scritto adesso?

Metto alla prova tutto quello che posso farla franca. Ad un certo punto devi fermarti e farti pagare.

Tenendolo presente, non è semplicemente un comportamento. Sono i confini. Il tuo comportamento è "mostrami i vicini". I tuoi confini in cui la logica di quel comportamento cambia sono a 0,1,7, 8, 9, 16, 57, 58, & 63. Questi generano ogni forma possibile attorno al tuo quadrato.

Sono 9 test che personalmente preferirei vedere fatto nel tuo primo stile prima di abbatterli per il tuo secondo stile. È meglio catturare di più e riportarlo in modo scadente piuttosto che riportare esattamente e perdere molto.

Idealmente, se dovessi eseguire il debug del tuo codice (e un giorno potrei esserlo), vorrei tutti i 9 del secondo stile. Ma in 20 anni di codice non ho ancora visto il codice "ideale".

Per favore, datemi il più possibile.

    
risposta data 24.06.2016 - 23:01
fonte
1

tldr; La prima versione "debole" implica che hai scritto l'intera implementazione per superare il test. Dovresti utilizzare ciascuno dei test di forza bruta per guidare la tua implementazione, uno alla volta.

Va bene la versione lunga.

L'hai taggato con #TDD, quindi ti risponderò come farei in un modo basato su TDD. Generalmente vuoi iniziare con i casi più semplici e costruirti uno alla volta, ovvero l'approccio della forza bruta. Quindi i tuoi primi due casi sono abbastanza buoni, in quanto risulterebbero in un codice molto semplice da scrivere. Comunque il modo in cui hai spiegato il tuo intero comportamento una volta ti avvicina a uno è chiaro che immagini di scrivere quel test e poi di implementare l'intero algoritmo, o prima di scrivere l'intero algoritmo e poi aggiungere i test unitari. Questo non è sufficiente in un approccio basato su test o test-after.

Dopo aver superato ciascuno dei tuoi primi due casi di test, non avrei ancora implementato alcuna implementazione: sarebbe stato solo il controllo dei limiti. Ora cerco il prossimo caso di test che mi serve per scrivere il codice più semplice. Non voglio ancora scrivere l'intera implementazione - è probabile che si verifichino errori per più test case contemporaneamente. Invece, qual è un caso semplice che mi costringerà a scrivere più codice?

CandiedOrange ha fornito una buona lista di casi limite, ma 0 non è un buon primo caso. Il motivo è che 0 non ha nulla sopra di esso o alla sua sinistra. Questi sono casi speciali. Scegli invece un caso che si trova nel mezzo e assicurati che sputa fuori tutti i quadrati. Dovrebbe essere semplice (senza ifs o loop, solo ottenere tutti i quadrati attorno ad esso) e la configurazione più comune. Quindi aggiungine uno che si trova nella riga superiore, la riga destra, la riga inferiore, la riga sinistra e infine uno o due spigoli.

È un ordine perfetto? Non so - l'ordine perfetto non esiste - ma probabilmente è abbastanza vicino. Dopo ogni prova, scrivi quanto basta per ottenere il passaggio del nuovo test (e di tutti i test precedentemente scritti). Ciò significa che dovresti essere in grado di concentrarti su un caso alla volta, invece di eseguire il debug di più contemporaneamente.

Alla fine potresti voler ripulire i test usando un approccio basato sui dati. Uso raramente quelli personalmente, ma li apprezzo quando sono fatti bene.

    
risposta data 14.07.2016 - 02:52
fonte

Leggi altre domande sui tag