Bagaimana jenis kesalahan terdeteksi saat membuat tiruan dalam bahasa yang dinamis?

10

Masalah terjadi saat melakukan TDD. Setelah beberapa tes lulus, tipe-tipe pengembalian beberapa kelas / modul berubah. Dalam bahasa pemrograman yang diketik secara statis, jika objek mocked sebelumnya digunakan dalam tes beberapa kelas lain dan tidak dimodifikasi untuk mencerminkan perubahan tipe, maka kesalahan kompilasi akan terjadi.

Namun untuk bahasa dinamis, perubahan tipe pengembalian mungkin tidak terdeteksi dan tes dari kelas lain masih akan berlalu. Tentu mungkin ada tes integrasi yang harus gagal nanti, tetapi unit test akan keliru berlalu. Apakah ada cara bagaimana menghindari ini?

Memperbarui dengan sampel sepele (pada beberapa bahasa yang dibuat-buat) ...

Versi 1:

Calc = {
    doMultiply(x, y) {return x * y}
}
//.... more code ....

// On some faraway remote code on a different file
Rect = {
    computeArea(l, w) {return Calc.doMultipy(x*y)}
}

// test for Rect
testComputeArea() { 
    Calc = new Mock()
    Calc.expect(doMultiply, 2, 30) // where 2 is the arity
    assertEqual(30, computeArea)
}

Sekarang, pada versi 2:

// I change the return types. I also update the tests for Calc
Calc = {
    doMultiply(x, y) {return {result: (x * y), success:true}}
}

... Rect akan melempar eksepsi pada runtime, namun tes masih akan berhasil.

jvliwanag
sumber
1
Apa jawaban yang sejauh ini terlewatkan adalah bahwa pertanyaannya bukan tentang tes yang melibatkan perubahan class X, tetapi tes class Yyang bergantung pada Xdan dengan demikian diuji terhadap kontrak yang berbeda dari apa yang bertentangan dalam produksi.
Bart van Ingen Schenau
Saya baru saja menanyakan pertanyaan ini pada SO , saya sendiri, sehubungan dengan Injeksi Ketergantungan. Lihat Alasan 1: Kelas dependen dapat diubah pada waktu berjalan (pikirkan pengujian) . Kami berdua memiliki pola pikir yang sama tetapi tidak memiliki penjelasan yang bagus.
Scott Coates
Saya membaca kembali pertanyaan Anda tetapi saya sedikit bingung dengan interpretasinya. Bisakah Anda memberikan contoh?
Scott Coates

Jawaban:

2

Sampai batas tertentu, ini hanya sebagian dari biaya berbisnis dengan bahasa yang dinamis. Anda mendapatkan banyak fleksibilitas, atau dikenal sebagai "tali cukup untuk menggantung diri sendiri". Hati-hati dengan itu.

Bagi saya, masalahnya menyarankan menggunakan teknik refactoring yang berbeda dari yang Anda lakukan dalam bahasa yang diketik secara statis. Dalam bahasa statis, Anda mengganti tipe pengembalian sebagian sehingga Anda dapat "bersandar pada kompiler" untuk menemukan tempat yang mungkin rusak. Ini aman untuk dilakukan, dan mungkin lebih aman daripada memodifikasi tipe pengembalian di tempat.

Dalam bahasa yang dinamis, Anda tidak bisa melakukan itu, jadi jauh lebih aman untuk memodifikasi tipe pengembalian di tempat, daripada menggantinya. Mungkin, Anda memodifikasinya dengan menambahkan kelas baru Anda sebagai anggota, untuk kelas yang membutuhkannya.

tallseth
sumber
2

Jika kode Anda berubah dan tes Anda masih lulus, maka ada sesuatu yang salah dengan tes Anda (yaitu Anda kehilangan penegasan), atau kode itu tidak benar - benar berubah.

Apa yang saya maksud dengan itu? Nah, tes Anda menjelaskan kontrak antara bagian-bagian kode Anda. Jika kontrak adalah "nilai kembali harus dapat diubah", maka mengubah nilai kembali dari mengatakan array ke daftar sebenarnya bukan perubahan dalam kontrak, dan karena itu tidak akan serta merta memicu kegagalan pengujian.

Untuk menghindari pernyataan yang hilang, Anda dapat menggunakan alat-alat seperti analisis cakupan kode (tidak akan memberi tahu Anda bagian mana dari kode Anda yang diuji, tetapi akan memberi tahu Anda bagian mana yang tidak diuji), kompleksitas siklus dan kompleksitas NPath (yang memberi Anda batas bawah pada jumlah pernyataan yang diperlukan untuk mencapai cakupan kode C1 dan C2 penuh) dan penguji mutasi (yang menyuntikkan mutasi dalam kode Anda seperti beralih trueke false, angka negatif menjadi positif, objek menjadi nulldll, dan memeriksa apakah itu membuat tes gagal).

Jörg W Mittag
sumber
+1 Entah ada yang salah dengan tes atau kode yang tidak benar-benar berubah. Saya butuh waktu untuk membiasakan diri setelah bertahun-tahun C ++ / Java. Kontrak antara bagian-bagian dalam bahasa kode yang dinamis tidak boleh APA dikembalikan, tetapi apa yang dikembalikan berisi berisi dan apa yang bisa dilakukan.
MrFox
@sliklik: Sebenarnya, ini bukan tentang bahasa statis vs dinamis, melainkan tentang Abstraksi Data menggunakan Tipe Data Abstrak vs. Abstraksi Data Berorientasi Objek. Kemampuan untuk satu objek untuk mensimulasikan objek lain selama itu berperilaku tidak dapat dibedakan (bahkan jika mereka adalah tipe yang sama sekali berbeda dan contoh dari kelas yang sama sekali berbeda) adalah mendasar bagi seluruh gagasan OO. Atau dengan kata lain: jika dua objek berperilaku sama, mereka memiliki tipe yang sama, terlepas dari apa yang dikatakan kelas mereka. Dengan kata lain: kelas bukan tipe. Sayangnya, Java dan C # salah.
Jörg W Mittag
Dengan asumsi bahwa unit yang dipermainkan 100% tertutup dan tidak ada pernyataan yang hilang: bagaimana dengan perubahan yang lebih halus seperti "harus mengembalikan null untuk foo" menjadi "harus mengembalikan string kosong untuk foo". Jika seseorang mengubah unit itu dan pengujiannya, beberapa unit konsumsi mungkin rusak dan karena tiruan ini transparan. (Dan tidak: pada saat menulis atau menggunakan modul yang diejek, per "kontrak", tidak perlu menangani string kosong sebagai nilai pengembalian karena itu seharusnya mengembalikan string non-kosong atau hanya null;)). (Hanya pengujian integrasi yang tepat dari kedua modul dalam interaksi yang dapat menangkap hal ini.)
coba-tangkap-akhirnya