4 Stimmen

Constructor verwendet das Mock-Objekt, wie kann ich eine Methode isoliert testen?

Ich habe eine Klasse, die wie folgt aussieht:

class MyClass {

  private IDependency dep;

  public MyClass(IDependency dep) {
    this.dep = dep;
    this.dep.Reset();
  }

  public void Clear() {
    this.dep.Reset();
  }
}

Wie kann ich testen, dass die Reset-Methode ordnungsgemäß in der Clear-Methode aufgerufen wird, während ignoriert wird, was der Konstruktor tut?

Mein Moq-Test sieht folgendermaßen aus:

MockRepository mocks = new MockRepository(MockBehavior.Default);
var dep = mocks.Create<IDependency>();

dep.Setup(s => s.Reset());

MyClass myclass = new MyClass(dep.Object);
myclass.Clear():

state.Verify(s => s.Reset(), Times.Exactly(1));

Es schlägt fehl, weil Reset zweimal aufgerufen wurde (einmal im Konstruktor und einmal in der Clear-Methode).

2voto

Tim Lloyd Punkte 37154

Ich hatte gehofft, dass es einen besseren Weg gibt, aber das Spotting zeichnet alle Anrufe an Reset also mit einem Standard Verify wird immer 2 zurückgegeben. Im Folgenden wird ein separater Zähler verwaltet, was nicht sehr elegant ist. Wenn es einen eingebauten Weg gibt, dies mit Moq zu tun, würde ich das gerne wissen.

int clearResetCount = 0;

Mock<IDependency> dep = new Mock<IDependency>();

MyClass myclass = new MyClass(dep.Object);

dep.Setup(s => s.Reset()).Callback(() => clearResetCount++);

Assert.AreEqual(0, clearResetCount, "Expected clear reset count - before.");

myclass.Clear();

Assert.AreEqual(1, clearResetCount, "Expected clear reset count - after.");

2voto

bryanbcook Punkte 15032

Wie von anderen vorgeschlagen, können Sie Ihre eigene Attrappe erstellen oder eine Reihe von Erwartungen an die Abhängigkeit stellen.

So können Sie beispielsweise überprüfen, ob Ihre Methode aufgerufen wurde:

var mock = new Mock<IDependency>();
var subject = new MyClass(mock.Object);

subject.Clear();

mock.Verify( dep => dep.Reset(), Times.AtMost(2));

Es sollte jedoch darauf hingewiesen werden, dass Arbeit innerhalb des Konstruktors ist ein bekannter Codegeruch und dieser Geruch wird noch verstärkt, wenn Sie versuchen, Tests zu schreiben.

Die Tatsache, dass Ihr Konstruktor diese Methode in der Abhängigkeit aufrufen muss, deutet darauf hin, dass dieses Objekt zu viele Informationen über die Implementierungsdetails der Abhängigkeit kennt. Dies verstößt gegen das Open-Closed-Prinzip und schließt Sie von Szenarien aus, in denen Sie nicht wollen, dass die Reset-Methode bei der Initialisierung aufgerufen wird.

Bedenken Sie auch, dass jede Klasse oder jeder Test, der das konkrete Objekt MyClass als Dummy-Parameter verwendet, ein initialisiertes Mock benötigt, da sonst eine NullReferenceException ausgelöst wird. Dies führt zu einem beträchtlichen Mehraufwand bei der Erstellung Ihrer Tests und zu einem Grad an Anfälligkeit, der zu langfristiger Wartung und falschen Negativmeldungen in Ihren Tests führt. Die einzige Möglichkeit, dies zu umgehen, besteht darin, alles zu einer Schnittstelle zu machen, was zwar effektiv ist, aber auch nicht die beste langfristige Strategie darstellt.

Gemäß http://googletesting.blogspot.com/2009/07/separation-anxiety.html Die Verwendung einer Factory würde einen Teil dieser Kopplung reduzieren und eine bessere Wiederverwendung dieses Objekts ermöglichen.

1voto

enemymaker Punkte 11

Ich bin auf das gleiche Problem gestoßen.

Gehen Sie wie folgt vor, damit der Mock nur das Verhalten der Clear-Methode aufzeichnet:

MockRepository mocks = new MockRepository(MockBehavior.Default);
var dep = mocks.Create<IDependency>();

MyClass myclass = new MyClass(dep.Object);

// setting up the mock just before calling the method under test
// will ignore any prior call to IDependency.Reset
int resetTimes = 0;
dep.Setup(s => s.Reset()).Callback(() => resetTimes++);

myclass.Clear();

mocks.VerifyAll();
Assert.That(resetTimes, Is.EqualTo(1));

0voto

Pete Punkte 12142

Anstelle eines Mock-Objekts können Sie einen Spion schreiben. Dies erfordert etwas mehr Codierung, aber der Test ist einfacher zu lesen.

class DependencySpy : IDependency {
    public int ResetCallCount { get; private set; }
    public void Reset() { ResetCallCount++; }
    public void ClearResetCallCount() { ResetCallCount = 0; }
}

Der Test könnte wie folgt geschrieben werden

// Setup
var spy = new DependencySpy;
MyClass myclass = new MyClass(spy);
spy.ClearResetCallCount();
// Exercise
myclass.Clear();
// Validate
Assert.AreEqual(1, spy.ResetCallCount);

-1voto

Vadim Punkte 17727

Sie können Reflection verwenden, um das private Feld dep auf Ihr gespottetes Objekt zu setzen. Rufen Sie dann einfach die Clear-Methode auf und testen Sie den Abhängigkeitsaufruf.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X