Unit Tests, jak przetestować tą klasę

0

Mam klasę:

using System;
using System.IO;
using System.Text;

namespace NetLogger.Core.Logging
{
    internal class FileLogger : ILogger, IDisposable
    {
        private static Encoding UTF8NoBOM = new UTF8Encoding(false, true);
        private StreamWriter writer;

        public FileLogger(string path) : this(path, false, UTF8NoBOM)
        {
        }
        public FileLogger(string path, bool append) : this(path, append, UTF8NoBOM)
        {
        }
        public FileLogger(string path, bool append, Encoding encoding)
        {
            this.Encoding = encoding;
            this.Path = path;

            this.writer = new StreamWriter(path, append, encoding);
        }

        public Encoding Encoding { get; private set; }
        public string Path { get; private set; }

        public void Log<T>(T data, Func<T, string> formatter = null)
        {
            if (data == null) throw new ArgumentNullException("data");

            this.writer.WriteLine(formatter != null ? formatter(data) : data.ToString());
            this.writer.Flush();
        }

        #region IDisposable Support

        private bool disposedValue = false;

        public void Dispose()
        {
            Dispose(true);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    if (writer != null)
                    {
                        writer.Close();
                        writer = null;
                    }
                }

                disposedValue = true;
            }
        }

        #endregion IDisposable Support
    }
}

Uczę się nieco o testach jednostkowych, zabrałem się za bibliotekę NUnit oraz Moq, ale bladego pojęcia nie mam jak przetestować tą klasę? Chciałbym przetestować funkcję Log, pod kątem czy rzuca wyjątek jak prześlemy null, czy odpowiednio formatuje obiekty itp. Z tego co czytałem funkcje powinny być odizolowane od systemu, a tam siedzi SteamWriter więc jak to ogarnąć?

2

Tworzysz instancje tej klasy mockujesz writer

var writerMock = new Mock<StreamWriter>();


ustawiasz zachowanie mocka czyli metode writeline i flush. Co ma się stać po ich wykonaniu.

writerMock.Setup(m => m.WriteLine(argumenty)).ZACHOWANIE;

to samo z flush

no i sprawdzasz przebieg za pomocą Assert.

Np. dla scenariusza wyjątku. Z użyciem Xunit;

Assert.Throws<ArgumentNullException>( () => fileLogger.Log(/*argumenty*/));

Tutaj dokumentacja Moq.
https://github.com/Moq/moq4/wiki/Quickstart

0

Dzięki! :) Tylko jak potem tego mockowego writera przekazać do instancji FileLogger?

0

Przesłać przez konstruktor. Wstrzyknąć zależność. DI.

0

Ok, także potrzeba dodatkowego konstruktora. Dzięki :) Można zaakceptować odpowiedź.

0

Słabo orientuje się w testach, i chciałbym się na tym przykładzie dopytać:
A czy to nie będzie wtedy testowanie implementacji zamiast testowania funkcjonalności?

0

Zawsze sądziłem że funkcjonalność tworzymy poprzez jej implementację...

0

Tutaj powinny być 3 testy:

  • rzucenie wyjątku dla nulla
  • użycie formattera jeśli jest przekazany (formatter zamockowany i weryfikujemy czy został uzyty) i posłanie wyniku do mockowanego stream writera
  • posłanie gołego stringa jeśli nie ma formattera i posłanie wyniku do mockowanego stream writera

To nie jest testowanie implementacji bo przecież testujemy kontrakt tej metody. Kontrakt jest taki:

  • Jeśli dane są nullem to leci wyjątek
  • Jeśli przekazany jest formatter to jest użyty do formatowania wyniku
  • Jeśli nie ma formattera to dane lecą w formacie surowym

Jedyny zgrzyt to mockowanie tego stream writera bo ktoś teoretycznie mógłby logować inaczej. Tutaj faktycznie testujemy implementacje. Niemniej w takiej sytuacji szansa że ten fragment kodu ulegnie zmianie jest raczej niewielka. Możemy ewentualnie zrobić tam drugą metodę która loguje gołego przekazanego stringa. Wtedy w naszym teście weryfikujemy tylko czy zostala wywołana z odpowiednimi danymi. Taka metoda będzie miała dwie linijki i będzie w niej jedynie wywołanie this.writer.WriteLine() i Flush() czyli nie ma tam naszego kodu który należałoby testować.

1 użytkowników online, w tym zalogowanych: 0, gości: 1