TDD dan lengkapi cakupan tes di mana kasus uji eksponensial diperlukan

18

Saya sedang mengerjakan pembanding daftar untuk membantu menyortir daftar hasil pencarian yang tidak teratur per persyaratan yang sangat spesifik dari klien kami. Persyaratan membutuhkan algoritme relevansi yang diperingkat dengan aturan berikut sesuai kepentingan:

  1. Pencocokan tepat pada nama
  2. Semua kata permintaan pencarian dalam nama atau sinonim dari hasil
  3. Beberapa kata permintaan pencarian dalam nama atau sinonim dari hasil (% menurun)
  4. Semua kata dari permintaan pencarian dalam deskripsi
  5. Beberapa kata dari permintaan pencarian dalam deskripsi (% menurun)
  6. Tanggal modifikasi terakhir turun

Pilihan desain alami untuk pembanding ini tampaknya adalah peringkat skor berdasarkan kekuatan 2. Jumlah aturan yang kurang penting tidak akan pernah lebih dari kecocokan positif pada aturan kepentingan yang lebih tinggi. Ini dicapai dengan skor berikut:

  1. 32
  2. 16
  3. 8 (skor tie-breaker sekunder berdasarkan% menurun)
  4. 4
  5. 2 (skor tie-breaker sekunder berdasarkan% menurun)
  6. 1

Dalam semangat TDD saya memutuskan untuk memulai dengan unit test saya terlebih dahulu. Untuk memiliki test case untuk setiap skenario yang unik akan ada setidaknya 63 case test unik yang tidak mempertimbangkan test case tambahan untuk logika tie breaker sekunder pada aturan 3 dan 5. Ini sepertinya sombong.

Namun, tes yang sebenarnya akan lebih sedikit. Berdasarkan aturan aktual itu sendiri, aturan tertentu memastikan bahwa aturan yang lebih rendah akan selalu benar (Misalnya. Ketika 'Semua kata Permintaan Pencarian muncul dalam deskripsi' maka aturan 'Beberapa kata Permintaan Pencarian muncul dalam deskripsi' akan selalu benar). Masih apakah tingkat upaya dalam menulis setiap kasus uji ini sepadan? Apakah ini tingkat pengujian yang biasanya diperlukan ketika berbicara tentang cakupan pengujian 100% dalam TDD? Jika tidak maka apa yang akan menjadi strategi pengujian alternatif yang dapat diterima?

maple_shaft
sumber
1
Skenario ini dan yang serupa adalah alasan mengapa saya mengembangkan "TMatrixTestCase" dan enumerator di mana Anda dapat menulis kode tes satu kali dan memasukkannya dua atau lebih array yang berisi input dan hasil yang diharapkan.
Marjan Venema

Jawaban:

17

Pertanyaan Anda menyiratkan bahwa TDD ada hubungannya dengan "menulis semua kasus uji terlebih dahulu". IMHO itu bukan "dalam semangat TDD", sebenarnya itu menentangnya . Ingatlah bahwa TDD adalah singkatan dari "test driven development", jadi Anda hanya perlu test case yang benar-benar "mendorong" implementasi Anda, tidak lebih. Dan selama implementasi Anda tidak dirancang sedemikian rupa sehingga jumlah blok kode tumbuh secara eksponensial dengan setiap persyaratan baru, Anda tidak akan memerlukan jumlah kasus uji yang eksponensial juga. Dalam contoh Anda, siklus TDD mungkin akan terlihat seperti ini:

  • mulailah dengan persyaratan pertama dari daftar Anda: kata-kata dengan "Pencocokan tepat pada nama" harus mendapatkan skor lebih tinggi dari yang lainnya
  • sekarang Anda menulis test case pertama untuk ini (misalnya: kata yang cocok dengan kueri yang diberikan) dan menerapkan jumlah minimal kode kerja yang membuat lulus tes itu
  • tambahkan test case kedua untuk persyaratan pertama (misalnya: kata yang tidak cocok dengan kueri), dan sebelum menambahkan case test baru , ubah kode Anda yang ada sampai tes kedua berlalu
  • tergantung pada detail implementasi Anda, jangan ragu untuk menambahkan lebih banyak kasus uji, misalnya, kueri kosong, kata kosong dll. (ingat: TDD adalah pendekatan kotak putih , Anda dapat memanfaatkan fakta bahwa Anda mengetahui implementasi Anda saat Anda rancang kasus uji Anda).

Kemudian, mulailah dengan persyaratan ke-2:

  • "Semua kata permintaan pencarian dalam nama atau sinonim dari hasil" harus mendapatkan skor lebih rendah dari "Pencocokan tepat pada nama", tetapi skor lebih tinggi dari yang lainnya.
  • sekarang buat kasus uji untuk persyaratan baru ini, seperti di atas, satu demi satu, dan terapkan bagian selanjutnya dari kode Anda setelah setiap pengujian baru. Jangan lupa refactor di antaranya, kode Anda dan juga kasus pengujian Anda.

Inilah hasilnya : ketika Anda menambahkan kasus uji untuk persyaratan / nomor kategori "n", Anda hanya perlu menambahkan tes untuk memastikan bahwa skor kategori "n-1" lebih tinggi daripada skor untuk kategori "n" . Anda tidak perlu menambahkan kasus uji untuk setiap kombinasi kategori 1, ..., n-1, karena tes yang telah Anda tulis sebelumnya akan memastikan bahwa skor kategori tersebut masih dalam urutan yang benar.

Jadi ini akan memberi Anda sejumlah kasus uji yang tumbuh kira-kira linier dengan jumlah persyaratan, tidak secara eksponensial.

Doc Brown
sumber
1
Saya sangat suka jawaban ini. Ini memberikan strategi pengujian unit yang jelas dan ringkas untuk mendekati masalah ini dengan mengingat TDD. Anda memecahnya dengan cukup baik.
maple_shaft
@maple_shaft: terima kasih, dan saya sangat suka pertanyaan Anda. Saya ingin menambahkan bahwa saya kira bahkan dengan pendekatan Anda merancang semua kasus uji pertama, teknik klasik membangun kelas kesetaraan untuk tes mungkin cukup untuk mengurangi pertumbuhan eksponensial (tapi saya tidak berhasil sejauh ini).
Doc Brown
13

Pertimbangkan menulis kelas yang melewati daftar kondisi yang telah ditentukan dan mengalikan skor saat ini dengan 2 untuk setiap cek yang berhasil.

Ini dapat diuji dengan sangat mudah, hanya dengan menggunakan beberapa tes yang diejek.

Kemudian Anda dapat menulis kelas untuk setiap kondisi dan hanya ada 2 tes untuk setiap kasus.

Saya tidak benar-benar memahami kasus penggunaan Anda, tetapi mudah-mudahan contoh ini akan membantu.

public class ScoreBuilder
{
    private ISingleScorableCondition[] _conditions;
    public ScoreBuilder (ISingleScorableCondition[] conditions)
    {
        _conditions = conditions;
    }

    public int GetScore(string toBeScored)
    {
        foreach (var condition in _conditions)
        {
            if (_conditions.Test(toBeScored))
            {
                // score this somehow
            }
        }
    }
}

public class ExactMatchOnNameCondition : ISingleScorableCondition
{
    private IDataSource _dataSource;
    public ExactMatchOnNameCondition(IDataSource dataSource)
    {
        _dataSource = dataSource;
    }

    public bool Test(string toBeTested)
    {
        return _dataSource.Contains(toBeTested);
    }
}

// etc

Anda akan melihat bahwa pengujian 2 kondisi Anda dengan cepat turun ke 4+ (kondisi 2 *). 20 jauh lebih sombong daripada 64. Dan jika Anda menambahkan satu lagi nanti, Anda tidak perlu mengubah APAPUN dari kelas yang ada (prinsip buka-tutup), jadi Anda tidak perlu menulis 64 tes baru, Anda hanya perlu untuk menambahkan kelas lain dengan 2 tes baru dan menyuntikkannya ke kelas ScoreBuilder Anda.

pdr
sumber
Pendekatan yang menarik. Sepanjang waktu pikiran saya tidak pernah mempertimbangkan pendekatan OOP karena saya terjebak dalam pikiran komponen komparator tunggal. Saya benar-benar tidak mencari saran algoritma tetapi ini sangat membantu.
maple_shaft
4
@maple_shaft: Tidak, tetapi Anda sedang mencari saran TDD dan algoritma semacam ini sangat cocok untuk menghilangkan pertanyaan apakah itu sepadan dengan usaha, dengan sangat mengurangi upaya. Mengurangi kompleksitas adalah kunci untuk TDD.
pdr
+1, jawaban yang bagus. Meskipun saya percaya bahkan tanpa solusi canggih seperti itu, jumlah kasus uji tidak harus tumbuh secara eksponensial (lihat jawaban saya di bawah).
Doc Brown
Saya tidak menerima jawaban Anda karena saya merasa jawaban lain lebih baik menjawab pertanyaan yang sebenarnya, tetapi saya sangat menyukai pendekatan desain Anda sehingga saya menerapkannya seperti yang Anda sarankan. Ini memang mengurangi kompleksitas dan membuatnya lebih bisa diperluas dalam jangka panjang.
maple_shaft
4

Masih apakah tingkat upaya dalam menulis setiap kasus uji ini sepadan?

Anda harus mendefinisikan "sepadan". Masalah dengan skenario semacam ini adalah bahwa tes akan memiliki pengembalian manfaat yang semakin berkurang. Tentu saja tes pertama yang Anda tulis akan sangat bermanfaat. Itu dapat menemukan kesalahan yang jelas dalam prioritas dan bahkan hal-hal seperti kesalahan penguraian ketika mencoba untuk memecah kata-kata.

Tes kedua akan sia-sia karena mencakup jalur yang berbeda melalui kode, mungkin memeriksa hubungan prioritas lain.

Tes ke-63 mungkin tidak akan sia-sia karena itu adalah sesuatu yang Anda yakin 99,99% dicakup oleh logika kode Anda atau tes lain.

Apakah ini tingkat pengujian yang biasanya diperlukan ketika berbicara tentang cakupan pengujian 100% dalam TDD?

Pemahaman saya adalah cakupan 100% berarti semua jalur kode dijalankan. Ini tidak berarti Anda melakukan semua kombinasi aturan Anda, tetapi semua jalur yang berbeda yang dapat dihilangkan kode Anda (seperti yang Anda tunjukkan, beberapa kombinasi tidak dapat ada dalam kode). Tetapi karena Anda melakukan TDD, belum ada "kode" untuk memeriksa jalur. Surat proses akan mengatakan make all 63+.

Secara pribadi, saya menemukan cakupan 100% menjadi mimpi pipa. Selain itu, itu tidakagmatis. Tes unit ada untuk melayani Anda, bukan sebaliknya. Ketika Anda melakukan lebih banyak tes, Anda mendapatkan hasil yang semakin berkurang (kemungkinan bahwa tes mencegah bug + keyakinan bahwa kode tersebut benar). Tergantung pada apa kode Anda menentukan di mana pada skala geser Anda berhenti membuat tes. Jika kode Anda menjalankan reaktor nuklir, maka mungkin semua 63+ tes layak dilakukan. Jika kode Anda mengatur arsip musik Anda, maka Anda mungkin bisa mendapatkan lebih sedikit.

Telastyn
sumber
"cakupan" biasanya mengacu pada cakupan kode (setiap baris kode dieksekusi) atau cakupan cabang (setiap cabang dieksekusi setidaknya sekali dalam arah yang memungkinkan). Untuk kedua jenis pertanggungan tidak perlu untuk 64 kasus uji yang berbeda. Setidaknya, tidak dengan implementasi serius yang tidak mengandung bagian kode individual untuk masing-masing 64 kasus. Jadi cakupan 100% sepenuhnya mungkin.
Doc Brown
@DocBrown - tentu saja, dalam hal ini - hal-hal lain lebih sulit / tidak mungkin untuk diuji; pertimbangkan jalur pengecualian memori. Bukankah semua 64 diperlukan dalam 'oleh surat' TDD untuk menegakkan perilaku diuji bodoh pelaksanaannya?
Telastyn
baik, komentar saya terkait dengan pertanyaan, dan jawaban Anda memberi kesan bahwa mungkin sulit untuk mendapatkan cakupan 100% dalam kasus OP . Saya meragukan itu. Dan saya setuju kepada Anda bahwa orang dapat membuat kasus di mana cakupan 100% lebih sulit untuk dicapai, tetapi itu tidak ditanyakan.
Doc Brown
4

Saya berpendapat bahwa ini adalah kasus yang sempurna untuk TDD.

Anda memiliki serangkaian kriteria untuk diuji, dengan uraian logis dari kasus-kasus itu. Dengan asumsi Anda akan mengujinya sekarang atau nanti, tampaknya masuk akal untuk mengambil hasil yang diketahui dan membangunnya, memastikan Anda, pada kenyataannya, mencakup setiap aturan secara mandiri.

Plus, Anda bisa mencari tahu saat Anda pergi jika menambahkan aturan pencarian baru melanggar aturan yang ada. Jika Anda melakukan semua ini di akhir pengkodean, Anda mungkin menjalankan risiko lebih besar karena harus mengubah satu untuk memperbaikinya, yang merusak yang lain, yang melanggar yang lain ... Dan, Anda belajar saat Anda menerapkan aturan apakah desain Anda valid. atau perlu tweaker.

Wonko the Sane
sumber
1

Saya bukan penggemar ketat menafsirkan cakupan pengujian 100% sebagai menulis spesifikasi terhadap setiap metode tunggal atau menguji setiap permutasi kode. Melakukan ini secara fanatik cenderung mengarah pada desain yang digerakkan oleh tes dari kelas Anda yang tidak merangkum logika bisnis dengan baik dan menghasilkan tes / spesifikasi yang umumnya tidak berarti dalam hal menggambarkan logika bisnis yang didukung. Sebagai gantinya, saya fokus pada penataan tes seperti aturan bisnis itu sendiri dan berusaha untuk melakukan setiap cabang kondisional dari kode dengan tes dengan harapan eksplisit bahwa tes mudah dimengerti oleh tester seperti umumnya kasus penggunaan akan dan benar-benar menggambarkan aturan bisnis yang diterapkan.

Dengan ide ini dalam pikiran, saya akan menguji unit menyeluruh 6 faktor peringkat Anda terdaftar dalam satu sama lain ditindaklanjuti dengan 2 atau 3 tes gaya integrasi yang memastikan Anda menggulung hasil Anda ke nilai peringkat keseluruhan yang diharapkan. Sebagai contoh, kasus # 1, Pencocokan Tepat pada Nama, saya akan memiliki setidaknya dua unit tes untuk menguji kapan tepatnya dan kapan tidak dan bahwa dua skenario mengembalikan skor yang diharapkan. Jika case sensitif, maka juga case untuk menguji "Pencocokan Tepat" vs. "pencocokan tepat" dan mungkin variasi input lainnya seperti tanda baca, spasi tambahan, dll. Juga mengembalikan skor yang diharapkan.

Setelah saya bekerja melalui semua faktor individu yang berkontribusi pada skor peringkat, saya pada dasarnya menganggap ini berfungsi dengan benar di tingkat integrasi dan fokus pada memastikan faktor-faktor gabungan mereka dengan benar berkontribusi pada skor peringkat akhir yang diharapkan.

Dengan asumsi kasus # 2 / # 3 dan # 4 / # 5 digeneralisasikan ke metode dasar yang sama, tetapi dengan melewati bidang yang berbeda, Anda hanya perlu menulis satu set unit test untuk metode yang mendasarinya dan menulis tes unit tambahan sederhana untuk menguji spesifik bidang (judul, nama, deskripsi, dll.) dan penilaian di anjak yang ditentukan, jadi ini lebih lanjut mengurangi redundansi keseluruhan upaya pengujian Anda.

Dengan pendekatan ini, pendekatan yang dijelaskan di atas mungkin akan menghasilkan 3 atau 4 unit tes pada kasus # 1, mungkin 10 spesifikasi pada beberapa / semua w / sinonim dicatat - ditambah 4 spesifikasi pada penilaian kasus # 2 - # 5 dan 2 yang benar ke 3 spesifikasi pada tanggal terakhir yang diperintahkan peringkat, kemudian 3 hingga 4 tes tingkat integrasi yang mengukur semua 6 kasus yang digabungkan dalam cara yang mungkin (lupakan kasus tepi yang tidak jelas untuk saat ini kecuali jika Anda melihat dengan jelas masalah dalam kode Anda yang perlu dilakukan untuk memastikan kondisi itu ditangani) atau memastikan tidak dilanggar / rusak oleh revisi nanti. Itu menghasilkan sekitar 25 atau lebih spesifikasi untuk menjalankan 100% dari kode yang ditulis (meskipun Anda tidak secara langsung memanggil 100% dari metode yang ditulis).

Michael Lang
sumber
1

Saya tidak pernah menjadi penggemar cakupan tes 100%. Dalam pengalaman saya, jika ada sesuatu yang cukup sederhana untuk diuji dengan hanya satu atau dua kasus uji, maka itu cukup sederhana untuk jarang gagal. Ketika gagal, biasanya karena perubahan arsitektur yang memerlukan perubahan uji.

Yang sedang berkata, untuk persyaratan seperti Anda, saya selalu menguji unit secara menyeluruh, bahkan pada proyek pribadi di mana tidak ada yang membuat saya, karena itu adalah kasus ketika pengujian unit menghemat waktu dan kejengkelan Anda. Semakin banyak tes unit yang diperlukan untuk menguji sesuatu, semakin banyak tes unit waktu akan menghemat.

Itu karena Anda hanya bisa memegang begitu banyak hal di kepala Anda sekaligus. Jika Anda mencoba menulis kode yang berfungsi untuk 63 kombinasi berbeda, seringkali sulit untuk memperbaiki satu kombinasi tanpa merusak yang lain. Anda akhirnya menguji kombinasi lain secara manual berulang kali. Pengujian manual jauh lebih lambat, yang membuat Anda tidak ingin menjalankan kembali setiap kombinasi yang mungkin setiap kali Anda melakukan perubahan. Itu membuat Anda lebih mungkin melewatkan sesuatu, dan lebih cenderung membuang waktu untuk menempuh jalan yang tidak cocok untuk semua kasus.

Selain menghemat waktu dibandingkan dengan pengujian manual, ada ketegangan mental jauh lebih sedikit, yang membuatnya lebih mudah untuk fokus pada masalah yang dihadapi tanpa khawatir tentang memperkenalkan regresi secara tidak sengaja. Itu memungkinkan Anda bekerja lebih cepat dan lebih lama tanpa kelelahan. Menurut pendapat saya, manfaat kesehatan mental saja sepadan dengan biaya pengujian kode kompleks, bahkan jika itu tidak menghemat waktu Anda.

Karl Bielefeldt
sumber