Unit Test untuk menguji pembuatan Objek Domain

11

Saya memiliki Tes Unit, yang terlihat seperti ini:

[Test]
public void Should_create_person()
{
     Assert.DoesNotThrow(() => new Person(Guid.NewGuid(), new DateTime(1972, 01, 01));
}

Saya menyatakan bahwa objek Orang dibuat di sini yaitu validasi yang tidak gagal. Misalnya, jika Guid adalah nol atau tanggal lahir lebih awal dari 01/01/1900, maka validasi akan gagal dan pengecualian akan dilempar (artinya tes gagal).

Konstruktornya terlihat seperti ini:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

Apakah ini ide yang bagus untuk ujian?

Catatan : Saya mengikuti pendekatan Klasikis untuk Unit Menguji Model Domain jika itu berlaku.

w0051977
sumber
Apakah konstruktor memiliki logika yang layak untuk ditegaskan setelah-selama inisialisasi?
Laiv
2
Jangan pernah repot-repot menguji konstruktor !!! Konstruksi harus lurus ke depan. Apakah Anda mengharapkan gagal dalam Guid.NewGuid (), atau konstruktor DateTime?
ivenxu
@Laiv, silakan lihat pembaruan untuk pertanyaan itu.
w0051977
1
Tidak ada gunanya menerapkan tes seperti yang Anda bagikan. Namun, saya juga akan menguji sebaliknya. Saya akan menguji kasus di mana birthDate menyebabkan kesalahan. Itu adalah invarian dari kelas yang Anda inginkan berada di bawah kontrol dan ujian.
Laiv
3
Tes ini terlihat baik-baik saja, simpan untuk satu hal: nama. Should_create_person? Apa yang harus membuat seseorang? Berikan nama yang bermakna, misalnya Creating_person_with_valid_data_succeeds.
David Arno

Jawaban:

18

Ini adalah tes yang valid (meskipun agak terlalu bersemangat) dan saya kadang-kadang melakukannya untuk menguji logika konstruktor, namun seperti yang disebutkan Laiv dalam komentar Anda harus bertanya pada diri sendiri mengapa.

Jika konstruktor Anda terlihat seperti ini:

public Person(Guid guid, DateTime dob)
{
  this.Guid = guid;
  this.Dob = dob;
}

Apakah ada banyak gunanya menguji apakah itu melempar? Apakah parameter ditetapkan dengan benar, saya bisa mengerti tetapi tes Anda agak berlebihan.

Namun, jika tes Anda melakukan sesuatu seperti ini:

public Person(Guid guid, DateTime dob)
{
  if(guid == default(Guid)) throw new ArgumentException("Guid is invalid");
  if(dob == default(DateTime)) throw new ArgumentException("Dob is invalid");

  this.Guid = guid;
  this.Dob = dob;
}

Maka tes Anda menjadi lebih relevan (karena Anda benar-benar melemparkan pengecualian di suatu tempat dalam kode).

Satu hal yang akan saya katakan, umumnya praktik buruk memiliki banyak logika di konstruktor Anda. Validasi dasar (seperti cek null / default yang saya lakukan di atas) ok. Tetapi jika Anda terhubung ke database dan memuat data seseorang maka di situlah kode mulai benar-benar berbau ...

Karena itu, jika konstruktor Anda layak untuk diuji (karena ada banyak logika), maka mungkin ada hal lain yang salah.

Anda hampir pasti akan memiliki tes lain yang mencakup kelas ini di lapisan logika bisnis, konstruktor dan tugas variabel hampir pasti akan mendapatkan cakupan lengkap dari tes ini. Oleh karena itu mungkin ada gunanya menambahkan tes khusus khusus untuk konstruktor. Namun, tidak ada yang hitam dan putih dan saya tidak akan menentang pengujian ini jika saya meninjaunya - tapi saya akan mempertanyakan apakah mereka menambah banyak nilai di atas dan di luar tes di tempat lain dalam solusi Anda.

Dalam contoh Anda:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

Anda tidak hanya melakukan validasi, tetapi Anda juga memanggil konstruktor dasar. Bagi saya ini memberikan lebih banyak alasan untuk melakukan tes ini karena mereka memiliki logika konstruktor / validasi sekarang dibagi menjadi dua kelas yang mengurangi visibilitas dan meningkatkan risiko perubahan tak terduga.

TLDR

Ada beberapa nilai untuk tes ini, namun logika validasi / penugasan kemungkinan akan ditanggung oleh tes lain dalam solusi Anda. Jika ada banyak logika di konstruktor ini yang memang memerlukan pengujian signifikan maka itu menunjukkan kepada saya ada bau kode jahat yang bersembunyi di sana.

Liath
sumber
@Laith, Silakan lihat pembaruan untuk pertanyaan saya
w0051977
Saya perhatikan bahwa Anda memanggil konstruktor dasar dalam contoh Anda. IMHO ini menambah nilai tes Anda, logika konstruktor sekarang dibagi menjadi dua kelas dan karena itu risiko perubahan sedikit lebih tinggi karena itu memberikan lebih banyak alasan untuk mengujinya.
Liath
"Namun, jika tes Anda melakukan sesuatu seperti ini:" <Bukankah maksudmu "jika konstruktor Anda melakukan sesuatu seperti ini" ?
Kodos Johnson
"Ada beberapa nilai untuk tes ini" - yang menarik bagi saya, nilainya menunjukkan bahwa kita dapat membuat tes ini berlebihan dengan menggunakan kelas baru untuk mewakili orang bodoh (mis. PersonBirthdate) Yang melakukan validasi tanggal lahir. Demikian pula Guidcek dapat diimplementasikan di Idkelas. Ini berarti Anda benar-benar tidak perlu lagi memiliki logika validasi dalam Personkonstruktor karena tidak mungkin untuk membangunnya dengan data yang tidak valid - kecuali untuk nullreferensi. Tentu saja, Anda harus menulis tes untuk dua kelas lainnya :)
Stephen Byrne
12

Sudah jawaban yang bagus di sini, tapi saya pikir satu hal tambahan yang layak disebut.

Ketika melakukan TDD "oleh buku", orang perlu menulis tes pertama yang memanggil konstruktor, bahkan sebelum konstruktor diimplementasikan. Tes itu bisa benar-benar terlihat seperti yang Anda sajikan, bahkan jika akan ada nol logika validasi di dalam implementasi konstruktor.

Perhatikan juga bahwa untuk TDD, seseorang harus menulis tes lain dulu seperti

  Assert.Throws<ArgumentException>(() => new Person(Guid.NewGuid(), 
        new DateTime(1572, 01, 01));

sebelum menambahkan tanda centang DateTime(1900,01,01)ke konstruktor.

Dalam konteks TDD, tes yang ditampilkan sangat masuk akal.

Doc Brown
sumber
Saya tidak mempertimbangkan sudut yang bagus!
Liath
1
Ini menunjukkan kepada saya mengapa bentuk kaku TDD seperti itu adalah buang-buang waktu: tes harus memiliki nilai setelah kode ditulis, atau Anda hanya menulis setiap baris kode dua kali, sekali sebagai pernyataan, dan sekali sebagai kode. Saya berpendapat bahwa konstruktor itu sendiri bukan bagian dari logika yang perlu pengujian; aturan bisnis "orang-orang yang lahir sebelum tahun 1900 tidak boleh diwakili" dapat diuji, dan konstruktornya adalah tempat aturan itu diterapkan, tetapi kapan pengujian konstruktor kosong akan menambah nilai pada proyek?
IMSoP
Apakah ini benar-benar tdd menurut buku? Saya akan membuat instance dan memanggil metodenya segera dalam kode. Kemudian saya akan menulis tes untuk metode itu, dan dengan melakukan itu saya juga harus membuat contoh untuk metode itu, sehingga konstruktor dan metode akan dibahas dalam tes itu. Kecuali dalam konstruktor ada beberapa logika, tetapi bagian itu ditutupi oleh Liath.
Rafał Łużyński
@ RafałŁużyński: TDD "oleh buku" adalah tentang menulis tes terlebih dahulu . Ini sebenarnya berarti untuk selalu menulis tes yang gagal terlebih dahulu (tidak mengkompilasi jumlah sebagai kegagalan juga). Jadi Anda pertama kali menulis tes memanggil konstruktor bahkan ketika tidak ada konstruktor . Kemudian Anda mencoba mengkompilasi (yang gagal), lalu Anda mengimplementasikan konstruktor kosong, mengkompilasi, menjalankan tes, hasil = hijau. Kemudian Anda menulis tes gagal pertama dan menjalankannya - hasil = merah, lalu Anda menambahkan fungsionalitas untuk membuat tes "hijau" lagi, dan seterusnya.
Doc Brown
Tentu saja. Saya tidak bermaksud saya menulis implementasi terlebih dahulu, kemudian tes. Saya hanya menulis "penggunaan" kode itu pada level di atas, lalu menguji kode itu, lalu saya mengimplementasikannya. Saya biasanya melakukan "Diluar TDD".
Rafał Łużyński