blexin

Consulenza
Servizi e
Formazione it


Blog

Evolvi la tua azienda

Come passare dalla descrizione dei requisiti ai test in .NET con BDD e SpecFlow

Dai requisiti al testing con SpecFlow

Introduzione

Negli ultimi mesi mi è capitato di approfondire l'utilizzo di framework e metodologie per il testing del codice. In particolare, sto utilizzando due metodologie di scrittura del codice che mi stanno incuriosendo parecchio, una è il TDD (Test Driven Development) l'altra è il BDD (Behaviour Driven Development). In questo articolo vi parlerò di un framework con il quale poter fare facilmente BDD che si chiama SpecFlow.

SpecFlow

SpecFlow è un framework di testing che supporta BDD. Permette la definizione di test basati sul comportamento che la nostra feature dovrebbe avere, utilizzando una grammatica semplice tramite un linguaggio chiamato Gherkin. Per fare ciò utilizza un tipo di file con estensione feature file, di cui riporto un piccolo esempio:

Feature: Batmobile Weapons
    In order fight villains
    As Batman
    I want to fire weapons from Batmobile.

Scenario: Fire Cannon successfully
    Given I see villain
    When I press fire cannon button
    Then Batmobile cannons fire

Nel feature file prima si descrive a cosa servirà la mia funzionalità da testare ed in seguito si specificano i diversi scenari (use case in forma given-when-then) su cui voglio testarla. Capite bene che questo modo di scrivere test non solo permette di testare il codice scritto ma permette anche di documentare con precisione l'ambito della nostra funzionalità. Andiamo ad utilizzare SpecFlow con un esempio all'interno di Visual Studio.

Installazione e setup

L'installazione di SpecFlow consiste in due passaggi: 1) Installare l'estensione per Visual Studio 2017; 2) Creare un progetto e configurarlo per l'uso di SpecFlow.

Per installare l'estensione, andiamo nel menu Strumenti / Tools e selezioniamo Estensioni ed aggiornamenti

Estensione SpecFlow

Creiamo ora un nuovo progetto di tipo MSTest Test Project

Nuovo progetto SpecFlow

Una volta creato il progetto, tasto destro sulla solution e scegliamo Gestisci pacchetti NuGet per la Soluzione, e dalla finestra che si apre installiamo i seguenti pacchetti: 1) SpecFlow 2) SpecFlow.Tools.MsBuild.Generation 3) SpecRun.SpecFlow

Perfetto, siamo pronti per creare il nostro primo feature file!

Aggiungere un feature file

Abbiamo adesso bisogno di aggiungere al nostro progetto un feature file che definirà la specifica funzionalità e includerà gli scenari di test. Aggiungere un feature file al progetto è estremamente semplice, tasto destro sul nostro progetto, "Aggiungi" e poi "Nuovo Elemento" ed abbiamo sulla sinistra la possibilità di scelta dei template di SpecFlow, da cui andiamo a scegliere SpecFlow Feature File

Nuovo feature file

Bisogna definire adesso la nostra funzionalità e i nostri scenari, riportando un esempio che ho dovuto realizzare cercando di semplificarlo al massimo. Immaginiamo un progetto in cui si debbano importare dei file csv e che preveda una fonte esterna. Supponiamo che dobbiate verificare se dopo l'importazione i dati importati siano coerenti con il file di partenza. Ecco il feature file:

Feature: Items
    Import files from external application

Scenario: Import a valid Items file
    Given the following specific Items file
    """
    ItemNo;CreatedOn;ModifiedOn;Disabled
    10045112022051066;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051064;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051062;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051070;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051071;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051072;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051073;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051074;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    10045112022051075;"4/20/2017 10:32 AM";"4/21/2017 10:32 AM";No
    """
    When the service is started
    Then the following records must be present in Items table
    | ItemNo            | CreatedOn                 | ModifiedOn                | Disabled | 
    | 10045112022051066 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051064 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051062 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051070 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051071 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051072 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051073 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051074 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 
    | 10045112022051075 | '2017-04-20 10:32:00.000' | '2017-04-21 10:32:00.000' | 0        | 

In questo file la nostra feature Items non fa altro che importare files di input da un applicazione esterna. Il nostro scenario invece specifica che:

  • Given: quando abbiamo una serie di valori di input con una specifica struttura all'interno del CSV stesso (ItemNo;CreatedOn;ModifiedOn;Disabled);
  • When: ci aspettiamo all'avvio del servizio;
  • Then: i valori esplicitati nella table Items che corrisponderanno a quelli presenti nei file CSV di partenza.

Tutto bello, ma come facciamo adesso a scrivere il codice che deve eseguire questo test? Anche qui un passaggio molto semplice, tasto destro all'interno del nostro feature file e dal menu contestuale scegliamo Generating step definitions, inseriamo un nome per la classe di binding che stiamo per creare e date un click su "generate". A questo punto, SpecFlow ha creato per noi una classe che dovrebbe avere questa struttura:

    [Binding]
    public class ItemsSteps
    {
        [Given(@"the following specific Items file")]
        public void GivenTheFollowingSpecificItemsFile(string multilineText)
        {
            ScenarioContext.Current.Pending();
        }

        [When(@"the service is started")]
        public void WhenTheServiceIsStarted()
        {
            ScenarioContext.Current.Pending();
        }

        [Then(@"the following records must be present in Items table")]
        public void ThenTheFollowingRecordsMustBePresentInItemsTable(Table table)
        {
            ScenarioContext.Current.Pending();
        }
    }

Come vediamo, ogni passo del feature file adesso è bindato su un metodo su cui implementare quello che ci serve per l'esecuzione del test. Siamo pronti adesso per la prima esecuzione del test! Compiliamo la nostra soluzione, dal menu selezioniamo Test -> Finestre -> Esplora Test e clicchiamo su Esegui Tutti. Se l'esecuzione dei test andrà a buon fine avremo nel Test Explorer un pallino verde su tutti i test! Inseriamo ora un po' di codice all'interno della nostra classe di Binding.

    [Binding]
    public class ItemsSteps
    {
        List<string> itemList = new List<string>();

        [Given(@"the following specific Items file")]
        public void GivenTheFollowingSpecificItemsFile(string multilineText)
        {
            File.WriteAllText(
                Path.Combine(@"c:\temp", $"TEST_items.csv"),
                multilineText);
        }

        [When(@"the service is started")]
        public void WhenTheServiceIsStarted()
        {
            MyService service = new MyService();
            service.Start();
        }

        [Then(@"the following records must be present in Items table")]
        public void ThenTheFollowingRecordsMustBePresentInItemsTable(Table table)
        {
            foreach(var item in table.Rows)
            {
                Assert.IsTrue(FakeDatabase.ItemTable.Any(i => i.ItemNo == item[0]));
            }
        }

        [AfterTestRun]
        public static void AfterTestRun()
        {
            File.Delete(@"c:\temp\TEST_items.csv");
        }
    }

Ricapitolando:

  1. nel metodo GivenTheFollowingSpecificItemsFile creiamo il nostro CSV con le informazioni contenute nel feature file
  2. nel metodo WhenTheServiceIsStarted avviamo il nostro servizio, che non fa altro che leggere dal CSV gli item e salvarli da qualche parte
  3. nel metodo ThenTheFollowingRecordsMustBePresentInItemsTable ci assicuriamo che le righe inserite siano coerenti con la table presente nel feature file

Abbiamo aggiunto il metodo AfterTestRun per eseguire il clean-up dei test ed eliminare il file CSV creato durante l'esecuzione degli stessi. Nelle successive esecuzioni, verifichiamo nuovamente che i test saranno eseguite sempre correttamente.

Spero di avervi trasmesso un po' della mia curiosità per questo framework, e a questo link potete trovare il codice di esempio.

Al prossimo articolo!

Servizi

Evolvi la tua azienda