Domanda

Questa è una domanda difficile e aperta, lo so, ma ho pensato di buttarla sul pavimento e vedere se qualcuno avesse qualche suggerimento interessante.

Ho sviluppato un generatore di codice che porta la nostra interfaccia Python nel nostro codice C++ (generato tramite SWIG) e genera il codice necessario per esporlo come servizi Web.Quando ho sviluppato questo codice l'ho fatto usando TDD, ma ho trovato i miei test dannatamente fragili.Poiché ogni test essenzialmente voleva verificare che per un dato bit di codice in input (che sembra essere un'intestazione C++) otterrei un dato bit di codice in output, ho scritto un piccolo motore che legge le definizioni di test dai file di input XML e genera test casi da queste aspettative.

Il problema è che ho paura di modificare il codice.Questo e il fatto che gli stessi test unitari sono:complesso e b:fragile.

Quindi sto cercando di pensare ad approcci alternativi a questo problema, e mi sembra che forse lo stia affrontando nel modo sbagliato.Forse devo concentrarmi maggiormente sul risultato, IE:il codice che genero viene effettivamente eseguito e fa quello che voglio, piuttosto che, il codice ha l'aspetto che voglio.

Qualcuno ha avuto esperienze di qualcosa di simile a questo che vorrebbe condividere?

È stato utile?

Soluzione

Ho iniziato a scrivere un riepilogo della mia esperienza con il mio generatore di codice, poi sono tornato indietro e ho riletto la tua domanda e ho scoperto che avevi già affrontato gli stessi problemi tu stesso, concentrandoti sui risultati dell'esecuzione anziché sul layout/aspetto del codice.

Il problema è che è difficile da testare, il codice generato potrebbe non essere adatto per essere effettivamente eseguito nell'ambiente del sistema di test unitario e come si codificano i risultati attesi?

Ho scoperto che è necessario suddividere il generatore di codice in parti più piccole e testarle in unità.Se me lo chiedi, il test unitario di un generatore di codice completo è più simile al test di integrazione che al test unitario.

Altri suggerimenti

Ricordiamo che il "test unitario" è solo un tipo di test.Dovresti essere in grado di testare l'unità interno pezzi del tuo generatore di codice.Quello che stai realmente guardando qui è il test a livello di sistema (akatest di regressione).Non è solo semantica...ci sono diverse mentalità, approcci, aspettative, ecc.È sicuramente più lavoro, ma probabilmente dovrai stringere i denti e impostare una suite di test di regressione end-to-end:risolti i file C++ -> Interfacce SWIG -> moduli Python -> output noto.Vuoi davvero controllare l'input noto (codice C++ fisso) rispetto all'output previsto (ciò che esce dal programma Python finale).Controllare direttamente i risultati del generatore di codice sarebbe come differenziare i file oggetto...

Sì, i risultati sono l'UNICA cosa che conta.Il vero compito è scrivere un framework che consenta al codice generato di essere eseguito in modo indipendente...trascorri il tuo tempo lì.

Se stai utilizzando *nux potresti prendere in considerazione l'idea di eliminare il framework unittest in favore di uno script bash o di un makefile.su Windows potresti prendere in considerazione la creazione di un'app/funzione shell che esegue il generatore e quindi utilizza il codice (come un altro processo) e lo unittest.

Una terza opzione sarebbe generare il codice e quindi creare da esso un'app che non includa nient'altro che uno unittest.Ancora una volta avresti bisogno di uno script di shell o quant'altro per eseguirlo per ogni input.Per quanto riguarda come codificare il comportamento previsto, mi viene in mente che potrebbe essere fatto più o meno nello stesso modo in cui faresti per il codice C++, semplicemente utilizzando l'interfaccia generata anziché quella C++.

Volevo solo sottolineare che è ancora possibile eseguire test a grana fine verificando i risultati:puoi testare singoli blocchi di codice nidificandoli all'interno di alcuni codici di configurazione e verifica:

int x = 0;
GENERATED_CODE
assert(x == 100);

A condizione che tu abbia il codice generato assemblato da blocchi più piccoli e che i blocchi non cambino frequentemente, puoi esercitare più condizioni e testare un po' meglio, e si spera di evitare che tutti i test si interrompano quando modifichi le specifiche di un blocco.

Il test unitario è semplicemente quello di testare un'unità specifica.Quindi, se stai scrivendo una specifica per la classe A, l'ideale è che la classe A non abbia le versioni concrete delle classi B e C.

Ok, ho notato in seguito che il tag per questa domanda include C++/Python, ma i principi sono gli stessi:

    public class A : InterfaceA 
    {   
      InterfaceB b;

      InterfaceC c;

      public A(InterfaceB b, InterfaceC c)   {
          this._b = b;
          this._c = c;   }

      public string SomeOperation(string input)   
      {
          return this._b.SomeOtherOperation(input) 
               + this._c.EvenAnotherOperation(input); 
      } 
    }

Poiché il sistema A di cui sopra inserisce interfacce nei sistemi B e C, è possibile testare l'unità solo del sistema A, senza che la funzionalità reale venga eseguita da nessun altro sistema.Questo è un test unitario.

Ecco un modo intelligente per affrontare un sistema dalla creazione al completamento, con una diversa specifica del quando per ogni comportamento:

public class When_system_A_has_some_operation_called_with_valid_input : SystemASpecification
{
    private string _actualString;

    private string _expectedString;

    private string _input;

    private string _returnB;

    private string _returnC;

    [It]
    public void Should_return_the_expected_string()
    {
        _actualString.Should().Be.EqualTo(this._expectedString);
    }

    public override void GivenThat()
    {
        var randomGenerator = new RandomGenerator();
        this._input = randomGenerator.Generate<string>();
        this._returnB = randomGenerator.Generate<string>();
        this._returnC = randomGenerator.Generate<string>();

        Dep<InterfaceB>().Stub(b => b.SomeOtherOperation(_input))
                         .Return(this._returnB);
        Dep<InterfaceC>().Stub(c => c.EvenAnotherOperation(_input))
                         .Return(this._returnC);

        this._expectedString = this._returnB + this._returnC;
    }

    public override void WhenIRun()
    {
        this._actualString = Sut.SomeOperation(this._input);
    }
}

Quindi, in conclusione, una singola unità/specifica può avere più comportamenti e la specifica cresce man mano che si sviluppa l'unità/il sistema;e se il tuo sistema in prova dipende da altri sistemi concreti al suo interno, fai attenzione.

La mia raccomandazione sarebbe quella di individuare una serie di risultati input-output noti, come alcuni casi più semplici che avete già in atto, e unit test il codice che viene prodotto.È del tutto possibile che, cambiando il generatore, la stringa esatta prodotta possa essere leggermente diversa...ma quello che ti interessa veramente è se viene interpretato allo stesso modo.Pertanto, se testi i risultati come testeresti quel codice se fosse la tua funzionalità, scoprirai se ha successo nel modo desiderato.

Fondamentalmente, quello che vuoi veramente sapere è se il tuo generatore produrrà quello che ti aspetti senza testare fisicamente ogni possibile combinazione (anche:impossibile).Assicurandoti che il tuo generatore sia coerente nel modo previsto, puoi avere la certezza che il generatore avrà successo in situazioni sempre più complesse.

In questo modo, puoi anche costruire una suite di test di regressione (test unitari che devono continuare a funzionare correttamente).Ciò ti aiuterà a garantire che le modifiche al tuo generatore non interrompano altre forme di codice.Quando riscontri un bug che i tuoi test unitari non hanno rilevato, potresti volerlo includere per evitare interruzioni simili.

Trovo che sia necessario testare ciò che stai generando più del modo in cui lo generi.

Nel mio caso, il programma genera molti tipi di codice (C#, HTML, SCSS, JS, ecc.) che vengono compilati in un'applicazione web.Il modo migliore che ho trovato per ridurre complessivamente i bug di regressione è testare l'applicazione web stessa, non testare il generatore.

Non fraintendetemi, ci sono ancora test unitari che controllano parte del codice del generatore, ma il nostro più grande successo sono stati i test dell'interfaccia utente sull'app generata stessa.

Dal momento che lo stiamo generando, generiamo anche una bella astrazione in JS che possiamo utilizzare per testare l'app in modo programmatico.Abbiamo seguito alcune idee delineate qui: http://code.tutsplus.com/articles/maintainable-automated-ui-tests--net-35089

La parte migliore è che mette davvero alla prova il tuo sistema end-to-end, dalla generazione del codice a ciò che stai effettivamente generando.Una volta che un test fallisce, è facile rintracciarlo fino al punto in cui il generatore si è rotto.

È piuttosto dolce.

Buona fortuna!

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top