Saya telah menulis beberapa tes unit untuk beberapa kode baru di kantor, dan mengirimkannya untuk tinjauan kode. Salah satu rekan kerja saya membuat komentar tentang mengapa saya meletakkan variabel yang digunakan dalam sejumlah tes di luar ruang lingkup untuk tes.
Kode yang saya posting pada dasarnya
import org.junit.Test;
public class FooUnitTests {
@Test
public void testConstructorWithValidName() {
new Foo(VALID_NAME);
}
@Test(expected = IllegalArgumentException.class)
public void testConstructorWithNullName() {
new Foo(null);
}
@Test(expected = IllegalArgumentException.class)
public void testConstructorWithZeroLengthName() {
new Foo("");
}
@Test(expected = IllegalArgumentException.class)
public void testConstructorWithLeadingAndTrailingWhitespaceInName() {
final String name = " " + VALID_NAME + " ";
final Foo foo = new Foo(name);
assertThat(foo.getName(), is(equalTo(VALID_NAME)));
}
private static final String VALID_NAME = "name";
}
Usulan perubahannya pada dasarnya
import org.junit.Test;
public class FooUnitTests {
@Test
public void testConstructorWithValidName() {
final String name = "name";
final Foo foo = new Foo(name);
}
@Test(expected = IllegalArgumentException.class)
public void testConstructorWithNullName() {
final String name = null;
final Foo foo = new Foo(name);
}
@Test(expected = IllegalArgumentException.class)
public void testConstructorWithZeroLengthName() {
final String name = "";
final Foo foo = new Foo(name);
}
@Test(expected = IllegalArgumentException.class)
public void testConstructorWithLeadingAndTrailingWhitespaceInName() {
final String name = " name ";
final Foo foo = new Foo(name);
final String actual = foo.getName();
final String expected = "name";
assertThat(actual, is(equalTo(expected)));
}
}
Di mana segala sesuatu yang diperlukan dalam ruang lingkup tes, didefinisikan dalam ruang lingkup tes.
Beberapa keuntungan yang ia perdebatkan adalah
- Setiap tes mandiri.
- Setiap tes dapat dieksekusi dalam isolasi, atau dalam agregasi, dengan hasil yang sama.
- Peninjau tidak harus menggulir ke mana pun parameter ini dideklarasikan untuk mencari nilainya.
Beberapa kelemahan dari metodenya yang saya katakan adalah
- Meningkatkan duplikasi kode
- Dapat menambahkan noise ke pikiran pengulas jika ada beberapa tes serupa dengan nilai yang berbeda ditentukan (mis. Tes dengan
doFoo(bar)
hasil dalam satu nilai, sementara panggilan yang sama memiliki hasil yang berbeda karenabar
didefinisikan berbeda dalam metode itu).
Selain dari konvensi, apakah ada kelebihan / kekurangan menggunakan metode yang lain?
java
unit-testing
tdd
Zymus
sumber
sumber
Jawaban:
Anda harus terus melakukan apa yang Anda lakukan.
Mengulangi diri Anda sama buruknya dengan gagasan dalam kode uji seperti dalam kode bisnis, dan untuk alasan yang sama. Rekan Anda telah disesatkan oleh gagasan bahwa setiap tes harus mandiri . Itu cukup benar, tetapi "mandiri" tidak berarti harus berisi semua yang dibutuhkan dalam tubuh metodenya. Ini hanya berarti bahwa itu harus memberikan hasil yang sama apakah itu dijalankan secara terpisah atau sebagai bagian dari suite, dan terlepas dari urutan tes di suite. Dengan kata lain, kode tes harus memiliki semantik yang sama terlepas dari kode apa yang telah dijalankan sebelumnya ; itu tidak harus memiliki semua kode yang diperlukan dibundel secara tekstual .
Penggunaan kembali konstanta, kode pengaturan dan sejenisnya meningkatkan kualitas kode uji dan tidak membahayakan kemandiriannya, jadi sebaiknya Anda terus melakukannya.
sumber
Tergantung. Secara umum, memiliki konstanta berulang yang dinyatakan hanya di satu tempat adalah hal yang baik.
Namun, dalam contoh Anda, tidak ada gunanya mendefinisikan anggota
VALID_NAME
dengan cara yang ditunjukkan. Diasumsikan dalam varian kedua, seorang pengelola mengubah teksname
pada tes pertama, tes kedua kemungkinan besar akan tetap valid, dan sebaliknya. Sebagai contoh, mari kita asumsikan Anda juga ingin menguji huruf besar dan kecil, tetapi juga menyimpan kasus uji hanya dengan huruf kecil, dengan sesedikit mungkin kasus uji. Alih-alih menambahkan tes baru, Anda dapat mengubah tes pertama menjadidan masih menyimpan tes yang tersisa.
Bahkan, memiliki banyak tes tergantung pada variabel seperti itu, dan mengubah nilai
VALID_NAME
nanti mungkin secara tidak sengaja merusak beberapa tes Anda di waktu kemudian. Dan dalam situasi seperti itu, Anda memang dapat meningkatkan kemandirian tes Anda dengan tidak memperkenalkan konstanta buatan dengan tes yang berbeda tergantung pada itu.Namun, apa yang ditulis @KilianFoth tentang tidak mengulangi diri Anda sendiri juga benar: ikuti prinsip KERING dalam kode uji seperti halnya Anda melakukannya dalam kode bisnis. Misalnya, perkenalkan konstanta atau variabel anggota ketika penting untuk kode pengujian Anda bahwa nilainya sama di setiap tempat.
Contoh Anda menunjukkan bagaimana tes cenderung mengulang kode inisialisasi, itu adalah tempat khas untuk memulai dengan refactoring. Jadi, Anda dapat mempertimbangkan untuk mengulangi pengulangan
menjadi fungsi yang terpisah
karena itu akan memungkinkan Anda untuk mengubah tanda tangan
Foo
konstruktor pada titik waktu lebih mudah (karena ada lebih sedikit tempat di mananew Foo
sebenarnya disebut).sumber
Sebenarnya saya ... tidak akan memilih keduanya , tetapi saya akan memilih rekan kerja Anda untuk contoh sederhana Anda.
Hal pertama yang pertama, saya cenderung lebih menyukai inlining nilai daripada melakukan penugasan satu kali. Ini meningkatkan keterbacaan kode bagi saya karena saya tidak harus memecah kereta pemikiran saya menjadi beberapa pernyataan penugasan, kemudian membaca baris kode di mana mereka digunakan. Karena itu, saya akan lebih memilih pendekatan Anda daripada rekan kerja Anda, dengan sedikit nitpick yang
testConstructorWithLeadingAndTrailingWhitespaceInName()
mungkin saja:Itu membawa saya ke penamaan. Biasanya, jika tes Anda menyatakan
Exception
akan dilemparkan, nama tes Anda harus menggambarkan perilaku itu juga untuk kejelasan. Menggunakan contoh di atas, jadi bagaimana jika saya memiliki spasi putih tambahan dalam nama ketika saya membangunFoo
objek saya ? Dan bagaimana itu terkait dengan melemparIllegalArgumentException
?Berdasarkan pada
null
dan""
tes, saya menduga di sini bahwa hal yang samaException
dilemparkan kali ini untuk menelepongetName()
. Jika memang demikian, mungkin menetapkan nama metode pengujian sebagaicallingGetNameWithWhitespacePaddingThrows()
(IllegalArgumentException
- yang saya pikir akan menjadi bagian dari output JUnit, maka opsional) akan lebih baik. Jika memiliki spasi putih dalam nama selama instantiation juga akan membuang yang samaException
, maka saya akan melewatkan pernyataan sepenuhnya juga, untuk memperjelas bahwa itu dalam perilaku konstruktor.Namun, saya pikir ada cara yang lebih baik untuk melakukan sesuatu di sini ketika Anda mendapatkan lebih banyak permutasi untuk input yang valid dan tidak valid, yaitu pengujian parameter . Apa yang Anda uji persis seperti itu: Anda membuat banyak
Foo
objek dengan argumen konstruktor yang berbeda, dan kemudian menyatakannya gagal pada instantiasi atau pemanggilangetName()
. Karena itu, mengapa tidak membiarkan JUnit melakukan iterasi dan perancah untuk Anda? Anda dapat, dalam istilah awam, memberi tahu JUnit, "ini adalah nilai yang ingin saya buatFoo
objeknya", dan kemudian minta metode pengujian Anda menegaskan untukIllegalArgumentException
di tempat yang tepat. Sayangnya, karena cara pengujian parameter JUnit tidak bekerja dengan baik dengan pengujian untuk kasus-kasus yang sukses dan luar biasa dalam pengujian yang sama (sejauh yang saya ketahui), Anda mungkin perlu melompati sedikit lingkaran dengan struktur berikut:Memang, ini terlihat kikuk untuk apa yang dinyatakan sebagai pengujian empat nilai sederhana, dan tidak banyak terbantu dengan implementasi JUnit yang agak membatasi. Dalam hal ini, saya menemukan pendekatan TestNG
@DataProvider
menjadi lebih berguna, dan itu layak untuk dilihat lebih dekat jika Anda mempertimbangkan tes parameter.Begini cara seseorang dapat menggunakan TestNG untuk pengujian unit Anda (menggunakan Java 8
Stream
untuk menyederhanakanIterator
konstruksi). Beberapa manfaatnya adalah, tetapi tidak terbatas pada:@DataProvider
yang menyediakan berbagai jenis input.sumber