Pertanyaan ini telah mengganggu saya selama beberapa hari, dan rasanya seperti beberapa praktik yang saling bertentangan.
Contoh
Iterasi 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;
}
}
Sekarang, ketika menguji ini, saya akan memalsukan IFooConnection dan IBarConnection, dan menggunakan Dependency Injection (DI) ketika instantiating FooDao.
Saya dapat mengubah implementasinya, tanpa mengubah fungsionalitasnya.
Iterasi 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);
}
}
Sekarang, saya tidak akan menulis pembuat ini, tetapi bayangkan itu melakukan hal yang sama yang dilakukan FooDao sebelumnya. Ini hanya refactoring, jadi jelas, ini tidak mengubah fungsi, dan pengujian saya masih berjalan.
IFooBuilder adalah Internal, karena itu hanya ada untuk melakukan pekerjaan untuk perpustakaan, yaitu itu bukan bagian dari API.
Satu-satunya masalah adalah, saya tidak lagi mematuhi Ketergantungan Inversi. Jika saya menulis ulang ini untuk memperbaiki masalah itu, mungkin akan terlihat seperti ini.
Iterasi 3
public class FooDao : IFooDao
{
private IFooBuilder fooBuilder;
public FooDao(IFooBuilder fooBuilder)
{
this.fooBuilder = fooBuilder;
}
public Foo GetFoo(int id)
{
return fooBuilder.Build(id);
}
}
Ini harus dilakukan. Saya telah mengubah konstruktor, jadi tes saya perlu diubah untuk mendukung ini (atau sebaliknya), ini bukan masalah saya.
Agar ini bekerja dengan DI, FooBuilder dan IFooBuilder harus bersifat publik. Ini berarti saya harus menulis tes untuk FooBuilder, karena tiba-tiba menjadi bagian dari API perpustakaan saya. Masalah saya adalah, klien perpustakaan hanya boleh menggunakannya melalui desain API yang saya maksudkan, IFooDao, dan tes saya bertindak sebagai klien. Jika saya tidak mengikuti Ketergantungan Pembalikan, pengujian dan API saya lebih bersih.
Dengan kata lain, yang saya pedulikan sebagai klien, atau sebagai tes, adalah untuk mendapatkan Foo yang benar, bukan bagaimana itu dibangun.
Solusi
Haruskah saya tidak peduli, tulis tes untuk FooBuilder meskipun hanya publik untuk menyenangkan DI? - Mendukung iterasi 3
Haruskah saya menyadari bahwa memperluas API adalah kelemahan dari Dependency Inversion, dan sangat jelas mengapa saya memilih untuk tidak mematuhinya di sini? - Mendukung iterasi 2
Apakah saya terlalu menekankan memiliki API yang bersih? - Mendukung iterasi 3
EDIT: Saya ingin memperjelas, bahwa masalah saya bukan "Bagaimana cara menguji internal?", Melainkan sesuatu seperti "Bisakah saya tetap internal, dan masih mematuhi DIP, dan haruskah saya?".
IFooBuilder
perlu untuk umum? FooBuilder
hanya perlu diekspos ke sistem DI melalui semacam "dapatkan implementasi default" metode yang mengembalikan IFooBuilder
dan dapat tetap internal.
public
untuk API perpustakaan dan public
untuk pengujian". Atau dalam bentuk lain "Saya ingin menjaga metode pribadi agar tidak muncul di API, bagaimana cara menguji metode pribadi itu?". Jawabannya selalu "hidup dengan API publik yang lebih luas", "hidup dengan tes melalui API publik", atau "buat metode internal
dan buat mereka terlihat oleh kode tes".