Apa yang harus dilakukan ketika tes TDD mengungkapkan fungsionalitas baru yang diperlukan dan juga perlu tes?

13

Apa yang Anda lakukan saat menulis tes dan sampai pada titik di mana Anda perlu lulus ujian dan menyadari bahwa Anda memerlukan fungsionalitas tambahan yang harus dipisahkan ke dalam fungsinya sendiri? Fungsi baru itu perlu diuji juga, tetapi siklus TDD mengatakan untuk membuat tes gagal, membuatnya lulus kemudian refactor. Jika saya berada di langkah di mana saya mencoba untuk membuat tes lulus saya tidak seharusnya pergi dan memulai tes gagal lagi untuk menguji fungsionalitas baru yang perlu saya terapkan.

Sebagai contoh, saya menulis kelas titik yang memiliki fungsi WillCollideWith ( LineSegment ) :

public class Point {
    // Point data and constructor ...

    public bool CollidesWithLine(LineSegment lineSegment) {
        Vector PointEndOfMovement = new Vector(Position.X + Velocity.X,
                                               Position.Y + Velocity.Y);
        LineSegment pointPath = new LineSegment(Position, PointEndOfMovement);
        if (lineSegment.Intersects(pointPath)) return true;
        return false;
    }
}

Saya sedang menulis tes untuk CollidesWithLine ketika saya menyadari bahwa saya akan membutuhkan fungsi LineSegment.Intersects ( LineSegment ) . Tetapi, haruskah saya menghentikan apa yang saya lakukan pada siklus pengujian untuk membuat fungsi baru ini? Itu tampaknya melanggar prinsip "Merah, Hijau, Refactor".

Haruskah saya hanya menulis kode yang mendeteksi bahwa lineSegments Intersect di dalam fungsi CollidesWithLine dan refactor setelah berfungsi? Itu akan bekerja dalam kasus ini karena saya dapat mengakses data dari LineSegment , tetapi bagaimana jika dalam kasus-kasus di mana data semacam itu bersifat pribadi?

Joshua Harris
sumber

Jawaban:

14

Komentari tes Anda dan kode baru-baru ini (atau disimpan), jadi Anda sebenarnya telah memutar balik waktu ke awal siklus. Kemudian mulailah dengan LineSegment.Intersects(LineSegment)tes / kode / refactor. Ketika itu selesai, batalkan komentar tes / kode Anda sebelumnya (atau tarik dari simpanan) dan tetap dengan siklus.

Javier
sumber
Bagaimana ini berbeda lalu mengabaikannya dan kembali lagi nanti?
Joshua Harris
1
hanya detail kecil: tidak ada tes "abaikan saya" tambahan dalam laporan, dan jika Anda menggunakan simpanan, kode tidak dapat dibedakan dari kasus 'bersih'.
Javier
Apa itu simpanan? apakah itu seperti kontrol versi?
Joshua Harris
1
beberapa VCS mengimplementasikannya sebagai fitur (setidaknya Git dan Fosil). Ini memungkinkan Anda untuk menghapus perubahan tetapi menyimpannya untuk mengajukan kembali beberapa waktu kemudian. Ini tidak sulit untuk dilakukan secara manual, cukup simpan diff dan kembali ke status terakhir. Kemudian Anda menerapkan kembali diff dan terus.
Javier
6

Pada siklus TDD:

Pada fase "make the test pass", Anda seharusnya menulis implementasi paling sederhana yang akan membuat test pass . Untuk membuat lulus ujian Anda, Anda telah memutuskan untuk membuat kolaborator baru untuk menangani logika yang hilang karena mungkin terlalu banyak pekerjaan untuk dimasukkan ke dalam kelas poin Anda untuk membuat lulus ujian Anda. Di situlah masalahnya terletak. Saya kira ujian yang Anda coba lakukan adalah langkah yang terlalu besar . Jadi saya pikir masalahnya ada pada tes Anda sendiri, Anda harus menghapus / berkomentar tes itu, dan mencari tahu tes sederhana yang akan memungkinkan Anda untuk mengambil langkah kecil tanpa memperkenalkan bagian LineSegment.Intersects (LineSegment). Setelah tes lulus, Anda dapat refactorkode Anda (Di sini Anda akan menerapkan prinsip SRP) dengan memindahkan logika baru ini ke metode LineSegment.Intersects (LineSegment). Tes Anda masih akan berlalu karena Anda tidak akan mengubah perilaku apa pun tetapi hanya memindahkan beberapa kode.

Pada solusi desain Anda saat ini

Tetapi bagi saya, Anda memiliki masalah desain yang lebih mendalam di sini adalah bahwa Anda melanggar Prinsip Tanggung Jawab Tunggal . Peran sebuah Point adalah .... menjadi sebuah point, itu saja. Tidak ada kecerdasan dalam menjadi sebuah poin, itu hanya nilai x dan y. Poin adalah tipe nilai . Ini adalah hal yang sama untuk Segmen, segmen adalah tipe nilai yang terdiri dari dua poin. Mereka dapat berisi sedikit "kecerdasan" misalnya untuk menghitung panjangnya berdasarkan posisi poin mereka. Tapi begitulah.

Sekarang memutuskan apakah suatu titik dan suatu segmen bertabrakan, adalah tanggung jawab sepenuhnya pada dirinya sendiri. Dan tentu saja terlalu banyak pekerjaan untuk titik atau segmen untuk menangani sendiri. Itu tidak bisa menjadi bagian dari kelas Point, karena jika tidak Poin akan tahu tentang Segmen. Dan itu tidak dapat menjadi milik Segmen karena Segmen sudah memiliki tanggung jawab untuk mengurus poin dalam segmen tersebut dan mungkin juga menghitung panjang segmen itu sendiri.

Jadi tanggung jawab ini harus dimiliki oleh kelas lain seperti misalnya "PointSegmentCollisionDetector" yang akan memiliki metode seperti:

bool AreInCollision (Poin p, Segmen s)

Dan itu adalah sesuatu yang akan Anda uji secara terpisah dari Poin dan Segmen.

Yang menyenangkan dengan desain itu sekarang Anda bisa memiliki implementasi yang berbeda dari detektor tabrakan Anda. Jadi akan mudah misalnya untuk membuat tolok ukur mesin gim Anda (saya berasumsi Anda sedang menulis gim: p) dengan mengganti metode pendeteksian tabrakan saat runtime. Atau untuk melakukan beberapa pengecekan visual / eksperimen saat runtime antara berbagai strategi pendeteksian benturan.

Pada saat ini, dengan meletakkan logika ini di kelas poin Anda, Anda mengunci banyak hal dan terlalu banyak memikul tanggung jawab pada kelas Poin.

Semoga masuk akal,

foobarcode
sumber
Anda benar bahwa saya mencoba untuk tes terlalu besar dari perubahan dan saya pikir Anda benar tentang memisahkan yang keluar ke kelas tabrakan, tapi itu membuat saya bertanya pertanyaan yang sama sekali baru yang Anda mungkin bisa membantu saya dengan: Haruskah saya menggunakan antarmuka ketika metode hanya mirip? .
Joshua Harris
2

Hal termudah untuk dilakukan dalam mode TDD adalah mengekstrak antarmuka untuk LineSegment dan mengubah parameter metode Anda untuk mengambil antarmuka. Kemudian Anda bisa mengejek segmen jalur input dan kode / menguji metode Intersect secara mandiri.

Dan Lyons
sumber
1
Saya tahu bahwa itu adalah metode TDD yang paling saya dengar, tetapi ILineSegment tidak masuk akal. Ini adalah satu hal untuk antarmuka sumber daya eksternal atau sesuatu yang bisa datang dalam berbagai bentuk, tapi saya tidak bisa melihat satu alasan saya akan pernah melampirkan fungsi apa pun untuk apa pun selain segmen garis.
Joshua Harris
0

Dengan jUnit4 Anda dapat menggunakan @Ignoreanotasi untuk tes yang ingin Anda tunda.

Tambahkan Anotasi ke setiap metode yang Anda ingin tunda, dan lanjutkan menulis tes untuk fungsionalitas yang diperlukan. Lingkari kembali untuk memperbaiki kasus uji yang lebih lama nanti.

bakoyaro
sumber