Mocking di un parser di file

1

Ho un parser di file e il mio manager mi ha detto che ho bisogno di creare test unitari per questo. Ecco il mio codice:

public class ParsedDetails
{
    public int Id { get; set; }
    public Guid Guid { get; set; }
    public bool IsValid { get; set; }
}

public class CsvParser : IParser
{
    private const int CsvColumns = 2;
    private const char CsvSeparator = ';';
    private const int IdColumnIndex = 0;
    private const int GuidColumnIndex = 1;


    private ParsedDetails ParseLine(string line)
    {
        ParsedCardDetails result = new ParsedCardDetails();
        //validating and parsing data

        return result;
    }

    public IEnumerable<ParsedCardDetails> Parse(string path)
    {
        if (string.IsNullOrWhiteSpace(path))
        {
            throw new ArgumentException("The path must not be empty");
        }

        if (!File.Exists(path))
        {
            throw new FileNotFoundException();
        }

        List<ParsedCardDetails> result = new List<ParsedCardDetails>();

        var data = File.ReadAllLines(path);

        foreach (var item in data)
        {
            var validationResult = ParseLine(item);
            result.Add(validationResult);
        }

        return result;
    }
}

Quindi, l'unica cosa che mi è passata per la mente è stato scrivere test unitari al metodo ParseLine , ma questo è privato e non ho altre ragioni per renderlo pubblico e non c'è bisogno che io simuli la classe del parser . Hai qualche idea su come procedere?

Questa è la mia prima volta che scrivo i test delle unità.

    
posta Buda Gavril 19.10.2016 - 13:55
fonte

2 risposte

6

Per testare il codice, devi scrivere codice verificabile. Così com'è, la tua classe non è facile da testare, quindi ha bisogno di un ripensamento.

La cosa fondamentale da affrontare in questo caso è che si sta violando il principio di responsabilità singola, dato che la classe è responsabile della convalida del percorso di un file, del caricamento del contenuto di quel file e quindi dell'analisi in un nuovo modulo. Quindi spezzare la cosa in due:

public interface IParseableDataProvider
{
    IEnumerable<string> GetDataToParse();
}

internal class FileContentsDataProvider : IParseableDataProvider
{
    private readonly string _filePath;
    internal FileContentsDataProvider(string filePath)
    {
        _filePath = filePath;
    }

    public IEnumerable<string> GetDataToParse()
    {
        if (string.IsNullOrWhiteSpace(path))
        {
            throw new ArgumentException("The path must not be empty");
        }

        if (!File.Exists(path))
        {
            throw new FileNotFoundException();
        }

        return File.ReadAllLines(path);
    }
}


public class CsvParser : IParser
{
    private const int CsvColumns = 2;
    private const char CsvSeparator = ';';
    private const int IdColumnIndex = 0;
    private const int GuidColumnIndex = 1;


    private ParsedDetails ParseLine(string line)
    {
        ParsedCardDetails result = new ParsedCardDetails();
        //validating and parsing data

        return result;
    }

    public IEnumerable<ParsedDetails> ParseData(IParseableDataProvider dataProvider) =>
        dataProvider.Select(line => ParseLine(line));
}

Ora puoi unità test CsvParser utilizzando un'implementazione IParseableDataProvider che non tocca il file system ed è possibile eseguire un test di integrazione utilizzando FileContentsDataProvider con CsvParser .

    
risposta data 19.10.2016 - 14:21
fonte
5

Il metodo Parse sta facendo 2 cose, che è generalmente considerato non buono

  1. Caricamento del file dal disco
  2. Analisi del file caricato.

La classe CsvParser e tutti gli altri parser che verranno dopo di essa dovrebbero essere responsabili di 1 cosa: analizzare il testo. Parsers non dovrebbe preoccuparsi della provenienza del testo. Potrebbe provenire da un file, da un database, da un servizio Web, da una fata o da qualsiasi luogo. Parsers non importa da dove proviene il testo sorgente.

Quindi per semplificare i test, elimina il caricamento dei file dalla funzione Parse , quindi il metodo diventa

    public IEnumerable<ParsedCardDetails> Parse(String[] data)
    {
        List<ParsedCardDetails> result = new List<ParsedCardDetails>();

        foreach (var item in data)
        {
            var validationResult = ParseLine(item);
            result.Add(validationResult);
        }

        return result;
    }

Ora tutto a un tratto, diventa un gioco da ragazzi scrivere test per il parser. Ad esempio, è davvero facile testare il comportamento quando viene passato un array di stringhe vuoto.

[TestMethod]
public void ParsingEmptyDataReturnsAnEmptyList()
{
   CsvParser parser = new CsvParser();
   var result = parser.Parse(new String[]);
   Assert.AreEqual(0, result.Count);
}

Non sono sicuro che ciò verrà effettivamente compilato, ma il punto dovrebbe essere abbastanza chiaro.

Separa il caricamento del file dall'effettivo parsing e i test si scrivono praticamente da soli.

Se ParseLine non deve essere pubblico, non renderlo pubblico. È possibile convalidare la logica di analisi passando l'input noto nel metodo Parse , quindi esaminare l'elenco restituito per convalidare la logica di analisi correttamente.

    
risposta data 19.10.2016 - 14:19
fonte

Leggi altre domande sui tag