Seberapa dalam tes unit Anda?

88

Hal yang saya temukan tentang TDD adalah membutuhkan waktu untuk menyiapkan pengujian Anda dan secara alami malas. Saya selalu ingin menulis kode sesedikit mungkin. Hal pertama yang tampaknya saya lakukan adalah menguji konstruktor saya telah menyetel semua properti tetapi apakah ini berlebihan?

Pertanyaan saya adalah pada tingkat perincian apa Anda menulis tes unit?

..dan apakah ada kasus pengujian yang terlalu banyak?

Johnno Nolan
sumber

Jawaban:

221

Saya dibayar untuk kode yang berfungsi, bukan untuk pengujian, jadi filosofi saya adalah menguji sesedikit mungkin untuk mencapai tingkat kepercayaan tertentu (Saya menduga tingkat kepercayaan ini tinggi dibandingkan dengan standar industri, tetapi itu bisa saja keangkuhan) . Jika saya biasanya tidak membuat kesalahan (seperti mengatur variabel yang salah dalam konstruktor), saya tidak mengujinya. Saya cenderung memahami kesalahan pengujian, jadi saya ekstra hati-hati ketika saya memiliki logika dengan persyaratan yang rumit. Saat membuat kode dalam tim, saya mengubah strategi saya untuk menguji kode dengan cermat yang secara kolektif cenderung salah.

Orang yang berbeda akan memiliki strategi pengujian yang berbeda berdasarkan filosofi ini, tetapi tampaknya masuk akal bagi saya mengingat pemahaman yang belum matang tentang bagaimana pengujian dapat paling sesuai dengan loop dalam pengkodean. Sepuluh atau dua puluh tahun dari sekarang kita mungkin akan memiliki teori yang lebih universal tentang tes mana yang harus ditulis, tes mana yang tidak ditulis, dan bagaimana membedakannya. Sementara itu, eksperimen tampak teratur.

Kent Beck
sumber
40
Dunia tidak mengira bahwa Kent Beck akan mengatakan ini! Ada legiun pengembang yang dengan patuh mengejar cakupan 100% karena mereka pikir itulah yang akan dilakukan Kent Beck! Saya telah memberi tahu banyak yang Anda katakan, dalam buku XP Anda, bahwa Anda tidak selalu mengikuti Test First secara religius. Tapi aku juga kaget.
Charlie Flowers
6
Sebenarnya tidak setuju, karena kode yang dihasilkan developer bukan miliknya sendiri, dan sprint berikutnya, orang lain akan mengubahnya dan melakukan kesalahan yang Anda "tidak tahu". Juga TDD Anda memikirkan tes terlebih dahulu. Jadi jika Anda melakukan TDD dengan asumsi menguji sebagian kode, Anda melakukannya dengan salah
Ricardo Rodrigues
2
Saya tidak tertarik dengan liputan. Saya sangat tertarik dengan seberapa sering Tn. Beck melakukan kode yang tidak ditulis sebagai tanggapan atas kegagalan tes.
sheldonh
1
@RicardoRodrigues, Anda tidak dapat menulis tes untuk mencakup kode yang akan ditulis orang lain nanti. Itu tanggung jawab mereka.
Kief
2
Bukan itu yang saya tulis, baca dengan cermat; Saya menulis bahwa jika Anda menulis tes untuk mencakup hanya sebagian dari kode ANDA sendiri, meninggalkan bagian yang tidak tertutup di mana "Anda tahu Anda tidak membuat kesalahan" dan bagian-bagian itu diubah dan tidak memiliki tes yang tepat, Anda memiliki masalah di sana, dan itu sama sekali bukan TDD.
Ricardo Rodrigues
20

Tulis pengujian unit untuk hal-hal yang Anda perkirakan akan rusak, dan untuk kasus edge. Setelah itu, kasus uji harus ditambahkan saat laporan bug masuk - sebelum menulis perbaikan untuk bug. Pengembang kemudian dapat yakin bahwa:

  1. Bug sudah diperbaiki;
  2. Bug tidak akan muncul kembali.

Per komentar terlampir - Saya kira pendekatan untuk menulis tes unit ini dapat menyebabkan masalah, jika banyak bug, dari waktu ke waktu, ditemukan di kelas tertentu. Di sinilah keleluasaan bermanfaat - menambahkan pengujian unit hanya untuk bug yang cenderung muncul kembali, atau di mana kemunculannya kembali akan menyebabkan masalah serius. Saya telah menemukan bahwa ukuran pengujian integrasi dalam pengujian unit dapat membantu dalam skenario ini - pengujian kode jalur kode yang lebih tinggi dapat mencakup jalur kode yang lebih rendah.

Dominic Rodger
sumber
Dengan banyaknya bug yang saya tulis ini bisa menjadi anti pola. Dengan 100-an tes pada kode di mana ada yang rusak, ini bisa berarti bahwa tes Anda menjadi tidak dapat dibaca dan ketika saatnya tiba untuk menulis ulang tes itu bisa menjadi overhead.
Johnno Nolan
@ JohnNolan: Apakah keterbacaan tes itu penting? IMHO tidak, setidaknya untuk tes regresi khusus bug ini. Jika Anda sering menulis ulang pengujian, Anda mungkin menguji pada level yang terlalu rendah - idealnya antarmuka Anda harus tetap relatif stabil bahkan jika implementasi Anda berubah, dan Anda harus menguji pada level antarmuka (meskipun saya menyadari dunia nyata sering tidak. t seperti itu ...: - /) Jika antarmuka Anda berubah secara besar-besaran, saya lebih suka menghapus sebagian besar atau semua pengujian khusus bug ini daripada menulis ulang.
j_random_hacker
@j_random_hacker Ya, tentu saja keterbacaan itu penting. Tes adalah bentuk dokumentasi dan sama pentingnya dengan kode produksi. Saya setuju bahwa membatalkan tes untuk perubahan besar adalah hal yang baik (tm) dan tes harus dilakukan di tingkat antarmuka.
Johnno Nolan
19

Semuanya harus dibuat sesederhana mungkin, tetapi tidak lebih sederhana. - A. Einstein

Salah satu hal yang paling disalahpahami tentang TDD adalah kata pertama di dalamnya. Uji. Itulah mengapa BDD muncul. Karena orang kurang paham kalau D yang pertama itu yang penting, yaitu Driven. Kita semua cenderung berpikir sedikit tentang Pengujian, dan sedikit tentang penggerak desain. Dan saya rasa ini adalah jawaban yang tidak jelas untuk pertanyaan Anda, tetapi Anda mungkin harus mempertimbangkan bagaimana menjalankan kode Anda, alih-alih apa yang sebenarnya Anda uji; Itu adalah sesuatu yang dapat dibantu oleh Alat Cakupan. Desain adalah masalah yang lebih besar dan lebih bermasalah.

kitofr
sumber
Ya itu tidak jelas ... Apakah ini berarti sebagai konstruktor bukanlah perilaku bagian, kita tidak boleh mengujinya. Tetapi saya harus menguji MyClass.DoSomething ()?
Johnno Nolan
Tergantung pada: P ... uji konstruksi sering kali merupakan awal yang baik saat mencoba menguji kode warisan. Tetapi saya mungkin (dalam banyak kasus) akan meninggalkan tes konstruksi, ketika mulai merancang sesuatu dari awal.
kitofr
Ini didorong pengembangan, bukan desain yang didorong. Artinya, dapatkan dasar yang berfungsi, tulis pengujian untuk memverifikasi fungsionalitas, teruskan pengembangan. Saya hampir selalu menulis tes saya tepat sebelum saya memfaktorkan beberapa kode untuk pertama kalinya.
Evan Plaice
Saya akan mengatakan bahwa D terakhir, Desain, adalah kata yang dilupakan orang, sehingga kehilangan fokus. Dalam desain yang digerakkan oleh pengujian, Anda menulis kode sebagai respons atas pengujian yang gagal. Jika Anda melakukan desain berbasis pengujian, berapa banyak kode yang belum teruji yang akan Anda dapatkan?
sheldonh
15

Bagi mereka yang mengusulkan pengujian "semuanya": sadari bahwa "pengujian penuh" metode seperti int square(int x)membutuhkan sekitar 4 miliar kasus pengujian dalam bahasa umum dan lingkungan yang khas.

Bahkan, itu bahkan lebih buruk dari itu: metode void setX(int newX)juga wajib tidak mengubah nilai-nilai dari setiap anggota lain selain x- kamu mencobai bahwa obj.y, obj.z, dll semua tetap tidak berubah setelah memanggil obj.setX(42);?

Praktis hanya untuk menguji sebagian dari "semuanya". Setelah Anda menerima ini, akan lebih cocok untuk mempertimbangkan untuk tidak menguji perilaku yang sangat mendasar. Setiap programmer memiliki distribusi kemungkinan lokasi bug; pendekatan cerdasnya adalah memfokuskan energi Anda pada wilayah pengujian tempat Anda memperkirakan kemungkinan bug menjadi tinggi.

j_random_hacker
sumber
9

Jawaban klasiknya adalah "uji apa pun yang mungkin bisa merusak". Saya menafsirkannya sebagai arti bahwa pengujian setter dan getter yang tidak melakukan apa pun kecuali set atau get mungkin terlalu banyak pengujian, tidak perlu meluangkan waktu. Kecuali jika IDE Anda yang menuliskannya untuk Anda, Anda mungkin juga melakukannya.

Jika konstruktor Anda tidak menyetel properti dapat menyebabkan kesalahan nanti, maka pengujian bahwa properti sudah disetel tidak berlebihan.

Dennis S.
sumber
yup dan ini adalah bind untuk kelas dengan banyak properti dan banyak pembatas.
Johnno Nolan
Masalah yang lebih sepele adalah (seperti lupa memasukkan anggota ke nol), semakin banyak waktu yang dibutuhkan untuk men-debugnya.
Im
5

Saya menulis tes untuk menutupi asumsi kelas yang akan saya tulis. Tes menegakkan persyaratan. Intinya, jika x tidak pernah bisa menjadi 3, misalnya, saya akan memastikan ada tes yang mencakup persyaratan itu.

Selalu, jika saya tidak menulis tes untuk menutupi suatu kondisi, itu akan muncul nanti selama pengujian "manusia". Saya pasti akan menulis satu, tapi saya lebih suka menangkap mereka lebih awal. Saya pikir intinya adalah bahwa pengujian itu membosankan (mungkin) tetapi perlu. Saya menulis cukup banyak tes untuk diselesaikan tetapi tidak lebih dari itu.

itsmatt
sumber
5

Bagian dari masalah dengan melewatkan pengujian sederhana sekarang adalah di masa depan, refactoring dapat membuat properti sederhana itu menjadi sangat rumit dengan banyak logika. Saya pikir ide terbaiknya adalah Anda dapat menggunakan Tes untuk memverifikasi persyaratan modul. Jika ketika Anda lulus X Anda harus mendapatkan Y kembali, maka itulah yang ingin Anda uji. Kemudian ketika Anda mengubah kodenya nanti, Anda dapat memverifikasi bahwa X memberi Anda Y, dan Anda dapat menambahkan tes untuk A memberi Anda B, ketika persyaratan itu ditambahkan nanti.

Saya telah menemukan bahwa waktu yang saya habiskan selama tes menulis pengembangan awal terbayar dalam perbaikan bug pertama atau kedua. Kemampuan untuk mengambil kode yang belum Anda lihat dalam 3 bulan dan cukup yakin bahwa perbaikan Anda mencakup semua kasus, dan "mungkin" tidak merusak apa pun adalah sangat berharga. Anda juga akan menemukan bahwa pengujian unit akan membantu triase bug jauh melampaui pelacakan tumpukan, dll. Melihat bagaimana bagian individu dari aplikasi bekerja dan gagal memberikan wawasan yang sangat besar tentang mengapa mereka bekerja atau gagal secara keseluruhan.

Matt
sumber
4

Dalam kebanyakan kasus, saya akan mengatakan, jika ada logika di sana, ujilah. Ini termasuk konstruktor dan properti, terutama ketika lebih dari satu hal diatur dalam properti.

Sehubungan dengan terlalu banyak pengujian, itu bisa diperdebatkan. Beberapa orang akan mengatakan bahwa segala sesuatu harus diuji ketahanannya, yang lain mengatakan bahwa untuk pengujian yang efisien, hanya hal-hal yang mungkin rusak (yaitu logika) yang harus diuji.

Saya lebih condong ke kamp kedua, hanya dari pengalaman pribadi, tetapi jika seseorang memutuskan untuk menguji semuanya, saya tidak akan mengatakan itu terlalu banyak ... mungkin sedikit berlebihan untuk saya, tetapi tidak terlalu banyak untuk mereka.

Jadi, Tidak - saya akan mengatakan tidak ada yang namanya pengujian "terlalu banyak" dalam arti umum, hanya untuk individu.

Menggoreng
sumber
3

Test Driven Development berarti Anda berhenti membuat kode ketika semua tes Anda lulus.

Jika Anda tidak memiliki pengujian untuk sebuah properti, lalu mengapa Anda harus menerapkannya? Jika Anda tidak menguji / menentukan perilaku yang diharapkan jika terjadi penetapan "ilegal", apa yang harus dilakukan properti?

Oleh karena itu saya benar-benar untuk menguji setiap perilaku yang harus ditunjukkan oleh kelas. Termasuk properti "primitif".

Untuk mempermudah pengujian ini, saya membuat NUnit sederhana TestFixtureyang menyediakan poin ekstensi untuk menyetel / mendapatkan nilai dan mengambil daftar nilai yang valid dan tidak valid serta memiliki satu pengujian untuk memeriksa apakah properti berfungsi dengan benar. Menguji satu properti bisa terlihat seperti ini:

[TestFixture]
public class Test_MyObject_SomeProperty : PropertyTest<int>
{

    private MyObject obj = null;

    public override void SetUp() { obj = new MyObject(); }
    public override void TearDown() { obj = null; }

    public override int Get() { return obj.SomeProperty; }
    public override Set(int value) { obj.SomeProperty = value; }

    public override IEnumerable<int> SomeValidValues() { return new List() { 1,3,5,7 }; }
    public override IEnumerable<int> SomeInvalidValues() { return new List() { 2,4,6 }; }

}

Menggunakan lambda dan atribut, ini bahkan dapat ditulis dengan lebih ringkas. Saya mengumpulkan MBUnit bahkan memiliki beberapa dukungan asli untuk hal-hal seperti itu. Intinya adalah bahwa kode di atas menangkap maksud dari properti.

PS: Mungkin PropertyTest juga harus memiliki cara untuk memeriksa bahwa properti lain pada objek tidak berubah. Hmm .. kembali ke papan gambar.

David Schmitt
sumber
Saya pergi ke presentasi di mbUnit. Itu terlihat sangat bagus.
Johnno Nolan
Tetapi David, izinkan saya bertanya kepada Anda: apakah Anda terkejut dengan tanggapan Kent Beck di atas? Apakah jawabannya membuat Anda bertanya-tanya apakah Anda harus memikirkan kembali pendekatan Anda? Bukan karena ada yang punya "jawaban dari atas", tentu saja. Tapi Kent dianggap sebagai salah satu pendukung inti tes pertama. Penny untuk pikiran Anda!
Charlie Flowers
@Charly: Tanggapan Kent sangat pragmatis. Saya "hanya" mengerjakan proyek di mana saya akan mengintegrasikan kode dari berbagai sumber dan saya ingin memberikan tingkat kepercayaan yang sangat tinggi.
David Schmitt
Karena itu, saya berusaha keras untuk memiliki tes yang lebih sederhana daripada kode yang diuji dan tingkat detail ini mungkin hanya sepadan dalam tes integrasi di mana semua generator, modul, aturan bisnis, dan validator bersatu.
David Schmitt
1

Saya membuat tes unit untuk mencapai cakupan maksimum yang memungkinkan. Jika saya tidak dapat menjangkau beberapa kode, saya melakukan refactor hingga cakupannya penuh

Setelah selesai tes menulis membutakan, saya biasanya menulis satu kasus tes yang mereproduksi setiap bug

Saya terbiasa memisahkan antara pengujian kode dan pengujian integrasi. Selama pengujian integrasi, (yang juga merupakan pengujian unit tetapi pada kelompok komponen, jadi tidak persis untuk pengujian unit) saya akan menguji persyaratan untuk diterapkan dengan benar.

Lorenzo Boccaccia
sumber
1

Jadi, semakin saya mengarahkan pemrograman saya dengan menulis tes, semakin sedikit saya khawatir tentang tingkat granualitas pengujian. Melihat ke belakang, sepertinya saya melakukan hal yang paling sederhana untuk mencapai tujuan saya dalam memvalidasi perilaku . Ini berarti saya menghasilkan lapisan kepercayaan bahwa kode saya melakukan apa yang saya minta, namun ini tidak dianggap sebagai jaminan mutlak bahwa kode saya bebas bug. Saya merasa bahwa keseimbangan yang benar adalah menguji perilaku standar dan mungkin satu atau dua kasus tepi kemudian beralih ke bagian selanjutnya dari desain saya.

Saya menerima bahwa ini tidak akan mencakup semua bug dan menggunakan metode pengujian tradisional lainnya untuk menangkapnya.

Johnno Nolan
sumber
0

Umumnya, saya memulai dari yang kecil, dengan input dan output yang saya tahu harus berfungsi. Kemudian, saat saya memperbaiki bug, saya menambahkan lebih banyak tes untuk memastikan hal-hal yang telah saya perbaiki diuji. Ini organik, dan bekerja dengan baik untuk saya.

Bisakah Anda menguji terlalu banyak? Mungkin, tetapi mungkin lebih baik untuk melakukan kesalahan di sisi kehati-hatian secara umum, meskipun itu akan tergantung pada seberapa kritis aplikasi Anda.

Tim Sullivan
sumber
0

Saya pikir Anda harus menguji semua yang ada di "inti" logika bisnis Anda. Getter ans Setter juga karena mereka dapat menerima nilai negatif atau nilai null yang mungkin tidak ingin Anda terima. Jika Anda punya waktu (selalu bergantung pada atasan Anda), ada baiknya untuk menguji logika bisnis lain dan semua pengontrol yang memanggil objek ini (Anda beralih dari pengujian unit ke pengujian integrasi secara perlahan).

Patrick Desjardins
sumber
0

Saya tidak menguji unit metode penyetel / pengambil sederhana yang tidak memiliki efek samping. Tapi saya melakukan tes unit setiap metode publik lainnya. Saya mencoba membuat tes untuk semua kondisi batas di algorthims saya dan memeriksa cakupan tes unit saya.

Ini banyak pekerjaan tapi saya pikir itu sepadan. Saya lebih suka menulis kode (bahkan menguji kode) daripada melangkah melalui kode dalam debugger. Saya menemukan siklus kode-build-deploy-debug sangat memakan waktu dan semakin lengkap pengujian unit yang telah saya integrasikan ke dalam build saya, semakin sedikit waktu yang saya habiskan untuk melalui siklus code-build-deploy-debug.

Anda tidak mengatakan mengapa arsitektur Anda kodekan juga. Tapi untuk Java saya menggunakan Maven 2 , JUnit , DbUnit , Cobertura , & EasyMock .

Brian Matthews
sumber
Saya tidak mengatakan yang mana sebagai pertanyaan yang cukup bahasa-agnostik.
Johnno Nolan
Pengujian unit dalam TDD tidak hanya mencakup Anda saat Anda menulis kode, tetapi juga melindungi dari orang yang mewarisi kode Anda dan kemudian menganggap masuk akal untuk memformat nilai di dalam getter!
Paxic
0

Semakin banyak saya membacanya, semakin saya pikir beberapa unit test hanya seperti beberapa pola: Bau bahasa yang tidak memadai.

Saat Anda perlu menguji apakah pengambil trivial Anda benar-benar mengembalikan nilai yang benar, itu karena Anda mungkin mencampurkan nama pengambil dan nama variabel anggota. Masukkan 'attr_reader: name' dari ruby, dan ini tidak dapat terjadi lagi. Tidak mungkin di java.

Jika pengambil Anda tidak sepele, Anda masih dapat menambahkan tes untuk itu.


sumber
Saya setuju bahwa menguji seorang getter itu sepele. Namun saya mungkin cukup bodoh untuk lupa mengaturnya dalam konstruktor. Oleh karena itu diperlukan suatu tes. Pikiranku berubah sejak aku mengajukan pertanyaan itu. Lihat jawaban saya stackoverflow.com/questions/153234/how-deep-are-your-unit-tests/…
Johnno Nolan
1
Sebenarnya, saya berpendapat bahwa dalam beberapa hal, pengujian unit secara keseluruhan adalah bau masalah bahasa. Bahasa yang mendukung kontrak (kondisi sebelum / sesudah pada metode) seperti Eiffel, masih memerlukan beberapa pengujian unit, tetapi mereka membutuhkannya lebih sedikit. Dalam praktiknya, bahkan kontrak sederhana membuatnya sangat mudah untuk menemukan bug: ketika kontrak suatu metode putus, bug biasanya ada dalam metode itu.
Damien Pollet
@Damien: Mungkin tes unit dan kontrak benar-benar hal yang sama yang menyamar? Yang saya maksud adalah, bahasa yang "mendukung" kontrak pada dasarnya memudahkan untuk menulis potongan kode - tes - yang (secara opsional) dijalankan sebelum dan sesudah potongan kode lainnya, benar? Jika tata bahasanya cukup sederhana, bahasa yang tidak mendukung kontrak secara native dapat dengan mudah diperluas untuk mendukungnya dengan menulis preprocessor, bukan? Atau adakah beberapa hal yang dapat dilakukan oleh satu pendekatan (kontrak atau pengujian unit) tetapi yang lain tidak bisa melakukannya?
j_random_hacker
0

Uji kode sumber yang membuat Anda khawatir.

Tidak berguna untuk menguji bagian kode yang sangat Anda yakini, selama Anda tidak membuat kesalahan di dalamnya.

Uji perbaikan bug, jadi ini adalah kali pertama dan terakhir Anda memperbaiki bug.

Uji untuk mendapatkan keyakinan bagian kode yang tidak jelas, sehingga Anda menciptakan pengetahuan.

Uji sebelum pemfaktoran ulang berat dan sedang, agar Anda tidak merusak fitur yang ada.

kastil 1971
sumber
0

Jawaban ini lebih untuk mencari tahu berapa banyak pengujian unit yang digunakan untuk metode tertentu yang Anda tahu ingin Anda uji unit karena kekritisan / pentingnya. Dengan menggunakan teknik Pengujian Jalur Dasar oleh McCabe, Anda dapat melakukan hal berikut untuk secara kuantitatif memiliki keyakinan cakupan kode yang lebih baik daripada "cakupan pernyataan" atau "cakupan cabang" yang sederhana:

  1. Tentukan nilai Cyclomatic Complexity dari metode Anda yang ingin Anda uji unit (Visual Studio 2010 Ultimate misalnya dapat menghitung ini untuk Anda dengan alat analisis statis; jika tidak, Anda dapat menghitungnya secara manual melalui metode flowgraph - http://users.csc. calpoly.edu/~jdalbey/206/Lectures/BasisPathTutorial/index.html )
  2. Buat daftar basis set jalur independen yang mengalir melalui metode Anda - lihat tautan di atas untuk contoh diagram alur
  3. Siapkan pengujian unit untuk setiap jalur basis independen yang ditentukan di langkah 2
JD
sumber
Anda akan melakukan ini untuk setiap metode? Sungguh?
Kristopher Johnson