2016-05-16 8 views
0

Ich habe über TDD gelesen und ich habe eine Menge Beiträge über keine Datenbanktransaktion zu sehen, weil "einzelne, isolierte Block des Codes ohne Abhängigkeiten".TDD Einfügen in die Datenbank

So jetzt habe ich ein kleines Dilemma - Ich möchte testen können, ob meine Service-Layer-Methode namens AddNewStudent tatsächlich funktioniert. Diese Methode geht in meine DbContext und dann fügen Sie einen neuen Datensatz in die Datenbank. Wenn Datenbankoperationen nicht für TDD empfohlen werden, wie kann ich sonst die Methode AddNewStudent testen, anstatt nur meine Anwendung in einem Browser zu testen?

public class StudentManager : ManagerBase 
{ 
    internal StudentManager() { } 

    public Student AddNewStudent(string fName, string lName, DateTime dob) 
    { 
     // Create a student model instance using factory 
     var record = Factories.StudentFac.CreateOne(fName, lName, dob); 

     DbContext.Students.Add(record); 
     DbContext.SaveChanges(); 

     return record; 
    } 
} 

Und mein Test wie dieser

[TestMethod] 
public void StudentManager_AddNewStudent_Test() 
{ 
    var fName = "Ryan"; 
    var lName = "Rigil"; 
    var dob = DateTime.Parse("3/1/2006"); 

    var student = Managers.StudentManager.AddNewStudent(fName, lName, dob); 

    Assert.AreEqual(fName, student.FirstName); 
    Assert.AreEqual(lName, student.LastName); 
    Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString()); 
} 
+1

Erstellen Sie eine Abstraktion Ihres DbContext, die Sie in Ihren Komponententests simulieren können, um die zu testende Methode zu verifizieren. – Nkosi

+0

Woher kommt 'DbContext'? Ist es Entitätsrahmen? Welches Einheitentestframework verwenden Sie (d. H. NUnit)? Können wir einen Beispielcode sehen, der zeigt, was Sie gerne testen möchten? –

+0

Ein Beispielcode über –

Antwort

1

sieht Ihr StudentManager hat Abhängigkeiten intern begraben, dass es schwierig zu testen machen. Erwägen Sie eine Umstrukturierung Ihres Designs, um eine bessere Testbarkeit zu ermöglichen.

am StudentManager Blick die folgenden Annahmen abgeleitet wurden ...

//An assumed abstraction of the ManagerBase 
public abstract class ManagerBase { 
    public ManagerBase(IDbContext dbContext, IFactory factories) { 
     DbContext = dbContext; 
     Factories = factories; 
    } 
    public IDbContext DbContext { get; private set; } 
    public IFactory Factories { get; private set; } 
} 

//An abstraction of what the unit of work would look like 
public interface IDbContext { 
    //student repository 
    DbSet<Student> Students { get; } 
    //...other repositories 
    int SaveChanges(); 
} 

//Just an example of the Student Factory. 
public interface IModelFactory<T> where T : class, new() { 
    T Create(Action<T> configuration); 
} 

public interface IFactory { 
    IModelFactory<Student> StudentFac { get; } 
    //...other factories. Should try to make your factories Generic 
} 

Damit die Zielklasse refactors zu ...

public class StudentManager : ManagerBase { 
    public StudentManager(IDbContext dbContext, IFactory factories) : base(dbContext, factories) { } 

    public Student AddNewStudent(string fName, string lName, DateTime dob) { 
     // Create a student model instance using factory 
     var record = Factories.StudentFac.Create(r => { 
      r.FirstName = fName; 
      r.LastName = lName; 
      r.DoB = dob; 
     }); 

     base.DbContext.Students.Add(record); 
     base.DbContext.SaveChanges(); 

     return record; 
    } 
} 

Während es wie viel aussehen kann, wird es sehr helfen die Testbarkeit Ihres Codes.

Ein Mockframework wie Moq kann nun eine gefälschte Version des Datenbankzugriff und Fabriken ...

[TestMethod] 
public void StudentManager_Should_AddNewStudent() { 
    //Arrange: setup/initialize the dependencies of the test 
    var fName = "Ryan"; 
    var lName = "Rigil"; 
    var dob = DateTime.Parse("3006-01-03"); 

    //using Moq to create mocks/fake of dependencies 
    var dbContextMock = new Mock<IDbContext>(); 

    //Extension method used to create a mock of DbSet<T> 
    var dbSetMock = new List<Student>().AsDbSetMock(); 
    dbContextMock.Setup(x => x.Students).Returns(dbSetMock.Object); 

    var factoryMock = new Mock<IFactory>(); 
    factoryMock 
     .Setup(x => x.StudentFac.Create(It.IsAny<Action<Student>>())) 
     .Returns<Action<Student>>(a => { 
      var s = new Student(); 
      a(s); 
      return s; 
     }); 

    //this is the system/class under test. 
    //while this is being created manually, you should look into 
    //using DI/IoC container to manage Dependency Injection 
    var studentManager = new StudentManager(dbContextMock.Object, factoryMock.Object); 

    //Act: here we actually test the method 
    var student = studentManager.AddNewStudent(fName, lName, dob); 

    //Assert: and check that it executed as expected 
    Assert.AreEqual(fName, student.FirstName); 
    Assert.AreEqual(lName, student.LastName); 
    Assert.AreEqual(dob.ToShortDateString(), student.DoB.ToShortDateString()); 
} 

Für die Produktion erstellen verwendet werden können Sie die richtige Implementierung der Schnittstellen erstellen und sie in die Klassen injizieren das hängt von ihnen ab. Diese Antwort basiert vollständig auf dem Beispiel, das Sie in Ihrem Post angegeben haben. Nehmen Sie sich etwas Zeit, um die verwendeten Konzepte zu verstehen, und recherchieren Sie online. Sie können diese Konzepte dann mit dem Rest Ihres Projekts anwenden, während Sie mit TDD fortfahren.

+0

Schnelle Frage ... In meinen vorherigen Projekten würde ich abstrahierte UoW verwenden, aber da dies eine EDMX-Datei verwenden wird, ist Abstraktion von UoW noch notwendig? Ist das nicht doppelte Arbeit? –

+0

Eine andere Sache ist der Grund, warum ich Fabriken nutzen möchte, denn wenn ich eine Instanz eines Modells (in diesem Fall ein Student) erstelle, muss ich nicht erraten, welche Eigenschaften benötigt werden oder nicht. Mit Action gibt es eigentlich nichts, was mich daran hindern würde, eine Instanz eines Studenten ohne Vornamen und Nachnamen zu erstellen - ein Student mit nur DoB. –

+0

Das war nur ein Beispiel basierend auf dem Code in Ihrem ursprünglichen Post.Sie können immer noch Ihre eigene Methode erstellen, die das tut, was Sie ursprünglich beabsichtigten. – Nkosi