Saya membaca tentang snafu ini: Biaya pemrograman bug Citigroup $ 7 juta setelah transaksi yang sah keliru untuk data pengujian selama 15 tahun .
Ketika sistem diperkenalkan pada pertengahan 1990-an, kode program menyaring semua transaksi yang diberi kode cabang tiga digit dari 089 hingga 100 dan menggunakan awalan itu untuk tujuan pengujian.
Tetapi pada tahun 1998, perusahaan mulai menggunakan kode cabang alfanumerik ketika memperluas bisnisnya. Di antara mereka adalah kode 10B, 10C dan seterusnya, yang sistem diperlakukan sebagai dalam kisaran yang dikecualikan, dan transaksi mereka dihapus dari laporan yang dikirim ke SEC.
(Saya pikir ini menggambarkan bahwa menggunakan indikator data non-eksplisit adalah ... sub-optimal. Akan jauh lebih baik untuk mengisi dan menggunakan properti semantik eksplisit Branch.IsLive
.)
Selain itu, reaksi pertama saya adalah "Tes unit akan membantu di sini" ... tetapi apakah mereka?
Saya baru-baru ini membaca Mengapa kebanyakan pengujian unit sia - sia , dan pertanyaan saya adalah: seperti apa pengujian unit yang gagal pada pengenalan kode cabang alfanumerik?
sumber
Jawaban:
Apakah Anda benar-benar bertanya, "apakah unit test akan membantu di sini?", Atau Anda bertanya, "dapatkah segala jenis tes mungkin membantu di sini?".
Bentuk pengujian yang paling jelas yang akan membantu, adalah pernyataan prasyarat dalam kode itu sendiri, bahwa pengenal cabang hanya terdiri dari digit (seandainya ini adalah asumsi yang diandalkan oleh pembuat kode dalam menulis kode).
Ini kemudian bisa gagal dalam beberapa jenis tes integrasi, dan segera setelah id cabang alfa-numerik baru diperkenalkan maka pernyataan itu meledak. Tapi itu bukan tes unit.
Atau, mungkin ada tes integrasi dari prosedur yang menghasilkan laporan SEC. Tes ini memastikan bahwa setiap pengenal cabang nyata melaporkan transaksinya (dan oleh karena itu membutuhkan input dunia nyata, daftar semua pengidentifikasi cabang yang digunakan). Jadi itu bukan tes unit juga.
Saya tidak bisa melihat definisi atau dokumentasi dari antarmuka yang terlibat, tetapi mungkin unit test tidak mungkin mendeteksi kesalahan karena unit tidak rusak . Jika unit diizinkan untuk menganggap bahwa pengenal cabang hanya terdiri dari angka, dan pengembang tidak pernah membuat keputusan apa yang harus dilakukan kode jika tidak, maka mereka tidak bolehtulis tes unit untuk menegakkan perilaku tertentu dalam kasus pengidentifikasi non-digit karena tes tersebut akan menolak implementasi hipotetis valid unit yang menangani pengidentifikasi cabang alfanumerik dengan benar, dan Anda biasanya tidak ingin menulis tes unit yang mencegah valid implementasi dan ekstensi di masa depan. Atau mungkin satu dokumen yang ditulis 40 tahun yang lalu secara implisit didefinisikan (melalui sejumlah leksikografis dalam EBCDIC mentah, alih-alih aturan pemeriksaan yang lebih ramah-manusia) bahwa 10B adalah pengidentifikasi tes karena pada kenyataannya jatuh antara 089 dan 100. Tetapi kemudian 15 tahun yang lalu seseorang memutuskan untuk menggunakannya sebagai pengidentifikasi nyata, sehingga "kesalahan" tidak terletak pada unit yang mengimplementasikan definisi asli dengan benar: itu terletak pada proses yang gagal untuk melihat bahwa 10B didefinisikan sebagai pengidentifikasi tes dan karenanya tidak boleh ditugaskan ke cabang. Hal yang sama akan terjadi di ASCII jika Anda mendefinisikan 089 - 100 sebagai rentang tes dan kemudian memperkenalkan pengidentifikasi 10 $ atau 1,0. Kebetulan dalam EBCDIC angka datang setelah surat.
Satu unit tes (atau bisa dibilang tes fungsional) yang mungkinmungkin telah menyelamatkan hari, adalah tes unit yang menghasilkan atau memvalidasi pengidentifikasi cabang baru. Tes itu akan menyatakan bahwa pengidentifikasi harus hanya berisi digit, dan akan ditulis untuk memungkinkan pengguna pengidentifikasi cabang untuk mengasumsikan yang sama. Atau mungkin ada unit di suatu tempat yang mengimpor pengenal cabang nyata tetapi tidak pernah melihat yang menguji, dan itu bisa diuji unit untuk memastikan ia menolak semua pengidentifikasi tes (jika pengidentifikasi hanya tiga karakter kita dapat menghitung semuanya, dan membandingkan perilaku validator ke filter-tes untuk memastikan mereka cocok, yang berkaitan dengan keberatan biasa untuk tes-spot). Kemudian ketika seseorang mengubah aturan, unit test akan gagal karena bertentangan dengan perilaku yang baru diperlukan.
Karena tes ada di sana untuk alasan yang baik, titik di mana Anda perlu menghapusnya karena perubahan persyaratan bisnis menjadi peluang bagi seseorang untuk diberi pekerjaan, "temukan setiap tempat dalam kode yang bergantung pada perilaku yang ingin kita lakukan. perubahan". Tentu saja ini sulit dan karenanya tidak dapat diandalkan, sehingga tidak berarti menjamin hari itu. Tetapi jika Anda menangkap asumsi Anda dalam pengujian unit yang Anda anggap sebagai properti, maka Anda telah memberi diri Anda kesempatan dan upaya tersebut tidak sepenuhnya sia-sia.
Saya setuju tentu saja bahwa jika unit tersebut tidak didefinisikan pada awalnya dengan input "berbentuk lucu", maka tidak akan ada yang perlu diuji. Divisi namespace Fiddly mungkin sulit untuk diuji dengan benar karena kesulitannya tidak terletak pada penerapan definisi lucu Anda, itu terletak pada memastikan bahwa semua orang memahami dan menghormati definisi lucu Anda. Itu bukan properti lokal dari satu unit kode. Selain itu, mengubah beberapa tipe data dari "string digit" menjadi "string alfanumerik" mirip dengan membuat program berbasis ASCII menangani Unicode: itu tidak akan mudah jika kode Anda sangat digabungkan ke definisi asli, dan ketika tipe data sangat mendasar untuk apa yang dilakukan oleh program maka sering kali sangat digabungkan.
Jika unit test Anda kadang gagal (saat Anda melakukan refactoring, misalnya), dan dengan melakukan hal itu memberi Anda informasi yang bermanfaat (perubahan Anda salah, misalnya), maka upaya itu tidak sia-sia. Yang tidak mereka lakukan adalah menguji apakah sistem Anda berfungsi. Jadi, jika Anda menulis tes unit alih-alih melakukan tes fungsional dan integrasi, maka Anda mungkin menggunakan waktu Anda secara kurang optimal.
sumber
Tes unit dapat menangkap bahwa kode cabang 10B dan 10C secara keliru diklasifikasikan sebagai "cabang pengujian", tetapi saya merasa tidak mungkin bahwa tes untuk klasifikasi cabang akan cukup luas untuk menangkap kesalahan itu.
Di sisi lain, pemeriksaan langsung dari laporan yang dihasilkan dapat mengungkapkan bahwa 10B dan 10C bercabang secara konsisten hilang dari laporan lebih cepat daripada 15 tahun bahwa bug sekarang dibiarkan tetap ada.
Akhirnya, ini adalah ilustrasi yang bagus mengapa itu adalah ide yang buruk untuk mencampur data pengujian dengan data produksi nyata dalam satu database. Jika mereka menggunakan database terpisah yang berisi data pengujian, tidak akan ada kebutuhan untuk menyaring yang keluar dari laporan resmi dan tidak mungkin untuk menyaring terlalu banyak.
sumber
Perangkat lunak harus menangani aturan bisnis tertentu. Jika ada tes unit, tes unit akan memeriksa bahwa perangkat lunak menangani aturan bisnis dengan benar.
Aturan bisnis berubah.
Tampaknya tidak ada yang menyadari bahwa aturan bisnis telah berubah, dan tidak ada yang mengubah perangkat lunak untuk menerapkan aturan bisnis baru. Jika ada unit test, unit test itu harus diubah, tetapi tidak ada yang akan melakukan itu karena tidak ada yang menyadari bahwa aturan bisnis telah berubah.
Jadi tidak, unit test tidak akan menangkap itu.
Pengecualiannya adalah jika tes unit dan perangkat lunak telah dibuat oleh tim independen, dan tim yang melakukan tes unit mengubah tes untuk menerapkan aturan bisnis baru. Maka unit test akan gagal, yang diharapkan akan menghasilkan perubahan perangkat lunak.
Tentu saja dalam kasus yang sama jika hanya perangkat lunak yang diubah dan bukan unit test, maka unit test juga akan gagal. Setiap kali tes unit gagal, itu tidak berarti perangkat lunak salah, itu berarti perangkat lunak atau tes unit (kadang-kadang keduanya) salah.
sumber
Tidak. Ini adalah salah satu masalah besar dengan pengujian unit: mereka menidurkan Anda ke rasa aman yang salah.
Jika semua tes Anda lulus, itu tidak berarti sistem Anda berfungsi dengan baik; itu berarti semua tes Anda lulus . Ini berarti bahwa bagian-bagian dari desain Anda yang secara sadar Anda pikirkan dan tulis untuk tes bekerja sesuai dengan yang Anda pikirkan, yang sebenarnya bukan masalah besar: itu adalah hal-hal yang benar-benar Anda perhatikan. untuk, jadi sangat mungkin Anda melakukannya dengan benar! Tetapi tidak ada gunanya menangkap kasus yang tidak pernah Anda pikirkan, seperti yang ini, karena Anda tidak pernah berpikir untuk menulis tes untuk mereka. (Dan jika Anda punya, Anda akan menyadari bahwa itu berarti perubahan kode diperlukan, dan Anda akan mengubahnya.)
sumber
Tidak, belum tentu.
Persyaratan asli adalah untuk menggunakan kode cabang numerik, sehingga unit test akan diproduksi untuk komponen yang menerima berbagai kode dan menolak 10B. Sistem akan dianggap berfungsi (yang dulu).
Kemudian, persyaratan akan berubah dan kode diperbarui, tetapi ini berarti kode uji unit yang memasok data buruk (yang sekarang merupakan data yang baik) harus diubah.
Sekarang kita berasumsi bahwa, orang yang mengelola sistem akan mengetahui hal ini dan akan mengubah tes unit untuk menangani kode baru ... tetapi jika mereka tahu itu terjadi, mereka juga akan tahu untuk mengubah kode yang menangani ini kode tetap .. dan mereka tidak melakukan itu. Tes unit yang awalnya menolak kode 10B akan dengan senang hati mengatakan "semuanya baik-baik saja di sini" ketika dijalankan, jika Anda tidak tahu untuk memperbarui tes itu.
Pengujian unit baik untuk pengembangan asli tetapi tidak untuk pengujian sistem, terutama tidak 15 tahun setelah persyaratan lama dilupakan.
Apa yang mereka butuhkan dalam situasi semacam ini adalah tes integrasi ujung ke ujung. Satu tempat Anda bisa menyampaikan data yang Anda harapkan berfungsi dan melihat apakah itu berhasil. Seseorang akan memperhatikan bahwa data input baru mereka tidak menghasilkan laporan dan kemudian akan menyelidiki lebih lanjut.
sumber
Jenis pengujian (proses pengujian invarian menggunakan data valid yang dihasilkan secara acak, seperti dicontohkan oleh perpustakaan pengujian Haskell QuickCheck dan berbagai port / alternatif yang terinspirasi olehnya dalam bahasa lain) mungkin telah menangkap masalah ini, unit testing hampir pasti tidak akan melakukan .
Ini karena ketika aturan untuk validitas kode cabang diperbarui, tidak mungkin ada orang yang berpikir untuk menguji rentang tertentu untuk memastikan mereka bekerja dengan benar.
Namun, jika pengujian tipe telah digunakan, seseorang harus pada saat sistem asli diimplementasikan telah menulis sepasang properti, satu untuk memeriksa bahwa kode spesifik untuk cabang uji diperlakukan sebagai data uji dan satu untuk memeriksa bahwa tidak ada kode lain adalah ... ketika definisi tipe data untuk kode cabang diperbarui (yang seharusnya diperlukan untuk memungkinkan pengujian bahwa salah satu perubahan untuk kode cabang dari digit ke angka bekerja), tes ini akan mulai menguji nilai dalam kisaran baru dan kemungkinan besar akan mengidentifikasi kesalahan.
Tentu saja, QuickCheck pertama kali dikembangkan pada tahun 1999, jadi sudah terlambat untuk menangkap masalah ini.
sumber
Saya benar-benar ragu pengujian unit akan membuat perbedaan untuk masalah ini. Kedengarannya seperti salah satu situasi penglihatan terowongan karena fungsi diubah untuk mendukung kode cabang baru, tetapi ini tidak dilakukan di semua area dalam sistem.
Kami menggunakan pengujian unit untuk merancang kelas. Menjalankan kembali tes unit hanya diperlukan jika desain telah berubah. Jika unit tertentu tidak berubah, maka unit tes yang tidak berubah akan mengembalikan hasil yang sama seperti sebelumnya. Tes unit tidak akan menunjukkan kepada Anda dampak perubahan ke unit lain (jika mereka melakukannya, Anda tidak menulis tes unit).
Anda hanya dapat mendeteksi masalah ini secara wajar melalui:
Tidak memiliki pengujian end-to-end yang memadai lebih mengkhawatirkan. Anda tidak dapat mengandalkan pengujian unit karena HANYA atau tes UTAMA Anda untuk perubahan sistem. Kedengarannya seperti itu hanya diperlukan seseorang untuk menjalankan laporan pada format kode cabang yang baru didukung.
sumber
Pernyataan bawaan untuk run-time mungkin membantu; sebagai contoh:
bool isTestOnly(string branchCode) { ... }
Lihat juga:
sumber
Keuntungan dari ini adalah ke Fail Fast .
Kami tidak memiliki kode, kami juga tidak memiliki banyak contoh awalan yang atau tidak menguji awalan cabang menurut kode. Yang kita miliki adalah ini:
Fakta bahwa kode memungkinkan angka dan string lebih dari sedikit aneh. Tentu saja, 10B dan 10C dapat dianggap sebagai angka heksa, tetapi jika semua awalan diperlakukan sebagai angka heksa, 10B dan 10C berada di luar rentang tes dan akan diperlakukan sebagai cabang nyata.
Ini kemungkinan berarti bahwa awalan disimpan sebagai string tetapi diperlakukan sebagai angka dalam beberapa kasus. Berikut adalah kode paling sederhana yang dapat saya pikirkan yang mereplikasi perilaku ini (menggunakan C # untuk tujuan ilustrasi):
Dalam bahasa Inggris, jika string adalah angka dan antara 89 dan 100, itu adalah tes. Jika itu bukan angka, itu ujian. Kalau tidak, ini bukan ujian.
Jika kode mengikuti pola ini, tidak ada unit test akan menangkap ini pada saat kode itu digunakan. Berikut adalah beberapa contoh unit test:
Tes unit menunjukkan bahwa "10B" harus diperlakukan sebagai cabang uji. Pengguna @ gnasher729 di atas mengatakan bahwa aturan bisnis berubah dan itulah yang ditunjukkan oleh pernyataan terakhir di atas. Pada titik tertentu yang menyatakan harus beralih ke
isFalse
, tetapi itu tidak terjadi. Tes unit dijalankan pada waktu pengembangan dan waktu pengembangan tetapi kemudian tidak ada titik setelahnya.Apa pelajarannya di sini? Kode membutuhkan beberapa cara untuk memberi sinyal bahwa ia menerima input yang tidak terduga. Berikut adalah cara alternatif untuk menulis kode ini yang menekankan bahwa ia mengharapkan awalan menjadi angka:
Bagi mereka yang tidak tahu C #, nilai kembali menunjukkan apakah kode mampu mengurai awalan dari string yang diberikan. Jika nilai kembali benar, kode panggilan dapat menggunakan variabel isTest out untuk memeriksa apakah awalan cabang adalah awalan tes. Jika nilai kembali salah, kode panggilan harus melaporkan bahwa awalan yang diberikan tidak diharapkan, dan variabel isTest out tidak berarti dan harus diabaikan.
Jika Anda setuju dengan pengecualian, Anda dapat melakukan ini sebagai gantinya:
Alternatif ini lebih mudah. Dalam hal ini, kode panggilan harus menangkap pengecualian. Dalam kedua kasus, kode harus memiliki beberapa cara pelaporan kepada pemanggil bahwa itu tidak mengharapkan strPrefix yang tidak dapat dikonversi ke integer. Dengan cara ini kode gagal dengan cepat dan bank dapat dengan cepat menemukan masalah tanpa rasa malu denda SEC.
sumber
Begitu banyak jawaban dan bahkan kutipan Dijkstra:
Karena itu tergantung. Jika kode diuji dengan benar, kemungkinan besar bug ini tidak akan ada.
sumber
Saya pikir unit test di sini akan memastikan masalah tidak pernah ada sejak awal.
Pertimbangkan, Anda telah menulis
bool IsTestData(string branchCode)
fungsinya.Tes unit pertama yang Anda tulis harus untuk string nol dan kosong. Kemudian untuk string panjang yang salah maka untuk string non integer.
Untuk membuat semua tes lulus, Anda harus menambahkan pemeriksaan parameter ke fungsi.
Bahkan jika Anda hanya menguji data 'baik' 001 -> 999 tidak memikirkan kemungkinan 10A, pengecekan parameter akan memaksa Anda untuk menulis ulang fungsi saat Anda mulai menggunakan alfanumerik untuk menghindari pengecualian yang akan dilontarkannya.
sumber
IsValidBranchCode
fungsi untuk melakukan pemeriksaan ini? Dan fungsi ini mungkin akan diubah tanpa perlu memodifikasiIsTestData
? Jadi jika Anda hanya menguji 'data bagus', tes tidak akan membantu. Tes kasus tepi harus menyertakan beberapa kode cabang yang sekarang valid (dan bukan hanya beberapa yang masih tidak valid) untuk mulai gagal.