Diese Frage hat mich einige Tage lang beschäftigt, und es scheint, als würden sich mehrere Praktiken widersprechen.
Beispiel
Iteration 1
public class FooDao : IFooDao
{
private IFooConnection fooConnection;
private IBarConnection barConnection;
public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
{
this.fooConnection = fooConnection;
this.barConnection = barConnection;
}
public Foo GetFoo(int id)
{
Foo foo = fooConection.get(id);
Bar bar = barConnection.get(foo);
foo.bar = bar;
return foo;
}
}
Wenn ich dies teste, fälsche ich IFooConnection und IBarConnection und verwende Dependency Injection (DI), wenn ich FooDao instanziiere.
Ich kann die Implementierung ändern, ohne die Funktionalität zu ändern.
Iteration 2
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooConnection fooConnection, IBarConnection barConnection)
{
this.fooBuilder = new FooBuilder(fooConnection, barConnection);
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
Jetzt werde ich diesen Builder nicht schreiben, aber stellen Sie sich vor, er macht dasselbe wie FooDao zuvor. Dies ist nur ein Refactoring, daher ändert dies offensichtlich nichts an der Funktionalität, und so besteht mein Test immer noch.
IFooBuilder ist intern, da es nur für die Arbeit in der Bibliothek vorhanden ist, dh nicht Teil der API.
Das einzige Problem ist, dass ich die Abhängigkeitsinversion nicht mehr einhalte. Wenn ich dies umschreibe, um das Problem zu beheben, sieht es möglicherweise so aus.
Iteration 3
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooBuilder fooBuilder)
{
this.fooBuilder = fooBuilder;
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
Das sollte es tun. Ich habe den Konstruktor geändert, daher muss mein Test geändert werden, um dies zu unterstützen (oder umgekehrt). Dies ist jedoch nicht mein Problem.
Damit dies mit DI funktioniert, müssen FooBuilder und IFooBuilder öffentlich sein. Dies bedeutet, dass ich einen Test für FooBuilder schreiben sollte, da dieser plötzlich Teil der API meiner Bibliothek wurde. Mein Problem ist, dass Clients der Bibliothek sie nur über mein beabsichtigtes API-Design IFooDao verwenden sollten und meine Tests als Clients fungieren. Wenn ich Dependency Inversion nicht folge, sind meine Tests und die API sauberer.
Mit anderen Worten, alles, was mich als Client oder als Test interessiert, ist, das richtige Foo zu erhalten, nicht wie es aufgebaut ist.
Lösungen
Sollte es mich einfach nicht interessieren, schreibe die Tests für FooBuilder, obwohl es nur öffentlich ist, DI zu gefallen? - Unterstützt Iteration 3
Sollte ich erkennen, dass das Erweitern der API ein Nachteil von Dependency Inversion ist, und mir klar sein, warum ich mich hier dagegen entschieden habe? - Unterstützt Iteration 2
Mache ich zu viel Wert auf eine saubere API? - Unterstützt Iteration 3
EDIT: Ich möchte klarstellen, dass mein Problem nicht "Wie teste ich Interna?" Ist, sondern so etwas wie "Kann ich es intern behalten und trotzdem DIP einhalten, und sollte ich?".
IFooBuilder
öffentlich sein? FooBuilder
müssen nur über eine Art "Get Default Implementation" -Methode für das DI-System verfügbar gemacht werden, die eine zurückgibt IFooBuilder
und daher intern bleiben kann.
public
Bibliotheks-APIs und public
Tests machen". Oder in der anderen Form "Ich möchte Methoden privat halten, um zu vermeiden, dass sie in der API angezeigt werden. Wie teste ich diese privaten Methoden?". Die Antworten lauten immer "Live mit der breiteren öffentlichen API", "Live mit Tests über die öffentliche API" oder "Methoden internal
erstellen und für den Testcode sichtbar machen".