2016-08-24 3 views
0

Ich habe die folgende Logger-Logger-Klasse und ich möchte wissen, die beste zu Unit-Test es.Unit Testing mit Moq und Autofac

Einige Beobachtungen:

  1. Ich brauchte die Schnittstelle IFileWrapper zu brechen, um die Abhängigkeit mit System.IO Abhängigkeit und in der Lage zu Benutzer Dependency Injection (Autofac)
  2. ich Unit-Tests war in der Lage zu schaffen die Methode FileWrapper.WriteLog durch Implementieren von IFileWrapper mit einem MemoryString, aber wenn ich ein erwartetes Verhalten innerhalb der Methode testen wollte, kann ich nicht (z. B .: Ausnahmen, falscher Pfad und Dateiname usw.)

    /// <summary> 
    /// Creates an instance of type <see cref="FileLogger"/> 
    /// </summary> 
    /// <remarks>Implements the Singleton Pattern</remarks> 
    private FileLogger() 
    { 
        FileName = string.Format("\\{0: MMM dd, yy}.log", DateTime.Now); 
        Path = Environment.CurrentDirectory; 
    
        FileWrapper = ContainerBuilderFactory.Container.Resolve<IFileWrapper>(); 
    } 
    
    /// <summary> 
    /// Log the <paramref name="Message"/> in the <paramref name="Path"/> specified. 
    /// The <paramref name="UserName"/>, <paramref name="Host"/> must be supplied 
    /// </summary> 
    /// <example> 
    ///  <code> 
    ///   var handler = new LoggerHandlerFactory(); 
    ///   var logger = handler.GetHandler<FileLogger>(); 
    ///   logger.Log("Hello CSharpLogger"); 
    ///  </code> 
    /// </example> 
    /// <exception cref="ArgumentNullException"></exception> 
    /// <exception cref="ArgumentException"></exception> 
    /// <exception cref="NotSupportedException"></exception> 
    /// <exception cref="FileNotFoundException"></exception> 
    /// <exception cref="IOException"></exception> 
    /// <exception cref="SecurityException"></exception> 
    /// <exception cref="DirectoryNotFoundException"></exception> 
    /// <exception cref="UnauthorizedAccessException"></exception> 
    /// <exception cref="PathTooLongException"></exception> 
    /// <exception cref="ArgumentOutOfRangeException"></exception> 
    /// <exception cref="FormatException"></exception> 
    public void Log(string message, LogLevel level = LogLevel.INFO) 
    { 
        lock (_current) 
        { 
         var configLevel = CSharpLoggerConfiguration.Configuration.GetLogLevel(); 
    
         if (configLevel != LogLevel.OFF & level != LogLevel.OFF && configLevel >= level) 
         { 
          try 
          { 
           FileWrapper.WriteLog(string.Concat(Path, FileName), message, level); 
          } 
          catch (CSharpLoggerException) 
          { 
           throw; 
          } 
         } 
        } 
    } 
    

So habe ich die folgende Unittesting mit Moq:

//arrange 
     CSharpLoggerConfiguration.Configuration.SetLogLevel(LogLevel.DEBUG); 

     var mock = new Mock<IFileWrapper>(); 
     mock.Setup(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>())); 

     logger.FileWrapper = mock.Object; 

     //act 
     logger.Log("Hello CSharpLogger", LogLevel.DEBUG); 
     logger.Log("Hello CSharpLogger", LogLevel.WARN); 

     //assert 
     mock.Verify(x => x.WriteLog(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<LogLevel>()), Times.Exactly(2)); 

So weit so gut. Was ich nicht confortable bin, ist mit dieser Zeile: logger.FileWrapper = mock.Object; Ich möchte FileWrapper property privat halten.

Jede Beratung ist willkommen.

Ich werde den Code http://csharplogger.codeplex.com/ veröffentlichen, falls Sie weitere Informationen wünschen.

Antwort

3

Verwenden Sie constructor injection. Zusamenfassend; Anstatt den Dienst (in diesem Fall den Dateiwrapper) durch Festlegen einer Eigenschaft bereitzustellen, muss der Protokollierer über einen öffentlichen Konstruktor verfügen, der ein Argument IFileWrapper akzeptiert.

public class Logger 
{ 
    public Logger(IFileWrapper fileWrapper) 
    { 
     FileWrapper = fileWrapper; 
    } 

    public IFileWrapper FileWrapper { get; } 
} 

// in your test: 
var logger = new Logger(mock.Object); 

Zur Beantwortung der Frage über einen Singleton-Wrapper für Dateien mit gründlichen mit, hier ist ein Codebeispiel für die Anwendung (Nicht-Test) Code:

public static class FileWrapperFactory 
{ 
    private static IFileWrapper _fileWrapper; 

    public static IFileWrapper GetInstance() 
    { 
     return _fileWrapper ?? (_fileWrapper = CreateInstance()); 
    } 

    private static IFileWrapper CreateInstance() 
    { 
     // do all the necessary setup here 
     return new FileWrapper(); 
    } 
} 


public class StuffDoer 
{ 
    public void DoStuff() 
    { 
     var logger = new FileLogger(FileWrapperFactory.GetInstance()); 

     logger.WriteLog("Starting to do stuff..."); 

     // do stuff 

     logger.WriteLog("Stuff was done."); 
    } 
} 

Da die FileWrapperFactory eine statische Instanz des unterhält Datei-Wrapper, du wirst nie mehr als einen haben. Sie können jedoch mehrere Logger erstellen und müssen sich nicht darum kümmern. Wenn Sie in Zukunft entscheiden, dass viele Dateiwrapper in Ordnung sind, muss der Protokollierungscode nicht geändert werden.

In einer realen Anwendung würde ich Ihnen raten, eine Art von DI-Framework zu wählen, um all diese Buchhaltung für Sie zu erledigen; Die meisten haben eine ausgezeichnete Unterstützung für Singleton-Instanzen, was im Wesentlichen das tut, was die obigen FileWrapperFactory tut (aber normalerweise in einer anspruchsvolleren und robusteren Weise. FileWrapperFactory ist nicht thread-safe, zum Beispiel ...).

+0

Hallo Tomas , Danke für deine Antwort. Ich habe darüber nachgedacht, aber da meine Klasse eine Singleton-Klasse ist, sollte es nicht funktionieren, oder? – user1922446

+0

Sie müssen vermeiden, Ihren Service als eine 'statische Klasse' zu ​​haben, da Sie eine Instanz davon injizieren müssen, um dieses Muster anzuwenden.Die Verwendung einer statischen (Singleton) -Instanz * ist kein Problem - die Tatsache, dass die Instanz Singleton ist, liegt jedoch nicht in der Verantwortung des Loggers *. (Es liegt in der Verantwortung desjenigen, der den Logger instanziiert, die Instanz des Dateiwrappers bereitzustellen und somit jedes Mal die Singleton-Instanz auszuwählen.) –

+0

@ user1922446: Siehe mein Update für eine detailliertere Erklärung dessen, was ich meine. –

0

Da Ihre Code-Kommentare zeigen, dass Ihr Logger ein Singleton ist, benötigen Sie einen anderen Weg als die Konstruktorinjektion, um die Abhängigkeit festzulegen. In seinem Buch über Legacy-Code, schlägt Mike Federn eine Funktion für solche Zwecke, die ausreichend benannt ist, so etwas wie

public void SetInstanceForTesting(IFileWrapper fileWrapper) {...} 

Nun wird diese Funktion nicht hoffnungsvoll für verschiedene Zwecke verwendet werden ...

+0

Hallo Bernhard, Danke für Ihre Antwort. Ja, ich benutze Abstract Factory und Singleton Design Patterns, die mit Abhängigkeitsinjektionen knifflige Aufgaben erledigen. Ich versuche nur zu sehen, was der beste Ansatz sein könnte. – user1922446