2009-04-07 7 views
14

Ich mache meine ersten Schritte mit MsTest und Moq und möchte eine Linq2SQL-Repository-Klasse Unit-Test. Das Problem ist, dass ich nicht möchte, dass die Komponententests meine Entwicklungsdatenbank permanent ändern.Unit testet ein LINQ2SQL-Repository

Welches wäre der beste Ansatz für dieses Szenario?

  • Lassen Sie jeden Test auf meine wirkliche Entwicklung Datenbank arbeiten, aber stellen Sie sicher, dass jeder Test nach selbst aufräumt
  • Erstellen Sie ein Duplikat meiner Entwicklung Datenbank und dbml für das Gerät zu testen und diesen Kontext stattdessen verwenden, damit ich kann klar die gesamte Datenbank vor jedem Testlauf
  • Finden Sie einige ausgeklügelte Art und Weise, den Datacontext zu verspotten (bitte beachten Sie, dass ich insgesamt ein Moq noob bin).
  • Etwas ganz anderes? Vielleicht etwas, was die Einrichtung der Datenbank für mich vor jedem Testlauf automatisieren würde?

Edit: Ich habe gerade erfahren, dass MBUnit ein Rollback-Attribut, das alle Datenbankoperationen von einem Testfall laufen umkehrt. Ich bin nicht besonders an MSTest gebunden, also könnte dies eine einfache Antwort auf mein Problem sein?

Antwort

1

ich ein wenig mit MBUnit gespielt und gelernt, dass für die meisten Testfälle können Sie weg die Datacontext ohne spöttische von MBUnit des [ROLLBACK] Attribut .

Leider gibt es auch Fälle, in denen das Attribut seltsame Nebeneffekte erzeugt, wie das Laden einer linq-Entität aus der Datenbank, das Ändern einer Eigenschaft (ohne submissionchanges) und das erneute Laden derselben Entität. Normalerweise führt dies zu keiner Aktualisierungsabfrage für die Datenbank, aber innerhalb der Testmethode scheint es so, als würde die Aktualisierung sofort ausgeführt, sobald ich die linq-Entitätseigenschaft ändere.

Keine perfekte Lösung, aber ich denke, ich werde mit dem [ROLLBACK] -Attribut gehen, da es weniger Aufwand ist und gut genug für mich funktioniert.

14

Ich ging mit Mocking/Fake der Datenbank mit einigen Wrapper-Klassen + eine falsche Implementierung basierend auf http://andrewtokeley.net/archive/2008/07/06/mocking-linq-to-sql-datacontext.aspx. Beachten Sie, dass ich am Ende die SubmitChanges-Logik in meinem falschen Datenkontext-Wrapper implementiert habe, um die Validierungslogik in der partiellen Klassenimplementierung meiner Entität zu testen. Ich denke, das war wirklich der einzige schwierige Teil, der sich wesentlich von der Implementierung von Tokeley unterschied.

Ich werde meine FakeDataContextWrapper Implementierung unten sind:

public class FakeDataContextWrapper : IDataContextWrapper 
{ 

    public DataContext Context 
    { 
     get { return null; } 
    } 

    private List<object> Added = new List<object>(); 
    private List<object> Deleted = new List<object>(); 

    private readonly IFakeDatabase mockDatabase; 

    public FakeDataContextWrapper(IFakeDatabase database) 
    { 
     mockDatabase = database; 
    } 

    protected List<T> InternalTable<T>() where T : class 
    { 
     return (List<T>)mockDatabase.Tables[typeof(T)]; 
    } 

    #region IDataContextWrapper Members 

    public virtual IQueryable<T> Table<T>() where T : class 
    { 
     return mockDatabase.GetTable<T>(); 
    } 

    public virtual ITable Table(Type type) 
    { 
     return new FakeTable(mockDatabase.Tables[type], type); 
    } 

    public virtual void DeleteAllOnSubmit<T>(IEnumerable<T> entities) where T : class 
    { 
     foreach (var entity in entities) 
     { 
      DeleteOnSubmit(entity); 
     } 
    } 

    public virtual void DeleteOnSubmit<T>(T entity) where T : class 
    { 
     this.Deleted.Add(entity); 
    } 

    public virtual void InsertAllOnSubmit<T>(IEnumerable<T> entities) where T : class 
    { 
     foreach (var entity in entities) 
     { 
      InsertOnSubmit(entity); 
     } 
    } 

    public virtual void InsertOnSubmit<T>(T entity) where T : class 
    { 
     this.Added.Add(entity); 
    } 

    public virtual void SubmitChanges() 
    { 
     this.SubmitChanges(ConflictMode.FailOnFirstConflict); 
    } 

    public virtual void SubmitChanges(ConflictMode failureMode) 
    { 
     try 
     { 
      foreach (object obj in this.Added) 
      { 
       MethodInfo validator = obj.GetType().GetMethod("OnValidate", BindingFlags.Instance | BindingFlags.NonPublic); 
       if (validator != null) 
       { 

        validator.Invoke(obj, new object[] { ChangeAction.Insert }); 
       } 
       this.mockDatabase.Tables[obj.GetType()].Add(obj); 
      } 

      this.Added.Clear(); 

      foreach (object obj in this.Deleted) 
      { 
       MethodInfo validator = obj.GetType().GetMethod("OnValidate", BindingFlags.Instance | BindingFlags.NonPublic); 
       if (validator != null) 
       { 
        validator.Invoke(obj, new object[] { ChangeAction.Delete }); 
       } 
       this.mockDatabase.Tables[obj.GetType()].Remove(obj); 
      } 

      this.Deleted.Clear(); 

      foreach (KeyValuePair<Type, IList> tablePair in this.mockDatabase.Tables) 
      { 
       MethodInfo validator = tablePair.Key.GetMethod("OnValidate", BindingFlags.Instance | BindingFlags.NonPublic); 
       if (validator != null) 
       { 
        foreach (object obj in tablePair.Value) 
        { 
         validator.Invoke(obj, new object[] { ChangeAction.Update }); 
        } 
       } 
      } 
     } 
     catch (TargetInvocationException e) 
     { 
      throw e.InnerException; 
     } 
    } 

    public void Dispose() { } 

    #endregion 
} 
+1

Yup, was ich auch getan habe. Das und erstellte Integrationstests, die gegen eine Datenbank ausgeführt wurden. Linq to Sql ist in dieser Hinsicht ziemlich gut - Sie können die Datenbank direkt aus dem Datenkontext erstellen und erstellen. – Will

2

Ich hatte ein ähnliches Bedürfnis - Unit-Test der Linq zu Sql-Klassen, also machte ich eine kleine Reihe von Klassen, um Mock Datenkontext, ITables und IQueryables in die Abfragen zu bekommen.

Ich habe den Code in einem Blog-Post "Mock and Stub for Linq to Sql". Es verwendet Moq und bietet möglicherweise genug Funktionalität für die Tests, nach denen Sie suchen, ohne die Datenbank zu treffen.