Laden...

Tests & Dateisystem. Best practices?

Erstellt von userid14268 vor 13 Jahren Letzter Beitrag vor 13 Jahren 1.408 Views
U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren
Tests & Dateisystem. Best practices?

Es geht um folgendes.

Man hat ja immer wieder den Fall das man Daten Laden und Speichern muss sowie Ordner erstellen, nehmen wir Xml Dateien in einem Unterordner zum Beispiel.

Dafür gibt es Objekte die können Ordner validieren und erstellen sowie Dateien speichern und laden.
Die Frage die sich mir stellt ist, wie kann man das vernünftig Testen? Gibt es da gute Praktiken?

Das Assert müsste dann ja auch noch selber die Datei laden können und zu überprüfen das sie korrekt gespeichert wurde... die bloße Existenz reicht nicht immer.

A
69 Beiträge seit 2010
vor 13 Jahren

Die validierende Logik muss sowieso gekapselt sein. Die zugreifende Logik (also wirklich nur Datei auslesen und speichern) ist so simpel und sollte in dem DAL ohne weitere Validierung zu finden sein.

Den reinen Zugriff auf die Datei testet man nicht (1-3 Zeilen Code). Die validierende Logik allerdings schon. Je nach erforderlichen Daten verwendet man eine art Mocking.

5.742 Beiträge seit 2007
vor 13 Jahren

"Ideal" wäre natürlich, den Zugriff auf das Dateisystem nochmal zu abstrahieren und dann beim Test in z.B einen MemoryStream zu schreiben. Ob das aber wirklich nötig ist, sollte man im Einzelfall prüfen.
Du sagtest, dass eine Xml-Datei erzeugst? Naja-im einfachsten Fall kann dein Assert diese binär prüfen, d.h. dass sie exakt mit einer erwarteten Datei übereinstimmen muss. Ansonsten kannst du natürlich auch mittels eines XmlReaders oder so die Existenz und den Wert einzelner Kmoten prüfen.

U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren

Also ich könnte ein Objekt erstellen welches mir schon mal das XDocument erstellt, das kann ich dann Validieren und kann es mit einen ein zeiler speichern lassen so wie Arithmetika es vor schlägt.
Nur dann habe ich den Fall nicht abgedeckt das die Datei wegen fehlenden Rechten nicht gespeichert werden konnte, der Benutzer wählt das Zielverzeichnis aus, und wenn er unter Vista/Seven "Programme" aus wählt muss ich das ja auch entsprechend handeln.
Auf jeden fall kann ich das, so denk ich, in zwei Teile Splitten.

Ein Objekt welches ein XmlDocument erstellt, das kann ich dann separiert Testen, und eines welches tatsächlich speichert, da bräuchte ich nur noch die Existenz prüfen, richtig?

Nehmen wir mal etwas Code Beispiel dazu, dann wird es einfacher.

So habe ich das bisher:

public class Foo
{
    // komplexeres Objekt welches gespeichert werden soll
}

public class FooManager
{
    public Foo Load(string sourcePath)
    {
        return new FooLoader().Load(sourcePath);
    }

    public bool Save(Foo foo, string targetPath)
    {
        return new FooSaver().Save(foo, targetPath);
    }
}

internal class FooSaver
{
    internal bool Save(Foo foo, string targetPath)
    {
        // tatsächlich speichern und return true wenn fertig ohne fehler
    }
}

internal class FooLoader
{
    internal Foo Load(string sourcePath)
    {
        // tatsächliches laden, null wenn nicht möglich
    }
}

Wie könnte och nun "Load" und "Save" korrekt testen? Bei meiner Idee müsste ich den FooSaver und FooLoader so ab ändern das er nicht mit einem Pfad arbeitet sondern mit einem XmlDocument, und das müsste ich dann public machen.

1.373 Beiträge seit 2004
vor 13 Jahren

Hi,

Welches Load/Save willst du denn testen? Das vom Manager oder das von FooLoader/FooSaver.

Allgemein wäre es evtl. ganz nützlich, wenn du statt eines Dateinamens einen Stream verwendest, dann kanst du leicht auch besagten MemoryStream übergeben.

Nur dann habe ich den Fall nicht abgedeckt das die Datei wegen fehlenden Rechten nicht gespeichert werden konnte, der Benutzer wählt das Zielverzeichnis aus, und wenn er unter Vista/Seven "Programme" aus wählt muss ich das ja auch entsprechend handeln.
Auf jeden fall kann ich das, so denk ich, in zwei Teile Splitten.

Das Problem wird doch eher sein, dass die Methode, die die Datei schreibt, eine solche Exception gar nicht abhandeln kann sondern sie einfach nach oben durchreicht.

Grüße,
Andre

U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren

Die gezeigten Objekte befinden ich in der "Models" Assembly und alle Zugriffe sollen nur über den Manager laufen, dh der Aufrufer kann nur manager.Load und manager.Save aufrufen, wie es der Manager intern ab speichert und läd braucht den Aufrufer nicht zu interessieren, entsprechend ist es da mit einem Stream auch unpraktisch.

Der Manager delegiert es intern auch bloß an separate Objekte weiter, diese übernehmen dann das tatsächliche Speichern und Laden.

//Dazu

Das Problem wird doch eher sein, dass die Methode, die die Datei schreibt, eine solche Exception gar nicht abhandeln kann sondern sie einfach nach oben durchreicht.

Stimmt, so habe ich das noch gar nicht gesehen.
Dann wäre es doch möglich das FooLoader und FooSaver nur XmlDocument<->Stream handhabt und das tatsächliche Speichern der Manager übernimmt? Dafür müsste ich dann den FooLoader und FooSaver public machen (und umbenennen) -.- (Und mein Code Coverage würde dann mein Manager.Load/Save als nicht getestet monieren)

1.373 Beiträge seit 2004
vor 13 Jahren

An sich finde ich die Verwendung von Streams bei so etwas eine gute Sache, eben weil sie so abstrakt sind. Eine Überladung mit Pfaden kann man immer noch anbieten. Wenn du das nicht möchtest - wie wärs mit so etwas:


interface IStreamProvider {
  Stream OpenRead(string name);
  Stream OpenWrite(string name);
  void Close(Stream stream);
}

class FileStreamProvider : IStreamProvider {
  public Stream OpenRead(string name){ return File.OpenRead(name); }
  public Stream OpenWrite(string name){ return File.OpenRead(name); }
  public void Close(Stream stream){ stream.Close(); }
}

class MemoryStreamProvider : IStreamProvider {
  private MemoryStream stream;

  public MemoryStreamProvider(MemoryStream stream){
    this.stream = stream;
  }

  public MemoryStreamProvider():this(new MemoryStream()){
  }

  public MemoryStream Stream{get {return stream;}}

  public Stream OpenRead(string name){ return stream; }
  public Stream OpenWrite(string name){ return stream; }
  public void Close(Stream stream) { /* nichts schließen */ }
}

class FooManager {
  private IStreamProvider streamProvider;

  public FooManager(IStreamProvider streamProvider){
    this.streamProvider = streamProvider;
  }

  public void Save(Foo foo, string path){
    var stream = streamProvider.OpenWrite(path);
    try {
      new FooSaver().Save(foo, stream);
    } finally {
      streamProvider.Close(stream);
    }
  }

  public Foo Load(string path){
    var stream = streamProvider.OpenRead(path);
    try {
      return new FooSaver().Load(stream);
    } finally {
      streamProvider.Close(stream);
    }
  }
}

Dann hast du nach außen hin noch immer Pfadangaben, kannst aber für die Tests einen MemoryStreamPovider verwenden, um zu überprüfen, was geschrieben wird.

Grüße,
Andre

U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren

Hei die Idee finde ich gut, vielen Dank 😃 Melde mich wieder wie ich es dann final gemacht habe 😃

1.373 Beiträge seit 2004
vor 13 Jahren

Noch einen Kommentar hierzu:

  
internal class FooSaver  
{  
    internal bool Save(Foo foo, string targetPath)  
    {  
        // tatsächlich speichern und return true wenn fertig ohne fehler  
    }  
}  
  
internal class FooLoader  
{  
    internal Foo Load(string sourcePath)  
    {  
        // tatsächliches laden, null wenn nicht möglich  
    }  
}  

Beide Methoden sollten meiner Meinung nach eine Exception werfen, wenn etwas schiefgeht, nicht null oder false. Sonst muss die aufrufende Methode die Rückgabewerte kontrollieren usw. Das ist mit Exceptions deutlich übersichtlicher.

Ich habe mir mittlerweile angeeignet, dass Argumente und Rückgabewerte standardmäßig nie null sein können. Die wenigen Ausnahmen, welche idealerweise auf private Methoden beschränkt sein sollten, müssen entsprechend eindeutig dokumentiert sein.

Grüße,
Andre

U
userid14268 Themenstarter:in
1.578 Beiträge seit 2009
vor 13 Jahren

Exceptions sollten doch aber nicht dem Kontrollfluss der Applikation dienen, also Exceptions zeigen nur Fehler, und das der Benutzer auch mal eben ein "ungültiges" Verzeichnis auswählt ist normal und kein Fehler.

1.373 Beiträge seit 2004
vor 13 Jahren

Es geht ja nicht um Flusskontrolle. Aber wenn FooLoader.Load null zurückgibt, was sagt es dir über die Ursache, warum das Laden fehlgeschlagen ist? Zugriffsrechte? Fehlendes Verzeichnis? Kosmische Strahlung?

Das bedeutet ja nicht, dass man nicht mal kurz vor dem Aufruf von FooLoader.Load checken kann, ob die Datei besteht etc.