Saya tidak menganggap diri saya seorang ahli DDD tetapi, sebagai arsitek solusi, cobalah untuk menerapkan praktik terbaik bila memungkinkan. Saya tahu ada banyak diskusi di sekitar pro dan kontra tentang "gaya" setter no (publik) di DDD dan saya bisa melihat kedua sisi argumen. Masalah saya adalah bahwa saya bekerja pada tim dengan beragam keterampilan, pengetahuan, dan pengalaman yang artinya saya tidak bisa percaya bahwa setiap pengembang akan melakukan hal-hal dengan cara yang "benar". Misalnya, jika objek domain kami dirancang sedemikian rupa sehingga perubahan pada keadaan internal objek dilakukan oleh suatu metode tetapi memberikan setter properti publik, seseorang akan tak terhindarkan mengatur properti alih-alih memanggil metode tersebut. Gunakan contoh ini:
public class MyClass
{
public Boolean IsPublished
{
get { return PublishDate != null; }
}
public DateTime? PublishDate { get; set; }
public void Publish()
{
if (IsPublished)
throw new InvalidOperationException("Already published.");
PublishDate = DateTime.Today;
Raise(new PublishedEvent());
}
}
Solusi saya adalah membuat setter properti menjadi privat yang dimungkinkan karena ORM yang kami gunakan untuk menghidrasi objek menggunakan refleksi sehingga dapat mengakses seter pribadi. Namun, ini menimbulkan masalah ketika mencoba menulis unit test. Misalnya, ketika saya ingin menulis unit test yang memverifikasi persyaratan yang tidak dapat kami terbitkan kembali, saya perlu menunjukkan bahwa objek tersebut telah diterbitkan. Saya pasti dapat melakukan ini dengan memanggil Terbitkan dua kali, tetapi kemudian pengujian saya mengasumsikan bahwa Publikasi diimplementasikan dengan benar untuk panggilan pertama. Sepertinya agak bau.
Mari buat skenario sedikit lebih nyata dengan kode berikut:
public class Document
{
public Document(String title)
{
if (String.IsNullOrWhiteSpace(title))
throw new ArgumentException("title");
Title = title;
}
public String ApprovedBy { get; private set; }
public DateTime? ApprovedOn { get; private set; }
public Boolean IsApproved { get; private set; }
public Boolean IsPublished { get; private set; }
public String PublishedBy { get; private set; }
public DateTime? PublishedOn { get; private set; }
public String Title { get; private set; }
public void Approve(String by)
{
if (IsApproved)
throw new InvalidOperationException("Already approved.");
ApprovedBy = by;
ApprovedOn = DateTime.Today;
IsApproved = true;
Raise(new ApprovedEvent(Title));
}
public void Publish(String by)
{
if (IsPublished)
throw new InvalidOperationException("Already published.");
if (!IsApproved)
throw new InvalidOperationException("Cannot publish until approved.");
PublishedBy = by;
PublishedOn = DateTime.Today;
IsPublished = true;
Raise(new PublishedEvent(Title));
}
}
Saya ingin menulis unit test yang memverifikasi:
- Saya tidak dapat menerbitkan kecuali Dokumen telah disetujui
- Saya tidak dapat menerbitkan kembali Dokumen
- Saat dipublikasikan, nilai-nilai PublishedBy dan PublishedOn diatur dengan benar
- Saat dipublikasikan, Publikasi muncul
Tanpa akses ke setter, saya tidak bisa memasukkan objek ke dalam kondisi yang diperlukan untuk melakukan tes. Membuka akses ke setter mengalahkan tujuan mencegah akses.
Bagaimana Anda menyelesaikan (d) masalah ini?
sumber
Jawaban:
Jika Anda tidak dapat menempatkan objek ke dalam negara yang dibutuhkan untuk melakukan tes, maka Anda tidak dapat menempatkan objek ke dalam negara dalam kode produksi, sehingga tidak perlu untuk menguji bahwa negara. Jelas, ini tidak benar dalam kasus Anda, Anda dapat menempatkan objek Anda ke dalam kondisi yang diperlukan, panggil saja Menyetujui.
Saya tidak dapat mempublikasikan kecuali jika Dokumen telah disetujui: tulis tes yang memanggil publikasikan sebelum menelepon menyetujui menyebabkan kesalahan yang tepat tanpa mengubah status objek.
Saya tidak dapat menerbitkan kembali Dokumen: tulis tes yang menyetujui objek, lalu panggil terbitkan setelah berhasil, tetapi yang kedua kali menyebabkan kesalahan yang tepat tanpa mengubah status objek.
Saat dipublikasikan, nilai-nilai PublishedBy dan PublishedOn diatur dengan benar: tulis tes yang disetujui panggilan lalu panggil terbitkan, menyatakan bahwa status objek berubah dengan benar
Saat dipublikasikan, diterbitkanEvent dinaikkan: kaitkan ke sistem acara dan atur bendera untuk memastikan namanya
Anda juga perlu menulis tes untuk menyetujui.
Dengan kata lain, jangan menguji hubungan antara bidang internal dan IsPublished dan IsApproved, tes Anda akan sangat rapuh jika Anda melakukan itu karena mengubah bidang Anda berarti mengubah kode tes Anda, sehingga tes itu akan cukup sia-sia. Alih-alih, Anda harus menguji hubungan antara panggilan metode publik, dengan cara ini, bahkan jika Anda memodifikasi bidang Anda tidak perlu memodifikasi tes.
sumber
setup()
metode --- bukan tes itu sendiri.approve()
entah bagaimana rapuh, namun tergantung padasetApproved(true)
entah bagaimana tidak?approve()
adalah ketergantungan yang sah dalam pengujian karena merupakan ketergantungan pada persyaratan. Jika ketergantungan hanya ada dalam tes, itu akan menjadi masalah lain.push()
danpop()
metode secara mandiri?Namun pendekatan lain adalah membuat konstruktor kelas yang memungkinkan properti internal diatur pada instantiation:
sumber
Salah satu strategi adalah bahwa Anda mewarisi kelas (dalam hal ini Dokumen) dan menulis tes terhadap kelas yang diwarisi. Kelas yang diwariskan memungkinkan beberapa cara untuk mengatur keadaan objek dalam tes.
Dalam C # satu strategi bisa membuat setter internal, kemudian mengekspos internal untuk menguji proyek.
Anda juga dapat menggunakan API kelas seperti yang Anda gambarkan ("Saya pasti bisa melakukan ini dengan memanggil Terbitkan dua kali"). Ini akan menetapkan status objek menggunakan layanan publik objek, sepertinya tidak terlalu bau bagi saya. Dalam contoh Anda, ini mungkin akan menjadi cara saya melakukannya.
sumber
Untuk menguji dalam isolasi absolut perintah dan kueri yang diterima objek domain, saya terbiasa menyediakan setiap tes dengan serialisasi objek dalam keadaan yang diharapkan. Di bagian susunan tes, ini memuat objek untuk menguji dari file yang telah saya siapkan sebelumnya. Pada awalnya saya mulai dengan serialisasi biner, tetapi json terbukti jauh lebih mudah untuk dipertahankan. Ini terbukti bekerja dengan baik, setiap kali isolasi absolut dalam tes memberikan nilai aktual.
sunting hanya sebuah catatan, beberapa kali serialisasi JSON gagal (seperti dalam kasus grafik objek siklik, yang berbau, btw). Dalam situasi seperti itu, saya menyelamatkan serialisasi biner. Agak pragmatis, tetapi berhasil. :-)
sumber
Kamu bilang
dan
dan saya harus berpikir bahwa menggunakan refleksi untuk memintas kontrol akses di kelas Anda bukan apa yang saya gambarkan sebagai "praktik terbaik". Ini akan menjadi sangat lambat juga.
Secara pribadi, saya akan memo kerangka unit test Anda dan pergi dengan sesuatu di kelas - tampaknya Anda sedang menulis tes dari sudut pandang menguji seluruh kelas, yang bagus. Di masa lalu, untuk beberapa komponen rumit yang memerlukan pengujian, saya ave memasukkan kode pengaturan dan pengaturan ke dalam kelas itu sendiri (dulu merupakan pola desain umum untuk memiliki metode pengujian () di setiap kelas), sehingga Anda membuat klien yang hanya instantiate objek dan memanggil metode pengujian yang dapat mengatur dirinya sendiri sesuka Anda tanpa nastiness seperti hacks refleksi.
Jika Anda khawatir tentang kode mengasapi, cukup bungkus metode pengujian di #ifdefs agar hanya tersedia dalam kode debug (mungkin praktik terbaik itu sendiri)
sumber