Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Questa procedura dettagliata descrive come sviluppare una DLL C++ nativa usando la metodologia dei test preventivi. I passaggi di base sono i seguenti:
Creare un progetto di test nativo. Il progetto di test si trova nella stessa soluzione del progetto DLL.
Creare un progetto DLL. In questa procedura dettagliata viene creato un nuovo file DLL, ma la procedura per testare una DLL esistente è simile.
Aumentare i test iterativamente. Si consiglia un ciclo "red-green-refactor", in cui lo sviluppo di codice è condotto dai test.
Debug di test non superati. È possibile eseguire test in modalità debug.
Effettuare il refactoring mantenendo i test invariati. Il refactoring indica il miglioramento della struttura del codice senza modificarne il comportamento esterno. È possibile eseguirla per migliorare prestazioni, estendibilità o leggibilità del codice. Poiché lo scopo non è di modificare il comportamento, non si modificano i test mentre si esegue il refactoring del codice. I test consentono di verificare che non vengano inseriti bug durante il refactoring.
Controllo del code coverage. I test d'unità sono particolarmente utili quando eseguono più parti del codice. È possibile individuare quali parti del codice sono state usate dai test.
Unità isolate da risorse esterne. In genere, una DLL dipende da altri componenti del sistema che si sta sviluppando, come altre DLL, i database, o dei sottosistemi remoti. È utile verificare ogni unità in isolamento dalle relative dipendenze. I componenti esterni possono provocare un rallentamento dell'esecuzione dei test. Durante lo sviluppo, gli altri componenti potrebbero non essere completati.
Creare un progetto nativo di unit test
Nel menu File scegliere Nuovo>Progetto.
Visual Studio 2017 e versioni precedenti: espandere Modelli>installati>Visual C++>Test. Visual Studio 2019: impostare Linguaggio su C++ e digitare "test" nella casella di ricerca.
Scegliere il modello Progetto unit test nativo o scegliere un qualsiasi altro framework installato. Se si sceglie un altro modello, ad esempio Google Test o Boost.Test, i principi di base sono gli stessi, cambiano però alcuni dettagli.
In questa procedura dettagliata, il progetto di test viene denominato
NativeRooterTest
.Nel nuovo progetto, controllare unittest1.cpp
Si noti che:
Ogni test è definito tramite
TEST_METHOD(YourTestName){...}
.Non è necessario scrivere una firma della funzione formale. La firma viene creata dalla macro TEST_METHOD. La macro genera un'istanza a una funzione che restituisce un valore nullo. Viene inoltre generata una funzione statica che restituisce informazioni sul metodo di test. Queste informazioni consentono ad Esplora test di individuare il metodo.
I metodi dei test vengono raggruppati in classi usando
TEST_CLASS(YourClassName){...}
.Quando vengono eseguiti i test, viene creata un'istanza di ogni classe di test. I metodi di test vengono chiamati in un ordine non specificato. È possibile definire metodi speciali che vengono richiamati prima e dopo ogni modulo, classe, o metodo.
Verificare che i test vengano eseguiti in Esplora test:
Inserire il codice di test:
TEST_METHOD(TestMethod1) { Assert::AreEqual(1,1); }
Si noti che la classe
Assert
fornisce diversi metodi statici che è possibile usare per verificare i risultati nei metodi di test.Nel menu Test scegliere Esegui>Tutti i test.
Viene eseguita la compilazione e l'esecuzione del test.
Verrà visualizzato Esplora test.
Il test verrà visualizzato in Test superati.
Creare un progetto DLL
La procedura seguente illustra come creare un progetto DLL in Visual Studio 2019.
Creare un progetto C++ usando la Creazione guidata desktop di Windows: fare clic con il pulsante destro del mouse sul nome della soluzione in Esplora soluzioni e scegliere Aggiungi>nuovo progetto. Impostare Linguaggio su C++ e quindi digitare "windows" nella casella di ricerca. Scegliere Creazione guidata applicazione desktop di Windows dall'elenco risultati.
In questa procedura dettagliata, il progetto viene denominato
RootFinder
.Premere Crea. Nella finestra di dialogo successiva, in Tipo di applicazione scegliere DLL (libreria a collegamento dinamico) e selezionare Esporta simboli.
L'opzione Esporta simboli genera una semplice macro che è possibile usare per dichiarare i metodi esportati.
Dichiarare una funzione esportata nel file con estensione h principale:
Il dichiaratore
__declspec(dllexport)
permette ai membri public e protected della classe di essere visibili al di fuori della DLL. Per altre informazioni, vedere Using dllimport and dllexport in C++ Classes.Nel file con estensione cpp principale, aggiungere il corpo minimo della funzione:
// Find the square root of a number. double CRootFinder::SquareRoot(double v) { return 0.0; }
Accoppiare il progetto di test al progetto DLL
Aggiungere il progetto DLL ai riferimenti del progetto di test:
Fare clic con il pulsante destro del mouse sul nodo del progetto in Esplora soluzioni e scegliere Aggiungi>Riferimento.
Nella finestra di dialogo Aggiungi riferimento , selezionare il progetto DLL e scegliere Aggiungi.
Nel file principale con estensione cpp dello unit test, includere il file con estensione h del codice DLL:
#include "..\RootFinder\RootFinder.h"
Aggiungere un test di base che usa la funzione esportata:
TEST_METHOD(BasicTest) { CRootFinder rooter; Assert::AreEqual( // Expected value: 0.0, // Actual value: rooter.SquareRoot(0.0), // Tolerance: 0.01, // Message: L"Basic test failed", // Line number - used if there is no PDB file: LINE_INFO()); }
Compilare la soluzione.
Il nuovo test viene visualizzato in Esplora test.
In Esplora test scegliere Esegui tutto.
È stato installato il test e i progetti di codice, e verificato che sia possibile eseguire test che eseguono funzioni nel progetto di codice. Ora è possibile iniziare a scrivere test e codici reali.
Incrementare i test in maniera iterativa e fare in modo che siano superati
Aggiungere un nuovo test:
TEST_METHOD(RangeTest) { CRootFinder rooter; for (double v = 1e-6; v < 1e6; v = v * 3.2) { double actual = rooter.SquareRoot(v*v); Assert::AreEqual(v, actual, v/1000); } }
Suggerimento
È consigliabile non modificare i test che siano stati superati. Al contrario, aggiungere un nuovo test, aggiornare il codice in modo che il test passi e quindi aggiungere un altro test, e così via.
Quando gli utenti modificano i requisiti, disabilitare i test che non sono più corretti. Scrivere nuovi test e farli funzionare uno alla volta, nello stesso modo incrementale.
Compilare la soluzione e quindi scegliere Esegui tutto in Esplora test.
Il nuovo test non riesce.
Suggerimento
Verificare che ogni test non venga superato subito dopo averlo scritto. Questo consente di evitare il semplice errore di scrivere un test che riesce sempre.
Ottimizzare il codice della DLL in modo che il nuovo test abbia esito positivo:
#include <math.h> ... double CRootFinder::SquareRoot(double v) { double result = v; double diff = v; while (diff > result/1000) { double oldResult = result; result = result - (result*result - v)/(2*result); diff = abs (oldResult - result); } return result; }
Compilare la soluzione e quindi scegliere Esegui tutto in Esplora test.
Entrambi i test vengono superati.
Suggerimento
Sviluppare il codice aggiungendo un test alla volta. Assicurarsi che tutti i test vengano superati dopo ogni iterazione.
Eseguire il debug di un test non superato
Aggiungere un altro test:
#include <stdexcept> ... // Verify that negative inputs throw an exception. TEST_METHOD(NegativeRangeTest) { wchar_t message[200]; CRootFinder rooter; for (double v = -0.1; v > -3.0; v = v - 0.5) { try { // Should raise an exception: double result = rooter.SquareRoot(v); _swprintf(message, L"No exception for input %g", v); Assert::Fail(message, LINE_INFO()); } catch (std::out_of_range ex) { continue; // Correct exception. } catch (...) { _swprintf(message, L"Incorrect exception for %g", v); Assert::Fail(message, LINE_INFO()); } } }
Compilare la soluzione e scegliere Esegui tutto.
Aprire (o fare doppio clic) sul test non superato.
L'asserzione fallita viene evidenziata. Il messaggio di errore è visibile nel riquadro dei dettagli di Esplora test.
Per capire perché il test non riesce, scorrere la funzione:
Impostare il punto di interruzione all'inizio della funzione SquareRoot.
Dal menu di scelta rapida del test non superato, scegliere Esegui debug test selezionati.
Quando l'esecuzione si arresta in corrispondenza del punto di interruzione, eseguire il codice un'istruzione alla volta.
Inserire il codice nella funzione che si sta sviluppando:
#include <stdexcept> ... double CRootFinder::SquareRoot(double v) { // Validate parameter: if (v < 0.0) { throw std::out_of_range("Can't do square roots of negatives"); }
Tutti i test vengono ora superati.
Suggerimento
Se i singoli test non hanno dipendenze che ne impediscono l'esecuzione in qualsiasi ordine, attivare l'esecuzione parallela dei test nel menu Impostazioni sulla barra degli strumenti. Questo può ridurre notevolmente il tempo impiegato per eseguire tutti i test.
Effettuare il refactoring del codice senza modificare i test
Semplificare il calcolo centrale nella funzione SquareRoot:
// old code: // result = result - (result*result - v)/(2*result); // new code: result = (result + v/result)/2.0;
Compilare la soluzione e scegliere Esegui tutto, per assicurarsi che non sia stato introdotto un errore.
Suggerimento
Un buon set di test d'unità consente di assicurarsi che non siano stati introdotti bug con la modifica del codice.
Mantenere il refactoring separato da altre modifiche.
Passaggi successivi
Isolamento. La maggior parte delle DLL dipende da altri sottosistemi quali database ed altre DLL. Queste altre componenti vengono spesso sviluppate in parallelo. Per permettere l'esecuzione di unit test mentre gli altri componenti non sono ancora disponibili è necessario sostituirli con una simulazione.
Test di verifica della compilazione. È possibile eseguire test sul server di compilazione del team a intervalli predefiniti. Questo assicura che i bug non verranno introdotti quando il lavoro dei diversi membri del team viene integrato.
Test di controllo. È possibile lasciare che alcuni test vengano eseguiti prima che ogni membro del team esegua controllo del codice sorgente. In genere questo è un sottoinsieme del set completo dei test di verifica della compilazione.
È anche possibile lasciare al chiamante un livello minimo di code coverage.