Domain saya terdiri dari banyak kelas sederhana yang tidak dapat diubah seperti ini:
public class Person
{
public string FullName { get; }
public string NameAtBirth { get; }
public string TaxId { get; }
public PhoneNumber PhoneNumber { get; }
public Address Address { get; }
public Person(
string fullName,
string nameAtBirth,
string taxId,
PhoneNumber phoneNumber,
Address address)
{
if (fullName == null)
throw new ArgumentNullException(nameof(fullName));
if (nameAtBirth == null)
throw new ArgumentNullException(nameof(nameAtBirth));
if (taxId == null)
throw new ArgumentNullException(nameof(taxId));
if (phoneNumber == null)
throw new ArgumentNullException(nameof(phoneNumber));
if (address == null)
throw new ArgumentNullException(nameof(address));
FullName = fullName;
NameAtBirth = nameAtBirth;
TaxId = taxId;
PhoneNumber = phoneNumber;
Address = address;
}
}
Menulis cek nol dan inisialisasi properti sudah sangat membosankan tetapi saat ini saya menulis tes unit untuk masing-masing kelas untuk memverifikasi bahwa validasi argumen berfungsi dengan benar dan bahwa semua properti diinisialisasi. Ini terasa seperti pekerjaan yang sangat membosankan dengan keuntungan yang tidak sepadan.
Solusi sebenarnya adalah untuk C # untuk mendukung tipe referensi immutability dan non-nullable. Tetapi apa yang dapat saya lakukan untuk memperbaiki situasi sementara itu? Apakah layak menulis semua tes ini? Apakah ide yang baik untuk menulis generator kode untuk kelas seperti itu untuk menghindari tes menulis untuk masing-masing dari mereka?
Inilah yang saya miliki sekarang berdasarkan jawaban.
Saya dapat menyederhanakan cek kosong dan inisialisasi properti agar terlihat seperti ini:
FullName = fullName.ThrowIfNull(nameof(fullName));
NameAtBirth = nameAtBirth.ThrowIfNull(nameof(nameAtBirth));
TaxId = taxId.ThrowIfNull(nameof(taxId));
PhoneNumber = phoneNumber.ThrowIfNull(nameof(phoneNumber));
Address = address.ThrowIfNull(nameof(address));
Menggunakan implementasi berikut oleh Robert Harvey :
public static class ArgumentValidationExtensions
{
public static T ThrowIfNull<T>(this T o, string paramName) where T : class
{
if (o == null)
throw new ArgumentNullException(paramName);
return o;
}
}
Menguji cek nol mudah menggunakan GuardClauseAssertion
dari AutoFixture.Idioms
(terima kasih atas sarannya, Esben Skov Pedersen ):
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var assertion = new GuardClauseAssertion(fixture);
assertion.Verify(typeof(Address).GetConstructors());
Ini dapat dikompresi lebih jauh:
typeof(Address).ShouldNotAcceptNullConstructorArguments();
Menggunakan metode ekstensi ini:
public static void ShouldNotAcceptNullConstructorArguments(this Type type)
{
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var assertion = new GuardClauseAssertion(fixture);
assertion.Verify(type.GetConstructors());
}
sumber
Jawaban:
Saya membuat template t4 untuk kasus-kasus seperti ini. Untuk menghindari menulis banyak boilerplate untuk kelas Immutable.
https://github.com/xaviergonz/T4Immutable T4Immutable adalah template T4 untuk C #. Aplikasi NET yang menghasilkan kode untuk kelas yang tidak dapat diubah.
Khususnya berbicara tentang tes tidak nol maka jika Anda menggunakan ini:
Konstruktornya adalah sebagai berikut:
Karena itu, jika Anda menggunakan JetBrains Annotations untuk pemeriksaan nol, Anda juga dapat melakukan ini:
Dan konstruktornya adalah sebagai berikut:
Juga ada beberapa fitur lebih banyak dari yang ini.
sumber
Anda bisa mendapatkan sedikit peningkatan dengan refactoring sederhana yang dapat meringankan masalah penulisan semua pagar itu. Pertama, Anda memerlukan metode ekstensi ini:
Anda kemudian dapat menulis:
Mengembalikan parameter asli dalam metode ekstensi menciptakan antarmuka yang lancar, sehingga Anda dapat memperluas konsep ini dengan metode ekstensi lainnya jika diinginkan, dan menyatukan semuanya dalam tugas Anda.
Teknik lain lebih elegan dalam konsep, tetapi semakin kompleks dalam eksekusi, seperti mendekorasi parameter dengan
[NotNull]
atribut, dan menggunakan Refleksi seperti ini .Yang mengatakan, Anda mungkin tidak memerlukan semua tes ini, kecuali kelas Anda adalah bagian dari API yang dihadapi publik.
sumber
null
pada argumen ke konstruktor, Anda tidak akan mendapatkan tes yang gagal.throw
luar konstruktor; Anda tidak benar-benar mentransfer kontrol di dalam konstruktor di tempat lain. Itu hanya metode utilitas, dan cara untuk melakukan berbagai hal dengan lancar , jika Anda mau.Dalam jangka pendek, tidak banyak yang dapat Anda lakukan tentang kebosanan menulis tes semacam itu. Namun, ada beberapa bantuan yang datang dengan ekspresi lemparan yang akan diimplementasikan sebagai bagian dari rilis C # (v7) berikutnya, kemungkinan jatuh tempo dalam beberapa bulan ke depan:
Anda dapat bereksperimen dengan membuang ekspresi melalui webapp Try Roslyn .
sumber
Saya terkejut tidak ada yang menyebutkan NullGuard . Belum ada yang tahu. Ini tersedia melalui NuGet dan secara otomatis akan menenun cek nol itu ke IL selama waktu kompilasi.
Jadi kode konstruktor Anda akan menjadi sederhana
dan NullGuard akan menambahkan cek nol itu untuk Anda mentransformasikannya menjadi apa yang Anda tulis.
Perhatikan bahwa NullGuard adalah opt-out , yaitu, ia akan menambahkan cek nol itu untuk setiap metode dan argumen konstruktor, pengambil properti dan penyetel dan bahkan memeriksa nilai metode pengembalian kecuali jika Anda secara eksplisit mengizinkan nilai nol dengan
[AllowNull]
atribut.sumber
[NotNull]
atribut alih-alih tidak membiarkan null menjadi default.Tidak, mungkin juga tidak. Apa kemungkinan Anda akan mengacaukannya? Apa kemungkinan beberapa semantik akan berubah dari bawah Anda? Apa dampaknya jika seseorang mengacaukannya?
Jika Anda menghabiskan banyak waktu membuat tes untuk sesuatu yang jarang akan pecah, dan merupakan perbaikan sepele jika itu ... mungkin tidak sepadan.
Mungkin? Hal semacam itu bisa dilakukan dengan mudah dengan refleksi. Sesuatu yang perlu dipertimbangkan adalah melakukan pembuatan kode untuk kode nyata, sehingga Anda tidak memiliki kelas N yang mungkin memiliki kesalahan manusia. Pencegahan bug> deteksi bug.
sumber
if...throw
mereka akan milikiThrowHelper.ArgumentNull(myArg, "myArg")
, dengan cara itu logikanya masih ada dan Anda tidak terpengaruh dengan cakupan kode. Di manaArgumentNull
metode akan melakukanif...throw
.Apakah layak menulis semua tes ini?
Tidak.
Karena saya cukup yakin Anda telah menguji sifat-sifat tersebut melalui beberapa tes logika lain di mana kelas-kelas tersebut digunakan.
Misalnya, Anda dapat memiliki tes untuk kelas Pabrik yang memiliki pernyataan berdasarkan pada properti tersebut (Contoh dibuat dengan
Name
properti yang ditugaskan dengan benar misalnya).Jika kelas-kelas tersebut terpapar ke API publik yang digunakan oleh beberapa pengguna bagian / akhir ketiga (@EJoshua terima kasih atas perhatiannya), maka tes untuk yang diharapkan
ArgumentNullException
dapat berguna.Sambil menunggu C # 7 Anda dapat menggunakan metode ekstensi
Untuk pengujian, Anda dapat menggunakan satu metode pengujian berparameter yang menggunakan refleksi untuk membuat
null
referensi untuk setiap parameter dan menegaskan pengecualian yang diharapkan.sumber
Anda selalu dapat menulis metode seperti berikut:
Minimal, itu akan menghemat beberapa pengetikan jika Anda memiliki beberapa metode yang memerlukan validasi semacam ini. Tentu saja, solusi ini mengasumsikan bahwa tidak ada parameter metode Anda bisa
null
, tetapi Anda dapat memodifikasi ini untuk mengubahnya jika Anda menginginkannya.Anda juga dapat memperluas ini untuk melakukan validasi tipe spesifik lainnya. Misalnya, jika Anda memiliki aturan bahwa string tidak boleh murni atau kosong, Anda bisa menambahkan kondisi berikut:
Metode GetParameterInfo yang saya rujuk pada dasarnya melakukan refleksi (sehingga saya tidak harus terus mengetik hal yang sama berulang-ulang, yang akan melanggar prinsip KERING ):
sumber
Saya tidak menyarankan untuk melempar pengecualian dari konstruktor. Masalahnya adalah Anda akan mengalami kesulitan untuk menguji ini karena Anda harus melewati parameter yang valid bahkan mereka tidak relevan untuk pengujian Anda.
Misalnya: Jika Anda ingin menguji apakah parameter ketiga melempar pengecualian, Anda harus memasukkan yang pertama dan yang kedua dengan benar. Jadi tes Anda tidak terisolasi lagi. ketika kendala dari parameter pertama dan kedua berubah, test case ini akan gagal bahkan itu menguji parameter ketiga.
Saya menyarankan untuk menggunakan API validasi java dan mengeksternalisasi proses validasi. Saya menyarankan untuk melibatkan empat tanggung jawab (kelas):
Objek saran dengan parameter validasi Java yang dianotasikan dengan metode validasi yang mengembalikan Set ConstraintViolations. Salah satu keuntungannya adalah Anda bisa mengedarkan objek ini tanpa pernyataan yang valid. Anda dapat menunda validasi hingga diperlukan tanpa harus mencoba untuk instantiate objek domain. Objek validasi dapat digunakan dalam berbagai lapisan karena merupakan POJO tanpa pengetahuan khusus lapisan. Itu bisa menjadi bagian dari API publik Anda.
Pabrik untuk objek domain yang bertanggung jawab untuk membuat objek yang valid. Anda meneruskan objek saran dan pabrik akan memanggil validasi dan membuat objek domain jika semuanya baik-baik saja.
Objek domain itu sendiri yang sebagian besar tidak dapat diakses untuk instantiasi untuk pengembang lain. Untuk tujuan pengujian saya sarankan untuk memiliki kelas ini dalam lingkup paket.
Antarmuka domain publik untuk menyembunyikan objek domain konkret dan membuat penyalahgunaan menjadi sulit.
Saya tidak menyarankan untuk memeriksa nilai nol. Segera setelah Anda masuk ke lapisan domain Anda telah menyingkirkan melewati nol atau mengembalikan nol selama Anda tidak memiliki rantai objek yang memiliki ujung atau fungsi pencarian untuk objek tunggal.
Satu hal lain: menulis kode sesedikit mungkin bukan metrik untuk kualitas kode. Pikirkan tentang kompetisi game 32k. Kode itu adalah yang paling ringkas tetapi juga kode paling berantakan yang bisa Anda miliki karena hanya peduli pada masalah teknis dan tidak peduli dengan semantik. Tapi semantik adalah poin yang membuat semuanya komprehensif.
sumber