Einfache Stubs für FileSystem-Aufrufe beim Unit Testing mit Moles

Eigentlich ist es eine einfache Aufgabe. Eine Funktion soll einen Dateinamen für eine Logdatei ermitteln. Der Name wird aus dem Datum erstellt und wenn die Datei bereits vorhanden ist, wird am Ende eine fortlaufende Nummer angefügt. Die Herausforderung liegt darin, dass auch Unit-Tests erstellt werden sollen.

Die zu testende Funktion „GetLogFileName“ ist einfach gehalten :

public class LogFileWriter
{
        public string Path { get; set; }
        public string GetLogFileName(DateTime date, int fileNumber = 0)
        {
            string fileName = Path + "\\" 
              + date.ToString("ddMMyyyy") + "_" 
              + fileNumber.ToString() + ".log";
 
            if (File.Exists(fileName))
                return GetLogFileName(date, fileNumber + 1);
            else
                return fileName;
        }
 
   }

 

Das Problem für den Unit-Test liegt im Aufruf File.Exists(). Diese Funktion greift auf das Filesystem zu. Für einen erfolgreichen Unit-Test müssten wir zuerst die Umgebung so vorbereiten, dass im entsprechenden Verzeichnis bereits eine Datei liegt. Aber genau genommen ist das dann kein Unit-Test mehr. Die Alternative ist, für die File-Klasse einen Stub anzulegen. Dieser Weg steht hier aber nicht offen, da File eine statische Klasse ist und in .NET die Ableitung von statischen Klassen nicht unterstützt wird. In diversen Foren wird oft darauf verwiesen, dass es keinen Grund gibt eine statische Klasse abzuleiten. Hier wäre einer! Ich möchte die Funktionalität der Exists()-Methode austauschen.

Die Lösung, die Microsoft hierfür zur Verfügung stellt ist das Moles-Framework. In den Visual Studio 2010 Power Tools ist Pex & Moles enthalten (http://msdn.microsoft.com/en-us/vstudio/bb980963). Mit diesem Detouring-Framework ist es möglich in jedem .Net-Assembly Methodenaufrufe „umzuleiten“. Somit kann für jede Methode eine selbsterstellte Methode hinterlegt werden.

Im Testprojekt müssen nur Moles-Assemblies für jedes Assembly hinzugefügt werden. Auch für die mscorlib.dll ist dies möglich.

image

Mit folgender Zeile wird festgelegt, für welche Klassen das Detouring möglich sein soll.

 [assembly: MoledType(typeof(System.IO.File))]

 

Nach dem Kompilieren steht die Moles-Klasse MFile zur Verfügung. In dieser kann im Unit-Test für jede Funktion ein Delegate hinterlegt werden. Die fertige Test-Methode hat dann folgenden Inhalt:

        [TestMethod()]
        [HostType("Moles")] 
        public void GetLogFileName_DateiVorhanden_LiefertDateinameMit1()
        {
            // Hinterlege für den Aufruf File.Exist() eine eigene Funktion:
            MFile.ExistsString = delegate(string fname)
            {
                if (fname == "C:\\Temp\\09012011_0.log")
                    return true;
                else
                    return false;
            };
            
            LogFileWriter target = new LogFileWriter(); 
            target.Path = "C:\\Temp";
 
            DateTime date = new DateTime(2011, 1, 9);           
            string expected = "C:\\Temp\\09012011_1.log";
            string actual;
            actual = target.GetLogFileName(date);
            Assert.AreEqual<string>(expected, actual);
        }
 

 

Wenig Aufwand, aber großer Nutzen beim Unit-Testing. Moles rocks!

Kommentar schreiben