2016-07-25 21 views
1

Ich bin ein Anfänger zum Codieren.Komponententest C# mock Schnittstelle in einer anderen Schnittstelle

Ich habe eine Klasse, A:

public class A 
{ 
    public InterfaceB _b; 

    public A() 
    { 
     _b = new B(); 
    } 

    public string functionA() 
    { 
     if(String.IsNullOrEmpty(_b.GetId())) 
      return String.Empty; 
     else if(String.IsNullOrEmpty(_b.GetKey())) 
      return String.Empty; 
     else 
      return _b.GetToken(); 
    } 
} 

public interface InterfaceB 
{  
    string GetId(); 
    string GetKey(); 
    string GetToken(); 
} 

Ich möchte functionA testen, wo ich in alle drei Verfahren von interfaceB eindringen kann. In meinem Komponententest erstelle ich eine Instanz der Klasse A und wenn ich sie anrufe, kann ich das Verhalten der Klasse B nicht einstellen.

Es trifft weiterhin die db, aber ich brauche es für andere Testfälle.

Wie kann ich das komplett durchspielen, damit ich die gesamte Logik testen kann?

+0

Sie einen Konstruktor auf 'A' benötigen, die ein' InterfaceB' als Parameter übernimmt, dann können Sie entweder direkt ein verspottete Objekt 'InterfaceB' zu diesem Kontruktor oder eingestellt für die Injektion, wenn Sie DI verwenden, auch wenn nur für diesen Test. – starlight54

Antwort

4

Um A und Mock-Schnittstelle zu testen InterfaceB Sie A so zu schreiben, dass es für eine Instanz von InterfaceB nicht verantwortlich ist. Stattdessen erhält es über seinen Konstruktor eine Instanz von InterfaceB.

Sie werden diese Muster über sehen und immer wieder:

public A() 
{ 
    private readonly InterfaceB _b; 

    public A(InterfaceB b) 
    { 
     _b = b; 
    } 

    public string functionA() 
    { 
     if(String.IsNullOrEmpty(_b.GetId())) return String.Empty; 
     else if(String.IsNullOrEmpty(_b.GetKey())) return String.Empty; 
     else return _b.GetToken(); 
    } 
} 

Dies wird Dependency Injection genannt. Es bedeutet, dass die Abhängigkeit einer Klasse in sie "injiziert" wird und nicht die Klasse, die sie erzeugt. Wenn wir uns so in den Konstruktor einfügen, nennen wir es auch "Konstruktorinjektion", aber gewöhnlich ist es nur "Abhängigkeitsinjektion". Und es ist einer der Gründe, warum wir es verwenden, wenn wir Schnittstellen nachahmen können.

Ein paar wichtigen Details:

  • Da InterfaceB an den Konstruktor übergeben werden, nichts in A immer „weiß“, was die tatsächliche Umsetzung ist. Es könnte alles sein. Daher ist A niemals an eine konkrete Implementierung gebunden. (Deshalb können Sie "spotten" InterfaceB.)
  • Das Feld _b ist readonly. Das ist nicht unbedingt notwendig, aber es bedeutet, dass _b nur vom Konstruktor gesetzt und nie wieder geändert werden kann. Das betont, dass A nur dieses empfängt und verwendet. Diese Klasse steuert nie, was ist. Was auch immer erzeugtA bestimmt, was dieser Wert ist.

Nun, wenn Sie ein Gerät zu testen gerade schreiben Sie verspott Implementierungen von InterfaceB erstellen können, die genau tun, was Sie wollen, wie

public class MockedInterfaceB : InterfaceB 
{ 
    private string _id; 
    private string _key; 
    private string _token; 

    public MockedInterfaceB(string id, string key, string token); 
    { 
     _id = id; 
     _key = key; 
     _token = token; 
    } 
    public string GetId() {return _id}; 
    public string GetKey() {return _key}; 
    public string GetToken() {return _token}; 
} 

Und dann in Ihrem Unit-Test können Sie die Implementierung verwenden:

var testA = new A(new MockedInterfaceB("myid","mykey","mytoken")); 

Sie können auch Tools wie Moq verwenden, um diese Mocks einfacher zu erstellen.

Wenn Sie von der Abhängigkeitsinjektion hören, befindet es sich oft im Kontext eines Abhängigkeitsinjektionscontainers wie Castle Windsor, Autofac oder Unity. Dies sind nützliche Werkzeuge, die Sie bei der Arbeit mit Abhängigkeitsinjektionen unterstützen. Sie sind es wert, darüber zu lernen.Bei der Abhängigkeitsinjektion geht es jedoch nur darum, wie Sie die Klassen schreiben, wie im obigen Beispiel, wo wir die Abhängigkeit (InterfaceB) in die Klasse A "injizieren".

+0

Super Scott! Die ID, der Schlüssel und das Token sind in einer Tabelle B in sql vorhanden. Ich würde gerne mehrere Szenarien simulieren, wie zum Beispiel leer, Nicht-Null und Null-ID. 64bit keys..Wie auch immer ich nicht einen zusätzlichen Konstruktor mit InterfaceB als Parameter schreiben möchte, da ich Abhängigkeit es injiziere und lieber Quellcode nicht auch berühren würde Ich möchte es in einem Teil von CI überprüfen. Wenn ich Moq benutze, kann ich zwei separate Mocks IA und IB erstellen und Erwartungen setzen und einen Moq in einen anderen injizieren, wenn B in A zusammengesetzt ist. Wie erreiche ich das, wenn ja? – user2791094

+0

Wenn Sie diese Art der Abwärtskompatibilität benötigen, machen Sie den Konstruktorparameter optional und geben Sie einen Standardwert an. Sie können Ihre Klassen "manuell" zusammenstellen, um sie bei Bedarf zu testen. Erstellen Sie ein Objekt und fügen Sie das gewünschte Objekt für einen Test ein und injizieren Sie das andere für den anderen Test. Sie können Methoden schreiben, um die Testobjekte für Sie zu erstellen. –

0

Tatsächlich ist es mit Typemock möglich, Ihre Methode zu testen, ohne Ihren Quellcode zu ändern. Sie können B als zukünftige Instanz vorstellen, bevor Sie eine Instanz von A erstellen. Dann können Sie das Methodenverhalten von B einfach ändern.

Zum Beispiel:

[TestMethod,Isolated] 
public void AllBMethodReturnStrings_WillReturnSuccess() 
{ 
    // Arrange 
    // Mocking future B's instance 
    var fakeIB = Isolate.Fake.NextInstance<B>(); 
    var realA = new A(); 

    Isolate.WhenCalled(()=> fakeIB.GetId()).WillReturn("fakeID"); 
    Isolate.WhenCalled(() => fakeIB.GetKey()).WillReturn("fakeKey"); 
    Isolate.WhenCalled(() => fakeIB.GetToken()).WillReturn("success"); 

    // Act 
    var result = realA.functionA(); 

    // Assert 
    Assert.AreEqual("success", result); 
}