Wie verspottet man eine Methode mit einem hartcodierten Objekt?

11

Ich arbeite an einer Anwendung, die mehrere Ebenen hat. Datenzugriffsschicht zum Abrufen und Speichern von Daten aus der Datenquelle, Geschäftslogik zum Bearbeiten von Daten, Benutzeroberfläche zum Anzeigen der Daten auf dem Bildschirm.

Ich mache auch Unit-Tests der Geschäftslogikschicht. Die einzige Anforderung besteht darin, den Ablauf der Business-Layer-Logik zu testen. Daher verwende ich das Moq-Framework, um die Datenzugriffsschicht zu verspotten und die Geschäftslogikschicht mit MS Unit zu testen.

Ich verwende die Schnittstellenprogrammierung, um das Design so weit wie möglich zu entkoppeln, damit ein Komponententest durchgeführt werden kann. Business-Schicht ruft Datenzugriffsschicht über Schnittstelle auf.

Ich habe ein Problem, wenn ich versuche, eine der Geschäftslogikmethoden zu testen. Diese Methode erledigt einige Arbeiten und erstellt ein Objekt und übergibt es an die Datenzugriffsschicht. Wenn ich versuche, diese Datenzugriffsschichtmethode zu verspotten, kann sie nicht erfolgreich verspottet werden.

Hier versuche ich, einen Demo-Code zu erstellen, um mein Problem zu zeigen.

Modell:

public class Employee
{
    public string Name { get; set; }
}

Datenzugriffsschicht:

public interface IDal
{
    string GetMessage(Employee emp);
}

public class Dal : IDal
{
    public string GetMessage(Employee emp)
    {
        // Doing some data source access work...

        return string.Format("Hello {0}", emp.Name);
    }
}

Geschäftslogikschicht:

public interface IBll
{
    string GetMessage();
}

public class Bll : IBll
{
    private readonly IDal _dal;

    public Bll(IDal dal)
    {
        _dal = dal;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method.
        Employee emp = new Employee(); 

        string msg = _dal.GetMessage(emp);
        return msg;
    }
}

Gerätetest:

[TestMethod]
    public void Is_GetMessage_Return_Proper_Result()
    {
        // Arrange.
        Employee emp = new Employee; // New object.

        Mock<IDal> mockDal = new Mock<IDal>();
        mockDal.Setup(d => d.GetMessage(emp)).Returns("Hello " + emp.Name);

        IBll bll = new Bll(mockDal.Object);

        // Act.

        // This will create another employee object inside the 
        // business logic method, which is different from the 
        // object which I have sent at the time of mocking.
        string msg = bll.GetMessage(); 

        // Assert.
        Assert.AreEqual("Hello arnab", msg);
    }

Im Unit-Test-Fall zum Zeitpunkt des Verspottens sende ich ein Employee-Objekt, aber beim Aufrufen der Geschäftslogikmethode wird ein anderes Employee-Objekt innerhalb der Methode erstellt. Deshalb kann ich das Objekt nicht verspotten.

Wie soll ich in diesem Fall so gestalten, dass ich das Problem lösen kann?

DeveloperArnab
quelle
Normalerweise besteht der Trick darin, das Objekt in eine Schnittstelle zu verpacken und alle Benutzer dazu zu bringen, diese Schnittstelle zu verwenden. Dann verspotten Sie einfach die Schnittstelle. Alternativ können Sie die Methode virtuell machen und dann kann moq die Methode ohne die Schnittstelle verspotten. Nicht sicher über Rhinomocks oder andere in diesem Fall jedoch.
Jimmy Hoffa

Antworten:

12

Anstatt ein EmployeeObjekt direkt mit zu erstellen new, könnte Ihre Klasse Bllhierfür eine EmployeeFactoryKlasse mit einer Methode verwenden createInstance, die über den Konstruktor eingefügt wird:

 class EmployeeFactory : IEmployeeFactory
 {
       public Employee createInstance(){return new Employee();}
 }

Der Konstruktor sollte das Factory-Objekt über eine Schnittstelle führen IEmployeeFactory, damit Sie die "echte" Factory problemlos durch eine Mock-Factory ersetzen können.

public class Bll : IBll
{
    private readonly IDal _dal;
    private readonly IEmployeeFactory _employeeFactory;

    public Bll(IDal dal, IEmployeeFactory employeeFactory)
    {
        _dal = dal;
        _employeeFactory=employeeFactory;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method
        // *** using a factory ***
        Employee emp = _employeeFactory.createObject(); 
        // ...
    }
    //...
}

Die Scheinfabrik kann dem Test jede Art von EmployeeObjekt zur Verfügung stellen, die Sie für Ihren Test benötigen (z. B. createInstancekönnte immer dasselbe Objekt zurückgegeben werden):

 class MockEmployeeFactory : IEmployeeFactory
 {
       private Employee _emp;

       public MockEmployeeFactory()
       {
          _emp = new Employee();
          // add any kind of special initializing here for testing purposes
       }

       public Employee createInstance()
       {
          // just for testing, return always the same object
          return _emp;
       }
 }

Jetzt sollte die Verwendung dieses Modells in Ihrem Test den Trick tun.

Doc Brown
quelle
Können Sie mir ein Codebeispiel geben, damit ich Ihre Theorie visualisieren kann?
DeveloperArnab
@DeveloperArnab: siehe meine Bearbeitung.
Doc Brown
Sehr hilfreich ...
DeveloperArnab
4

Ich würde es als eine Einheit zum Testen behandeln.

Solange Sie alle Eingaben steuern, aus denen das EmployeeObjekt erstellt wird, sollte die Tatsache, dass es im getesteten Objekt erstellt wird, keine Rolle spielen. Sie benötigen lediglich die Mock-Methode, um das erwartete Ergebnis zurückzugeben, wenn der Inhalt des Arguments der Erwartung entspricht.

Dies bedeutet natürlich, dass Sie eine benutzerdefinierte Logik für die Mock-Methode bereitstellen müssen. Fortgeschrittene Logik kann oft nicht nur mit Mocks vom Typ "for x return y" getestet werden.

Tatsächlich sollten Sie dafür sorgen, dass es in den Tests kein anderes Objekt zurückgibt als in der Produktion, da Sie sonst den Code, der es erstellen soll, nicht testen würden. Dieser Code ist jedoch integraler Bestandteil des Produktionscodes und sollte daher auch vom Testfall abgedeckt werden.

Jan Hudec
quelle
Ja, ich kümmere mich nicht um die Eingaben der Datenzugriffsschicht, ich möchte nur dieses Objekt verspotten und fest codierte Daten zurückgeben, damit ich die Geschäftslogik testen kann. Aber das Problem ist wegen zwei verschiedenen Employee-Objekten, ich kann die Datenzugriffsschicht-Methode nicht verspotten.
DeveloperArnab
@DeveloperArnab: Die Objekte sind unterschiedlich, haben aber bekannten Inhalt. Alles, was Sie tun müssen, ist, den Mock einen benutzerdefinierten Vergleich anstelle der Objektidentität durchführen zu lassen.
Jan Hudec
@DeveloperArnab: Wenn Sie Employeein Tests ein anderes Objekt einfügen, testen Sie den Code, der es normalerweise erstellt, nicht. Sie sollten es also nicht ändern.
Jan Hudec
0

Wenn einige Testtools fehlschlagen, müssen Sie immer Schnittstellen verwenden und alles muss so erstellt werden, dass Sie das schnittstellenbasierte Objekt gegen ein anderes austauschen können.

Es gibt jedoch bessere Tools - nehmen Sie Microsoft Fakes (wurde Moles genannt), mit denen Sie jedes Objekt austauschen können, auch statische und globale. Das Ersetzen von Objekten erfolgt auf niedrigerer Ebene, sodass Sie nicht überall Schnittstellen verwenden müssen, während Sie die gewohnten Schreibweisen für Tests beibehalten.

gbjbaanb
quelle