2013-11-04 10 views
6

Ich bin neu in der Unit-Tests und ich möchte etwas Hilfe. Ich verwende Code zuerst mit Repository-Muster. Ich habe eine generische Repository, das generische Repository (siehe Schlag) generic CRUD Betrieb enthält genanntUnit-Test mit Moq und Repository-Muster

public abstract class GenericRepository<T> where T : class 
    { 
     private HolidayDatabaseContext _dataContext; 
     private readonly IDbSet<T> _dbset; 
     protected GenericRepository(IDbFactory databaseFactory) 
     { 
      DatabaseFactory = databaseFactory; 
      _dbset = DataContext.Set<T>(); 
     } 
     protected IDbFactory DatabaseFactory 
     { 
      get; 
      private set; 
     } 
     protected HolidayDatabaseContext DataContext 
     { 
      get { return _dataContext ?? (_dataContext = DatabaseFactory.Get()); } 
     } 

     public virtual void Add(T entity) 
     { 
      _dbset.Add(entity); 
     } 
     public virtual void Update(T entity) 
     { 
      _dataContext.Entry(entity).State = EntityState.Modified; 
     } 
     public virtual void Delete(T entity) 
     { 
      _dbset.Remove(entity); 
     } 
     public virtual IEnumerable<T> Enumerable() 
     { 
      return _dbset.AsEnumerable<T>(); 
     } 

     public virtual IQueryable<T> List() 
     { 
      return _dbset.AsQueryable<T>(); 
     } 

     public virtual T GetSingleById(int id) 
     { 
      return _dbset.Find(id); 
     } 

     public void Save() 
     { 
      _dataContext.SaveChanges(); 
     } 

    } 

Ich habe dann in einem User-Repository geerbt und einige spezifische Methoden erstellt. siehe unten

public class UserRepository : GenericRepository<User>, IUserRepository 
    { 
     public UserRepository(IDbFactory databaseFactory) 
      : base(databaseFactory) { } 


     public int HolidayEntitlement(int userId) 
     { 
      return HolidayEntitlement(userId, DateTime.Now); 
     } 
     public int HolidayEntitlement(int userId, DateTime dateTime) 
     { 
      //Get the User 
      var user = this.GetSingleById(userId); 

      //Work Total Entitlement 
      int entitlement = user.BaseHolidayEntitlement; 

      //Years in Service 
      entitlement += (dateTime - user.EmploymentStartDate).Days/365; 

      return entitlement; 



     } 


     public int RemainingHolidayEntitlement(int userId) 
     { 
      return RemainingHolidayEntitlement(userId, DateTime.Now); 
     } 

     public int RemainingHolidayEntitlement(int userId, DateTime dateTime) 
     { 
      return int.MinValue; 
     } 
    } 

Ich mag würde Test HolidayEntitlement zu Unit (int userId, Datetime Datetime), aber ich brauche das GetSingleById Teil in der Methode zu verspotten

ich dies als Test geschrieben haben, aber es funktioniert nicht kompilieren.

[TestMethod] 
     public void GetHolidayEntitlement25() 
     { 
      //How to write this Unit test 


      //Setup 
      var user = new User { AnnualHolidayIncrement = 1, BaseHolidayEntitlement = 25, EmploymentStartDate = new DateTime(2013, 1, 1),Id=1 }; 

      Mock<UserRepository> mock = new Mock<UserRepository>(); 
      mock.Setup(m => m.GetSingleById(1)).Returns(user); 

      Assert.AreEqual(25, mock.Object.HolidayEntitlement(1)); 
     } 

Jede Hilfe wäre

Antwort

12

Sie scheinen klar sein, zu sagen, dass Sie wollen nur einen Teil der Schnittstelle verspotten. Wenn du anfängst, auf diese Art von Situation zu stoßen, deutet das darauf hin, dass du deine Bedenken mischt und wahrscheinlich irgendwo etwas falsch machst.

In diesem Fall macht Ihr Repository VIEL mehr als nur CRUD und hat daher mehrere Verantwortlichkeiten (es sollte nur eine, Nachschlagen SOLID-Programmierung haben). Sie führen Geschäftslogik im Repository durch und es sollte nicht dort leben! Alles andere als einfache CRUD-Operationen sollten in die Business-Logik-Ebene verschoben werden. I.e. Ihre HolidayEntitlement Methode berechnet etwas durch Anwendung einiger Logik und ist daher KEINE CRUD/Repository-Operation!

Also ... Was Sie tun sollten, ist das Verschieben der Business-Logik-Bits in eine neue Klasse, sagen UserLogic. Innerhalb der UserLogic Klasse würden Sie eine injizierte IUserRepository verwenden, um mit Ihrem Repository zu kommunizieren. In UserLogic das ist, wo Sie Ihre HolidayEntitlement Methode setzen würde und es würde einen Anruf an IUserRepository.GetSingleById machen. Also, wenn Sie dann Ihre UserLogic Klasse testen, würden Sie in Ihren Schein IUserRepository injizieren, die die Stub-Version von GetSingleById haben würde, und dann werden Sie wieder auf dem richtigen Weg sein!

Ich hoffe, das macht Sinn/hilft ?!

--ORIGINAL POST--

P. S. Mein Original-Beitrag erklärt, dass Sie Schnittstellen spotten sollte, keine Instanzen so noch gilt, und ich werde hier als Referenz verlassen:

Sie IUserRepository verspotten sollte NICHTUserRepository.

Das ist, weil UserRepository eine Implementierung von IUserRepository ist. Sie möchten sagen, dass Sie ihm eine NEUE Implementierung geben, d. H. Ihren Schein. Im Moment verwenden Sie die ACTUAL Klasse UserRepository.

+0

Dank für die schnelle Antwort danken, was bedeutete, dass ich zu sagen ist, dass mein Allgemein Repository verwaltet die CRUD-Operationen . und dann habe ich maßgeschneiderte Repositories für jede meiner Entitäten, die von generischen erbt. Ich schätze, der Fehler, den ich hier gemacht habe, ist die "Holiday Entitlement" -Methode in das Repository zu setzen. Es sollte in einer logischen Schicht sein, danke, dass Sie mich in die richtige Richtung weisen, ich werde dies aufteilen und versuchen, den Komponententest erneut zu erstellen. –

+1

@MikeRoss - Ja, Fehler sind einfach zu machen, aber ich freue mich, dass Sie Unit-Tests schreiben. Sie helfen Ihnen, diese Dinge früh aufzugreifen. Sie stellen sicher, dass du die Dinge an die richtige Stelle legst, denn wenn du dich nicht verspotten kannst, dann weißt du, dass etwas nicht stimmt. Mach weiter so! – Belogix

+0

@Daniel - Die ursprüngliche Frage lautete: "Ich möchte Unit Test HolidayEntitlement testen ...", was nicht bedeutet "Wie teste ich CRUD?". Das könnte Ihre ** Frage sein. In diesem Fall würde ich Ihnen raten, Ihre eigene Frage zu stellen und explizit zu fragen, was Sie gerne wissen möchten. – Belogix

4

Mocking im Allgemeinen verwendet, wenn Sie eine gefälschte Abhängigkeit und in diesem Fall liefern müssen Sie scheinen zu versuchen, das System Under Test (SUT) zu verspotten, die nicht wirklich Sinn macht - es gibt buchstäblich keinen Sinn, weil Ihr Test sagt Ihnen nichts über das Verhalten von UserRepository; Alles, was Sie tun, ist zu testen, ob Sie Ihren Mock richtig eingerichtet haben, was nicht sehr nützlich ist!

Der von Ihnen angegebene Testcode scheint anzuzeigen, dass Sie UserRepository.HolidayEntitlement testen möchten.

Ich wäre viel mehr geneigt, Funktionen wie diese aus Ihrer Repository-Klasse und in eine separate Business-Logik-Typ-Klasse zu verschieben. Auf diese Weise können Sie die Logik der Berechnung des Urlaubsanspruches eines Benutzers in völliger Isolation testen, was ein Hauptgrundsatz des Komponententests ist.

Um zu testen, dass diese Funktion tut, was es (dh eine Berechnung durchführen, basierend auf Eigenschaften eines User) tun soll ist müssen Sie sicherstellen, dass das, was User Instanz innerhalb dieser Funktion betrieben wird 100% isoliert und unter Ihre Kontrolle - entweder mit einer Mock oder Fake (Stub) Instanz von User, in diesem Fall sind Mocks eine ausgezeichnete Wahl, da Sie nur die Teile der Abhängigkeit implementieren müssen, die Ihr SUT benötigt.

Also, was Sie tun können, ist dies:

eine Schnittstelle für Benutzer definieren

public interface IUser 
{ 
    int BaseHolidayEntitlement{get;set;} 
    DateTime EmploymentStartDate {get;set;} 
    //other properties for a User here 
} 

Implementieren Sie diese auf Ihrem Benutzerklasse

public class User:IUser 
{ 
    //implemement your properties here 
    public int BaseHolidayEntitlement{get;set;} 
    public DateTime EmploymentStartDate {get;set;} 
    //and so on 
} 

Erstellen Sie eine Klasse für Benutzerlogik

public class UserRules 
{ 
    public int GetHolidayEntitlement(IUser user,DateTime dateTime) 
    { 
    //perform your logic here and return the result 
    } 
} 

Jetzt Ihren Test wird wesentlich einfacher und braucht nicht einmal das Repository

[TestMethod] 
public void GetHolidayEntitlement_WithBase25_Returns25() 
{ 
    //Arrange 
    var user = new Mock<IUser>(); 
    //setup known, controlled property values on the mock: 
    user.SetupGet(u=>u.BaseHolidayEntitlement).Returns(25); 
    user.SetupGet(u=>u.EmploymentStartDate).Returns(new DateTime(2013,1,1)); 
    var sut = new UserRules(); 
    int expected = 25; 
    //Act 
    int actual = sut.GetHolidayEntitlement(user.Object,DateTime.UtcNow); 
    //Assert 
    Assert.AreEqual(expected,actual,"GetHolidayEntitlement isn't working right..."); 
} 
+0

Vielen Dank für Ihre Antwort Stephen, ich denke, dass ich in ein eng gekoppeltes Design gefallen bin, indem ich die Geschäftslogik dort hinzugefügt habe. Ich schätze, ich werde mein Musterwissen auffrischen. –

+0

@MikeRoss Das ist das Schöne am Komponententest - Sie können Ihr Design genau betrachten und eine enge Kopplung erkennen. Viel Glück damit! –

+0

Nur eine kleine Sache - Sie haben GetHolidayEntitlement mit 2 Parametern definiert, und in der Testmethode übergeben Sie nur eine – Sasha