Condividi tramite


Giorno della vita di uno sviluppatore devops: scrivere nuovo codice per una storia utente

Azure DevOps Services | Azure DevOps Server 2022 - Azure DevOps Server 2019

Visual Studio 2019 | Visual Studio 2022

Questa esercitazione illustra in che modo l'utente e il team possono ottenere il massimo vantaggio dalle versioni più recenti di controllo della versione di Team Foundation (TFVC) e Visual Studio per compilare l'app. L'esercitazione fornisce esempi di come usare Visual Studio e TFVC per controllare e aggiornare il codice, sospendere il lavoro quando si è interrotti, richiedere una revisione del codice, archiviare le modifiche ed eseguire altre attività.

Quando un team adotta Visual Studio e TFVC per gestire il codice, configura i computer server e client, crea un backlog, pianifica un'iterazione e completa altre pianificazioni necessarie per iniziare a sviluppare l'app.

Gli sviluppatori esaminano i backlog per selezionare le attività su cui lavorare. Scrivono unit test per il codice che pianificano per lo sviluppo. In genere, eseguono i test più volte in un'ora, scrivendo gradualmente test più dettagliati e quindi scrivendo il codice che li rende superati. Gli sviluppatori spesso illustrano le interfacce di codice con i colleghi che useranno il metodo che scrivono.

Gli strumenti di Visual Studio My Work and Code Review consentono agli sviluppatori di gestire il lavoro e collaborare con i colleghi.

Nota

Le funzionalità di Visual Studio My Work e Code Review sono disponibili con le edizioni seguenti:

  • Visual Studio 2022: Visual Studio Community, Visual Studio Professional e Visual Studio Enterprise
  • Visual Studio 2019: Visual Studio Professional e Visual Studio Enterprise

Esaminare gli elementi di lavoro e prepararsi per iniziare il lavoro

Il team ha concordato che, durante lo sprint corrente, si lavorerà su Valuta stato fattura, un elemento con priorità superiore nel backlog del prodotto. Si decide di iniziare con Implementare funzioni matematiche, un'attività figlio dell'elemento backlog con priorità superiore.

In Visual Studio Team Explorer, nella pagina Lavoro personale trascinare questa attività dall'elenco Elementi di lavoro disponibili nell'elenco Lavoro in corso.

Per esaminare il backlog e preparare le attività per iniziare il lavoro

Screenshot della pagina Lavoro personale.

  1. In Team Explorer, se non si è già connessi al progetto in cui si vuole lavorare, connettersi al progetto.

  2. Nella home page selezionare Lavoro personale.

  3. Nella pagina Lavoro personale trascinare l'attività dall'elenco Elementi di lavoro disponibili nella sezione Lavoro in corso.

    È anche possibile selezionare l'attività nell'elenco Elementi di lavoro disponibili e quindi selezionare Avvia.

Bozza di piano di lavoro incrementale

Il codice viene sviluppato in una serie di piccoli passaggi. Ogni passaggio in genere non richiede più di un'ora e può richiedere meno di 10 minuti. In ogni passaggio si scrive un nuovo unit test e si modifica il codice in fase di sviluppo in modo che superi il nuovo test, oltre ai test già scritti. A volte si scrive il nuovo test prima di modificare il codice e talvolta si modifica il codice prima di scrivere il test. A volte si esegue il refactoring. Ciò significa che è sufficiente migliorare il codice senza aggiungere nuovi test. Non modificare mai un test superato, a meno che non si decida di non rappresentare correttamente un requisito.

Al termine di ogni piccolo passaggio, si eseguono tutti gli unit test rilevanti per questa area del codice. Non si considera il passaggio completato fino a quando non viene superato ogni test.

Il codice non viene archiviato nel server Azure DevOps fino a quando non si completa l'intera attività.

È possibile annotare un piano approssimativo per questa sequenza di piccoli passaggi. Si sa che i dettagli e l'ordine esatti di quelli successivi probabilmente cambieranno man mano che si lavora. Ecco l'elenco iniziale dei passaggi per questa particolare attività:

  1. Creare lo stub del metodo di test, ovvero solo la firma del metodo.
  2. Soddisfare un caso tipico specifico.
  3. Testare un'ampia gamma. Assicurarsi che il codice risponda correttamente a un intervallo elevato di valori.
  4. Eccezione in caso di negativo. Gestire correttamente i parametri non corretti.
  5. Code coverage. Assicurarsi che almeno l'80% del codice venga utilizzato dagli unit test.

Alcuni sviluppatori scrivono questo tipo di piano nei commenti nel codice di test. Altri si limitano a memorizzare il loro piano. Può essere utile scrivere l'elenco di passaggi nel campo Descrizione dell'elemento di lavoro Attività. Se è necessario passare temporaneamente a un'attività più urgente, si sa dove trovare l'elenco quando si è in grado di tornare ad esso.

Creare il primo unit test

Per iniziare, creare uno unit test. Iniziare con lo unit test perché si vuole scrivere un esempio di codice che usa la nuova classe.

Si tratta del primo unit test per la libreria di classi che si sta testando, quindi si crea un nuovo progetto di unit test.

  1. Selezionare File>Nuovo progetto.
  2. Nella finestra di dialogo Crea un nuovo progetto selezionare la freccia accanto a Tutti i linguaggi e selezionare C#, selezionare la freccia accanto a Tutti i tipi di progetto e scegliere Test, quindi selezionare Progetto di test MSTest.
  3. Selezionare Avantie quindi Crea.

Screenshot dell'opzione Unit Test selezionata nella finestra di dialogo Crea un nuovo progetto.

Nell'editor di codice sostituire il contenuto di UnitTest1.cs con il codice seguente. In questa fase, è sufficiente illustrare come verrà richiamato uno dei nuovi metodi:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Fabrikam.Math.UnitTest
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        // Demonstrates how to call the method.
        public void SignatureTest()
        {
            // Create an instance:
            var math = new Fabrikam.Math.LocalMath();

            // Get a value to calculate:
            double input = 0.0;

            // Call the method:
            double actualResult = math.SquareRoot(input);

            // Use the result:
            Assert.AreEqual(0.0, actualResult);
        }
    }
}

L'esempio viene scritto in un metodo di test perché, quando si scrive il codice, si vuole che l'esempio funzioni.

Per creare un progetto e metodi unit test

In genere si crea un nuovo progetto di test per ogni progetto da testare. Se esiste già un progetto di test, è sufficiente aggiungere nuovi metodi di test e classi.

Questa esercitazione usa il framework unit test di Visual Studio, ma è anche possibile usare framework di altri provider. Esplora test funziona altrettanto bene con altri framework, a condizione di installare l'adattatore appropriato.

  1. Creare un progetto di test usando i passaggi precedenti. È possibile scegliere linguaggi come C#, F# e Visual Basic.

  2. Aggiungere i test alla classe di test fornita. Ogni unit test è un metodo.

    • Ogni unit test deve essere preceduto dall'attributo TestMethod e il metodo unit test non deve avere parametri. È possibile usare qualsiasi nome desiderato per un metodo di unit test:

      [TestMethod]
      public void SignatureTest()
      {...}
      
      <TestMethod()>
      Public Sub SignatureTest()
      ...
      End Sub
      
    • Ogni metodo di test deve chiamare un metodo della Assert classe per indicare se è stato superato o non riuscito. In genere, si verifica che i risultati previsti e effettivi di un'operazione siano uguali:

      Assert.AreEqual(expectedResult, actualResult);
      
      Assert.AreEqual(expectedResult, actualResult)
      
    • I metodi di test possono chiamare altri metodi ordinari che non hanno l'attributo TestMethod .

    • È possibile organizzare i test in più classi. Ogni classe deve essere preceduta dall'attributo TestClass .

      [TestClass]
      public class UnitTest1
      { ... }
      
      <TestClass()>
      Public Class UnitTest1
      ...
      End Class
      

Per informazioni su come scrivere unit test in C++, vedere Scrittura di unit test per C/C++ con Microsoft Unit Testing Framework per C++.

Creare uno stub per il nuovo codice

Creare quindi un progetto di libreria di classi per il nuovo codice. È ora disponibile un progetto per il codice in fase di sviluppo e un progetto per gli unit test. Aggiungere un riferimento al progetto di test al codice in fase di sviluppo.

Screenshot di Esplora soluzioni con i progetti Test e Class.

Nel nuovo progetto si aggiungono la nuova classe e una versione minima del metodo che consentirà almeno la compilazione corretta del test. Il modo più rapido per eseguire questa operazione consiste nel generare uno stub di classe e metodo dalla chiamata nel test.

public double SquareRoot(double p)
{
    throw new NotImplementedException();
}

Per generare classi e metodi dai test

Creare prima di tutto il progetto in cui si vuole aggiungere la nuova classe, a meno che non esista già.

Per generare una classe

  1. Posizionare il cursore su un esempio della classe da generare, LocalMathad esempio , e selezionare Azioni rapide e refactoring.
  2. Scegliere Genera nuovo tipo dal menu di scelta rapida.
  3. Nella finestra di dialogo Genera tipo impostare Project sul progetto di libreria di classi. In questo esempio si tratta di Fabrikam.Math.

Per generare un metodo

  1. Posizionare il cursore su una chiamata al metodo , SquareRootad esempio , e selezionare Azioni rapide e refactoring.
  2. Nel menu di scelta rapida scegliere Genera metodo 'SquareRoot'.

Eseguire il primo test

Compilare ed eseguire il test. Il risultato del test mostra un indicatore rosso Non riuscito e il test viene visualizzato sotto l'elenco di test non superati.

Screenshot di Esplora test che mostra un test non riuscito.

Apportare una semplice modifica al codice:

public double SquareRoot(double p)
{
    return 0.0;
}

Eseguire di nuovo il test e viene superato.

Screenshot di Unit Test Explorer con un test superato.

Per eseguire unit test

Per eseguire unit test:

  • Selezionare Esegui>tutti i test
  • In alternativa, se Esplora test è aperto, scegliere Esegui o Esegui tutti i test in visualizzazione.

Screenshot di Esplora test che mostra il pulsante Esegui tutto.

Se un test viene visualizzato in Test non superati, aprire il test, ad esempio facendo doppio clic sul nome. Il punto in cui il test non è riuscito viene visualizzato nell'editor di codice.

  • Per visualizzare un elenco completo dei test, scegliere Mostra tutto.

  • Per visualizzare i dettagli di un risultato di test, selezionare il test in Esplora test.

  • Per passare al codice di un test, fare doppio clic sul test in Esplora test oppure scegliere Apri test dal menu di scelta rapida.

  • Per eseguire il debug di un test, aprire il menu di scelta rapida per uno o più test e quindi scegliere Debug.

  • Per eseguire test in background ogni volta che si compila la soluzione, selezionare la freccia accanto all'icona Impostazioni e quindi selezionare Esegui test dopo la compilazione. I test che in precedenza non sono riusciti vengono eseguiti per primi.

Accettare l'interfaccia

È possibile collaborare con i colleghi che useranno il componente condividendo lo schermo. Un collega potrebbe commentare che molte funzioni supererebbero il test precedente. Spiegare che questo test era solo per assicurarsi che il nome e i parametri della funzione siano corretti e ora è possibile scrivere un test che acquisisce il requisito principale di questa funzione.

Collaborare con i colleghi per scrivere il test seguente:

[TestMethod]
public void QuickNonZero()
{
    // Create an instance to test:
    LocalMath math = new LocalMath();

    // Create a test input and expected value:
    var expectedResult = 4.0;
    var inputValue = expectedResult * expectedResult;

    // Run the method:
    var actualResult = math.SquareRoot(inputValue);

    // Validate the result:
    var allowableError = expectedResult/1e6;
    Assert.AreEqual(expectedResult, actualResult, allowableError,
        "{0} is not within {1} of {2}", actualResult, allowableError, expectedResult);
}

Suggerimento

Per questa funzione si usa il primo sviluppo di test, in cui si scrive prima lo unit test per una funzionalità e quindi si scrive codice che soddisfa il test. In altri casi, questa procedura non è realistica, quindi si scrivono i test dopo aver scritto il codice. È tuttavia molto importante scrivere unit test, sia prima che dopo il codice, perché mantengono stabile il codice.

Rosso, Verde, Refactoring...

Seguire un ciclo in cui si scrive ripetutamente un test e verificare che ha esito negativo, scrivere codice per effettuare il superamento del test e quindi prendere in considerazione il refactoring, migliorando il codice senza modificare i test.

Rosso

Eseguire tutti i test, incluso il nuovo test creato. Dopo aver scritto qualsiasi test, eseguirlo sempre per assicurarsi che abbia esito negativo prima di scrivere il codice che lo rende superato. Se, ad esempio, si dimentica di inserire asserzioni in alcuni test scritti, il risultato fail offre la certezza che quando si effettua il superamento, il risultato del test indica correttamente che un requisito è stato soddisfatto.

Un'altra pratica utile consiste nell'impostare Esegui test dopo la compilazione. Questa opzione esegue i test in background ogni volta che si compila la soluzione, in modo da avere un report continuo dello stato del test del codice. Potrebbe essere necessario preoccuparsi che questa procedura potrebbe rallentare la risposta di Visual Studio, ma questo accade raramente.

Screenshot di Esplora test con un test non riuscito.

Verde

Scrive il primo tentativo al codice del metodo che si sta sviluppando:

public class LocalMath
{
    public double SquareRoot(double x)
    {
        double estimate = x;
        double previousEstimate = -x;
        while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000)
        {
            previousEstimate = estimate;
            estimate = (estimate * estimate - x) / (2 * estimate);
        }
        return estimate;
    }

Eseguire di nuovo i test e tutti i test vengono superati.

Screenshot di Esplora unit test con due test superati.

Refactoring

Ora che il codice esegue la funzione principale, esaminare il codice per trovare modi per migliorarne le prestazioni o per semplificare la modifica in futuro. È possibile ridurre il numero di calcoli eseguiti nel ciclo:

public class LocalMath
{
    public double SquareRoot(double x)
    {
        double estimate = x;
        double previousEstimate = -x;
        while (System.Math.Abs(estimate - previousEstimate) > estimate / 1000)
        {
            previousEstimate = estimate; 
            estimate = (estimate + x / estimate) / 2;
            //was: estimate = (estimate * estimate - x) / (2 * estimate);
        }
        return estimate;
    }

Verificare che i test siano ancora superati.

Suggerimenti

  • Ogni modifica apportata durante lo sviluppo del codice deve essere un refactoring o un'estensione:

    • Il refactoring significa che non si modificano i test perché non si aggiungono nuove funzionalità.
    • L'estensione significa aggiungere test e apportare le modifiche al codice necessarie per superare sia i test esistenti che i nuovi test.
  • Se si aggiorna il codice esistente ai requisiti modificati, si eliminano anche i vecchi test che non rappresentano più i requisiti correnti.

  • Evitare di modificare i test già superati. Piuttosto, aggiungere nuovi test. Scrivere solo test che rappresentano un requisito reale.

  • Eseguire i test dopo ogni modifica.

... e ripetere

Continuare la serie di passaggi di estensione e refactoring, usando l'elenco di piccoli passaggi come guida approssimativa. Non si esegue sempre un passaggio di refactoring dopo ogni estensione e a volte si esegue più di un passaggio di refactoring in successione. Tuttavia, gli unit test vengono sempre eseguiti dopo ogni modifica al codice.

A volte si aggiunge un test che non richiede alcuna modifica al codice, ma che aggiunge alla certezza che il codice funzioni correttamente. Ad esempio, si vuole assicurarsi che la funzione funzioni su un'ampia gamma di input. Si scrivono altri test, ad esempio questo:

[TestMethod]
public void SqRtValueRange()
{
    LocalMath math = new LocalMath();
    for (double expectedResult = 1e-8;
        expectedResult < 1e+8;
        expectedResult = expectedResult * 3.2)
    {
        VerifyOneRootValue(math, expectedResult);
    }
}
private void VerifyOneRootValue(LocalMath math, double expectedResult)
{
    double input = expectedResult * expectedResult;
    double actualResult = math.SquareRoot(input);
    Assert.AreEqual(expectedResult, actualResult, expectedResult / 1e6);
}

Questo test supera la prima volta che viene eseguita.

Screenshot di Esplora test con tre test superati.

Solo per assicurarsi che questo risultato non sia un errore, è possibile introdurre temporaneamente un piccolo errore nel test per renderlo negativo. Dopo aver visualizzato l'errore, è possibile correggerlo di nuovo.

Suggerimento

Eseguire sempre un test non superato prima di passarlo.

Eccezioni

Passare ora alla scrittura di test per input eccezionali:

[TestMethod]
public void RootTestNegativeInput()
{
    LocalMath math = new LocalMath();
    try
    {
        math.SquareRoot(-10.0);
    }
    catch (ArgumentOutOfRangeException)
    {
        return;
    }
    catch
    {
        Assert.Fail("Wrong exception on negative input");
        return;
    }
    Assert.Fail("No exception on negative input");
}

Questo test inserisce il codice in un ciclo. È necessario usare il pulsante Annulla in Esplora test. In questo modo il codice viene terminato entro 10 secondi.

Si vuole assicurarsi che non sia stato possibile eseguire un ciclo infinito nel server di compilazione. Anche se il server impone un timeout per un'esecuzione completa, si tratta di un timeout molto lungo e causerebbe un notevole ritardo. È quindi possibile aggiungere un timeout esplicito a questo test:

[TestMethod, Timeout(1000)]
public void RootTestNegativeInput()
{...

Il timeout esplicito fa sì che il test abbia esito negativo.

Aggiornare il codice per gestire questo caso eccezionale:

public double SquareRoot(double x)
{
    if (x <= 0.0) 
    {
        throw new ArgumentOutOfRangeException();
    }

Regressione

Il nuovo test viene superato, ma esiste una regressione. Un test usato per superare ora ha esito negativo:

Screenshot di Unit Test non superato in precedenza.

Trovare e correggere l'errore:

public double SquareRoot(double x)
{
    if (x < 0.0)  // not <=
    {
        throw new ArgumentOutOfRangeException();
    }

Dopo aver risolto il problema, tutti i test superano:

Screenshot di Unit Test Explorer con quattro test superati.

Suggerimento

Assicurarsi che ogni test venga superato dopo ogni modifica apportata al codice.

Code coverage

A intervalli durante il lavoro e infine prima di archiviare il codice, ottenere un report di code coverage. Ciò mostra la quantità di codice esercitata dai test.

Il team ha lo scopo di copertura di almeno l'80%. Riduce questo requisito per il codice generato, perché può essere difficile ottenere una copertura elevata per questo tipo di codice.

Una buona copertura non garantisce che la funzionalità completa del componente sia stata testata e non garantisce che il codice funzioni per ogni intervallo di valori di input. Tuttavia, esiste una correlazione abbastanza stretta tra la copertura delle righe di codice e la copertura dello spazio comportamentale di un componente. Pertanto, una buona copertura rafforza la fiducia del team che sta testando la maggior parte del comportamento che dovrebbero.

Per ottenere un report di code coverage, nel menu Test di Visual Studio selezionare Analizza code coverage per tutti i test. Tutti i test vengono eseguiti di nuovo.

Screenshot del risultato code coverage e del pulsante Mostra colore.

Quando si espande il totale nel report, viene mostrato che il codice in fase di sviluppo ha una copertura completa. Questo è molto soddisfacente, perché il punteggio importante è per il codice sottoposto a test. Le sezioni scoperte sono in realtà nei test stessi.

Attivando o disattivando il pulsante Mostra colorazione code coverage, è possibile vedere quali parti del codice di test non sono state esercitate. Il codice non usato nei test è evidenziato in arancione. Tuttavia, queste sezioni non sono importanti per la copertura perché si trovano nel codice di test e vengono usate solo se viene rilevato un errore.

Per verificare che un test specifico raggiunga rami specifici del codice, è possibile impostare Mostra colorazione code coverage e quindi eseguire il singolo test usando il comando Esegui nel menu di scelta rapida.

Quando hai finito?

Si continua ad aggiornare il codice in piccoli passaggi fino a quando non si è soddisfatti di quanto segue:

  • Tutti gli unit test disponibili vengono superati.

    In un progetto con un set molto elevato di unit test, può essere poco pratico che uno sviluppatore attenda l'esecuzione di tutti. Il progetto gestisce invece un servizio di archiviazione controllato, in cui vengono eseguiti tutti i test automatizzati per ogni set di scaffali archiviato prima che venga unito all'albero di origine. L'archiviazione viene rifiutata se l'esecuzione non riesce. Ciò consente agli sviluppatori di eseguire un set minimo di unit test nei propri computer e quindi procedere con altre operazioni, senza correre il rischio di interrompere la compilazione. Per altre informazioni, vedere Usare un processo di compilazione di archiviazione controllato per convalidare le modifiche.

  • Code coverage soddisfa lo standard del team. Il 75% è un requisito tipico del progetto.

  • Gli unit test simulano ogni aspetto del comportamento necessario, inclusi gli input tipici ed eccezionali.

  • Il codice è facile da comprendere ed estendere.

Quando vengono soddisfatti tutti questi criteri, è possibile controllare il codice nel controllo del codice sorgente.

Principi dello sviluppo di codice con unit test

Applicare i principi seguenti durante lo sviluppo di codice:

  • Sviluppare unit test insieme al codice ed eseguirli frequentemente durante lo sviluppo. Gli unit test rappresentano la specifica del componente.
  • Non modificare gli unit test, a meno che i requisiti non siano stati modificati o che i test non siano corretti. Aggiungere nuovi test gradualmente man mano che si estende la funzionalità del codice.
  • Mirare ad almeno il 75% del codice per essere coperto dai test. Esaminare i risultati del code coverage a intervalli e prima di archiviare il codice sorgente.
  • Archiviare gli unit test insieme al codice, in modo che vengano eseguiti dalle compilazioni server continue o regolari.
  • Dove pratico, per ogni parte di funzionalità, scrivere prima lo unit test. Eseguire questa operazione prima di sviluppare il codice che lo soddisfa.

Controllare le modifiche

Prima di controllare le modifiche, condividere di nuovo lo schermo con i colleghi in modo che possano esaminare in modo informale e interattivo con te ciò che hai creato. I test continuano a essere l'obiettivo della discussione con i colleghi che sono interessati principalmente a ciò che il codice fa, non a come funziona. Questi colleghi devono accettare che ciò che hai scritto soddisfi le loro esigenze.

Controllare tutte le modifiche apportate, inclusi i test e il codice, e associarle alle attività completate. L'archiviazione accoda il sistema di compilazione automatizzato del team per convalidare le modifiche usando il processo di compilazione della compilazione CI del team. Questo processo di compilazione consente al team di ridurre al minimo gli errori nella codebase creando e testando, in un ambiente pulito separato dai computer di sviluppo, ogni modifica apportata dal team.

Si riceve una notifica al termine della compilazione. Nella finestra dei risultati della compilazione si noterà che la compilazione è riuscita e tutti i test superati.

Per archiviare le modifiche

  1. Nella pagina Lavoro personale in Team Explorer selezionare Archivia.

    Screenshot dell'archiviazione da My Work.

  2. Nella pagina Modifiche in sospeso verificare che:

    • Tutte le modifiche rilevanti sono elencate in Modifiche incluse.
    • Tutti gli elementi di lavoro pertinenti sono elencati in Elementi di lavoro correlati.
  3. Immettere un commento per aiutare il team a comprendere lo scopo di queste modifiche quando esaminano la cronologia del controllo della versione dei file e delle cartelle modificati.

  4. Scegliere Archiviazione.

    Screenshot del controllo delle modifiche in sospeso.

Per integrare continuamente il codice

Per altre informazioni su come definire un processo di compilazione di integrazione continua, vedere Configurare una compilazione CI. Dopo aver configurato questo processo di compilazione, è possibile scegliere di ricevere una notifica sui risultati delle compilazioni dei team.

Screenshot della pagina Compilazioni personali con una compilazione completata.

Per altre informazioni, vedere Eseguire, monitorare e gestire le compilazioni.

Passaggi successivi