Unit test di una DLL in Visual C++ in app di Windows Store
Questo argomento descrive come creare unit test per una DLL in C++ per app di Windows Store utilizzando Visual Studio 2012 Express per Windows 8 e il framework per unit test di Microsoft per C++. La DLL RooterLib rammenta vagamente la teoria dei limiti di calcolo implementando una funzione che calcola una stima della radice quadrata di un numero specificato. La DLL può quindi essere inclusa in un'app di Windows Store che mostra a un utente le varie operazioni che si possono eseguire con questa funzione matematica.
Nota
Negli argomenti di questa sezione viene descritta la funzionalità di Visual Studio 2012 Express per Windows 8. Visual Studio Ultimate, VS Premium e VS Professional includono funzionalità aggiuntive per gli unit test.
-
Con VS Ultimate, VS Premium e VS Professional puoi utilizzare un framework di unit test open source o di terze parti qualsiasi che abbia creato un adattatore di componente aggiuntivo per Esplora test di Microsoft. Puoi inoltre analizzare e visualizzare le informazioni di code coverage per i test.
-
In VS Ultimate e VS Premium puoi eseguire i test dopo ogni compilazione.
Per ulteriori informazioni, vedi Verifica del codice tramite unit test in MSDN Library.
In questo argomento viene illustrato come utilizzare unit test come primo passaggio dell'attività di sviluppo. Secondo questo approccio devi innanzitutto scrivere un metodo di test che verifica il comportamento specifico del sistema che stai testando, quindi scriverai il codice che supera il test. Apportando modifiche nell'ordine in cui sono presentate le procedure riportate di seguito, puoi invertire questa strategia scrivendo per primo il codice che vuoi testare, quindi gli unit test.
In questo argomento si creerà inoltre una soluzione di Visual Studio e progetti distinti per gli unit test e la DLL da testare. Puoi anche includere gli unit test direttamente nel progetto DLL oppure creare soluzioni separate per gli unit test e la DLL. Per suggerimenti sulla struttura da utilizzare, vedi Unit test delle applicazioni C++ esistenti con Esplora test.
In questo argomento
Questo argomento descrive le seguenti attività:
Creare la soluzione e il progetto unit test
Verificare che i test siano eseguiti in Esplora test
Aggiungere il progetto di DLL alla soluzione
Abbinare il progetto di test al progetto di DLL
Incrementare i test in maniera iterativa e fare in modo che siano superati
Eseguire il debug di un test non superato
Eseguire il refactoring del codice senza modificare i test
Creare la soluzione e il progetto unit test
Scegli Nuovo dal menu File, quindi Nuovo progetto.
Nella finestra di dialogo Nuovo progetto espandi Installato, quindi Visual C++ e scegli Windows Store. Scegli quindi Libreria unit test (applicazioni Windows Store) dall'elenco di modelli di progetto.
Assegna al progetto il nome RooterLibTests, specifica il percorso, assegna alla soluzione il nome RooterLib e assicurati che l'opzione Crea directory per soluzione sia selezionata.
Nel nuovo progetto apri unittest1.cpp.
Come puoi notare:
Ogni test viene definito tramite TEST_METHOD(YourTestName){...}.
Non è necessario scrivere una firma della funzione convenzionale. La firma viene creata dalla macro TEST_METHOD. La macro genera una funzione di istanza che restituisce void. Genera inoltre una funzione statica che restituisce informazioni sul metodo di test. Queste informazioni consentono a Esplora test di individuare il metodo.
I metodi di test sono raggruppati in classi utilizzando TEST_CLASS(YourClassName){...}.
Quando si eseguono i test, viene creata un'istanza di ogni classe di test. I metodi di test vengono chiamati in un ordine non specificato. Puoi definire metodi speciali che vengono richiamati prima e dopo ogni modulo, classe o metodo. Per ulteriori informazioni, vedi Utilizzo di Microsoft.VisualStudio.TestTools.CppUnitTestFramework in MSDN Library.
Verificare che i test siano eseguiti in Esplora test
Inserisci il codice di test:
TEST_METHOD(TestMethod1) { Assert::AreEqual(1,1); }
Nota che la classe Assert fornisce diversi metodi statici che puoi utilizzare per verificare i risultati nei metodi di test.
Scegli Esegui dal menu Test, quindi Esegui tutto.
Il progetto di test viene compilato ed eseguito. Compare la finestra di Esplora test con il test elencato in Test superati. Nel riquadro di riepilogo nella parte inferiore della finestra sono disponibili ulteriori dettagli sul test selezionato.
Aggiungere il progetto di DLL alla soluzione
Scegli il nome della soluzione in Esplora soluzioni. Dal menu di scelta rapida scegli Aggiungi, quindi Aggiungi nuovo progetto.
Nella finestra di dialogo Aggiungi nuovo progetto scegli DLL (app di Windows Store).
Aggiungere il codice seguente al file RooterLib.h:
// The following ifdef block is the standard way of creating macros which make exporting // from a DLL simpler. All files within this DLL are compiled with the ROOTERLIB_EXPORTS // symbol defined on the command line. This symbol should not be defined on any project // that uses this DLL. This way any other project whose source files include this file see // ROOTERLIB_API functions as being imported from a DLL, whereas this DLL sees symbols // defined with this macro as being exported. #ifdef ROOTERLIB_EXPORTS #define ROOTERLIB_API __declspec(dllexport) #else #define ROOTERLIB_API __declspec(dllimport) #endif //ROOTERLIB_EXPORTS class ROOTERLIB_API CRooterLib { public: CRooterLib(void); double SquareRoot(double v); };
I commenti spiegano il blocco ifdef non solo allo sviluppatore della DLL, ma a chiunque faccia riferimento alla DLL nel progetto. Puoi aggiungere il simbolo di ROOTERLIB_EXPORTS alla riga di comando utilizzando le proprietà di progetto della DLL.
La classe CRooterLib dichiara un costruttore e il metodo estimativo SqareRoot.
Aggiungi il simbolo di ROOTERLIB_EXPORTS alla riga di comando.
In Esplora soluzioni scegli il progetto RooterLib, quindi scegli Proprietà dal menu di scelta rapida.
Nella finestra di dialogo della pagina delle proprietà di RooterLib espandi Proprietà di configurazione, quindi C++ e scegliere Preprocessore.
Scegli <Modifica> dall'elenco Definizioni preprocessore quindi aggiungi ROOTERLIB_EXPORTS nella finestra di dialogo Definizioni preprocessore.
Aggiungi implementazioni minime delle funzioni dichiarate. Apri RooterLib.cpp e aggiungi il seguente codice:
// constructor CRooterLib::CRooterLib() { } // Find the square root of a number. double CRooterLib::SquareRoot(double v) { return 0.0; }
Abbinare il progetto di test al progetto di DLL
Aggiungi RooterLib al progetto RooterLibTests.
In Esplora soluzioni scegli il progetto RooterLibTests, quindi scegli Riferimenti dal menu di scelta rapida.
Nella finestra di dialogo delle proprietà del progetto RooterLib espandi Proprietà comuni e scegli Framework e riferimenti.
Scegli Aggiungi nuovo riferimento.
Nella finestra di dialogo Aggiungi riferimento espandi Soluzione, quindi scegli Progetti. Seleziona quindi l'elemento RouterLib.
Includi il file di intestazione RooterLib in unittest1.cpp.
Apri unittest1.cpp.
Aggiungi questo codice sotto la riga #include "CppUnitTest.h":
#include "..\RooterLib\RooterLib.h"
Aggiungi un test che utilizza la funzione importata. Aggiungi il seguente codice a unittest1.cpp.
TEST_METHOD(BasicTest) { CRooterLib 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()); }
Compila la soluzione.
Il nuovo test viene visualizzato in Esplora test nel nodo Test non eseguiti.
In Esplora test scegli Esegui tutto.
A questo punto hai configurato il test e i progetti di codice. Hai inoltre verificato di poter eseguire i test che eseguono funzioni nel progetto di codice. Ora puoi iniziare a scrivere test e codice reali.
Incrementare i test in maniera iterativa e fare in modo che siano superati
Aggiungi un nuovo test:
TEST_METHOD(RangeTest) { CRooterLib rooter; for (double v = 1e-6; v < 1e6; v = v * 3.2) { double expected = v; double actual = rooter.SquareRoot(v*v); double tolerance = expected/1000; Assert::AreEqual(expected, actual, tolerance); } };
Suggerimento
Ti suggeriamo di non modificare i test superati. Aggiungi invece un nuovo test, aggiorna il codice in modo che il test venga superato, quindi aggiungi un altro test e così via.
Se gli utenti modificano i requisiti, disabilita i test che non risultano più corretti. Scrivi nuovi test e verificane il funzionamento, uno alla volta, sempre in modo incrementale.
In Esplora test scegli Esegui tutto.
Il test ha esito negativo.
Suggerimento
Verifica che dopo che l'hai scritto, ogni test abbia immediatamente esito negativo. In questo modo eviti l'errore comune di scrivere un test che non ha mai esito negativo.
Modifica il codice sottoposto a test in modo che il nuovo test venga superato. Aggiungere quando segue a RooterLib.cpp:
#include <math.h> ... // Find the square root of a number. double CRooterLib::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; }
Compila la soluzione, quindi in Esplora test scegli Esegui tutto.
Entrambi i test vengono superati.
Suggerimento
Sviluppa il codice aggiungendo i test uno alla volta. Assicurati che tutti i test vengano superati dopo ogni iterazione.
Eseguire il debug di un test non superato
Aggiungi un altro test a unittest1.cpp:
// Verify that negative inputs throw an exception. TEST_METHOD(NegativeRangeTest) { wchar_t message[200]; CRooterLib rooter; for (double v = -0.1; v > -3.0; v = v - 0.5) { try { // Should raise an exception: double result = rooter.SquareRoot(v); swprintf_s(message, L"No exception for input %g", v); Assert::Fail(message, LINE_INFO()); } catch (std::out_of_range ex) { continue; // Correct exception. } catch (...) { swprintf_s(message, L"Incorrect exception for %g", v); Assert::Fail(message, LINE_INFO()); } } };
In Esplora test scegli Esegui tutto.
Il test ha esito negativo. Scegli il nome del test in Esplora test. L'asserzione non riuscita è evidenziata. Il messaggio di errore è visibile nel riquadro dei dettagli di Esplora test.
Per verificare il motivo per cui il test non riesce, esegui la funzione un'istruzione alla volta:
Imposta un punto di interruzione all'inizio della funzione SquareRoot.
Dal menu di scelta rapida del test non superato scegli Esegui debug test selezionati.
Quando l'esecuzione si ferma in corrispondenza del punto di interruzione, esegui il codice un'istruzione alla volta.
Aggiungi il codice a RooterLib.cpp per intercettare l'eccezione:
#include <stdexcept> ... double CRooterLib::SquareRoot(double v) { //Validate the input parameter: if (v < 0.0) { throw std::out_of_range("Can't do square roots of negatives"); } ...
In Esplora test scegli Esegui tutto per testare il metodo corretto e assicurarti di non aver introdotto una regressione.
Ora tutti i test vengono superati.
Eseguire 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;
Scegli Esegui tutto per testare il metodo di cui è stato eseguito il refactoring e assicurarti di non aver introdotto una regressione.
Suggerimento
Un set stabile di unit test corretti indica con sufficiente sicurezza che non sono stati introdotti bug in fase di modifica del codice.
Mantenere il refactoring separato da altre modifiche.