Bagaimana cara menguji ketika mengatur data terlalu rumit?

19

Saya menulis parser dan sebagai bagian dari itu, saya memiliki Expanderkelas yang "memperluas" pernyataan kompleks tunggal menjadi beberapa pernyataan sederhana. Misalnya, itu akan memperluas ini:

x = 2 + 3 * a

ke:

tmp1 = 3 * a
x = 2 + tmp1

Sekarang saya sedang berpikir tentang bagaimana menguji kelas ini, khususnya bagaimana mengatur tes. Saya bisa secara manual membuat pohon sintaks input:

var input = new AssignStatement(
    new Variable("x"),
    new BinaryExpression(
        new Constant(2),
        BinaryOperator.Plus,
        new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));

Atau saya bisa menulisnya sebagai string dan menguraikannya:

var input = new Parser().ParseStatement("x = 2 + 3 * a");

Opsi kedua jauh lebih sederhana, lebih pendek dan mudah dibaca. Tapi itu juga memperkenalkan denpendensi Parser, yang berarti bug di Parserbisa gagal dalam tes ini. Jadi, tes akan berhenti menjadi unit test Expander, dan saya kira secara teknis menjadi tes integrasiParser dan Expander.

Pertanyaan saya adalah: apakah boleh mengandalkan sebagian besar (atau sepenuhnya) pada tes integrasi semacam ini untuk menguji Expanderkelas ini ?

svick
sumber
3
Bahwa bug dalam Parserdapat gagal dalam beberapa pengujian lain tidak menjadi masalah jika Anda terbiasa melakukan hanya pada nol kegagalan, sebaliknya itu berarti Anda memiliki lebih banyak cakupan Parser. Apa yang saya lebih suka khawatirkan adalah bug dalam Parserdapat membuat tes ini berhasil padahal seharusnya gagal . Tes unit ada untuk menemukan bug, setelah semua - tes rusak ketika tidak tetapi harus dilakukan.
Jonas Kölker

Jawaban:

27

Anda akan menemukan diri Anda menulis lebih banyak tes, perilaku yang jauh lebih rumit, menarik, dan berguna, jika Anda dapat melakukannya dengan sederhana. Jadi opsi itu melibatkan

var input = new Parser().ParseStatement("x = 2 + 3 * a");

cukup valid. Itu tergantung pada komponen lain. Tetapi semuanya tergantung pada lusinan komponen lainnya. Jika Anda mengejek sesuatu dalam satu inci dari kehidupannya, Anda mungkin tergantung pada banyak fitur mengejek dan alat uji.

Pengembang terkadang terlalu fokus pada kemurnian tes unit mereka , atau mengembangkan tes unit dan tes unit saja , tanpa modul, integrasi, stres atau jenis tes lainnya. Semua formulir itu valid dan bermanfaat, dan semuanya merupakan tanggung jawab pengembang yang tepat - bukan hanya T / A atau personel operasi yang berada di jalur pipa.

Salah satu pendekatan yang saya gunakan adalah memulai dengan level run yang lebih tinggi ini, kemudian menggunakan data yang dihasilkan dari mereka untuk membangun ekspresi bentuk-panjang, terendah-umum-denominator dari tes. Misalnya ketika Anda membuang struktur data dari yang inputdihasilkan di atas, maka Anda dapat dengan mudah membangun:

var input = new AssignStatement(
    new Variable("x"),
    new BinaryExpression(
        new Constant(2),
        BinaryOperator.Plus,
        new BinaryExpression(new Constant(3), BinaryOperator.Multiply, new Variable("a"))));

jenis tes yang menguji pada tingkat yang paling rendah. Dengan begitu Anda mendapatkan campuran yang bagus: Sejumlah tes primitif yang paling dasar (tes unit murni), tetapi belum menghabiskan satu minggu menulis tes pada tingkat primitif itu. Itu memberi Anda sumber daya waktu yang dibutuhkan untuk menulis lebih banyak, lebih sedikit pengujian atom menggunakan Parsersebagai penolong. Hasil akhir: Lebih banyak tes, lebih banyak cakupan, lebih banyak sudut dan kasus menarik lainnya, kode lebih baik dan jaminan kualitas yang lebih tinggi.

Jonathan Eunice
sumber
2
Ini masuk akal - terutama mengenai kenyataan bahwa semuanya tergantung pada banyak orang lain. Unit test yang baik harus menguji seminimal mungkin. Apa pun yang ada dalam jumlah minimum yang mungkin harus diuji dengan uji unit sebelumnya. Jika Anda telah sepenuhnya menguji Parser, Anda dapat mengasumsikan bahwa Anda dapat menggunakan Parser dengan aman untuk menguji ParseStatement
Jon Story
6
Perhatian utama kemurnian (saya pikir) adalah untuk menghindari penulisan dependensi melingkar dalam tes unit Anda. Jika parser atau parser tes menggunakan expander, dan tes expander ini bergantung pada parser yang berfungsi, maka Anda memiliki risiko yang sulit untuk dikelola bahwa semua yang Anda uji adalah parser dan expander yang konsisten , sedangkan apa yang ingin Anda lakukan adalah menguji bahwa expander benar-benar melakukan apa yang seharusnya . Tetapi selama tidak ada ketergantungan kembali ke arah lain, menggunakan parser dalam unit test ini tidak benar-benar berbeda dari menggunakan pustaka standar dalam unit test.
Steve Jessop
@SteveJessop Poin bagus. Sangat penting untuk menggunakan komponen independen .
Jonathan Eunice
3
Sesuatu yang telah saya lakukan dalam kasus di mana parser itu sendiri adalah operasi yang mahal (misalnya membaca data dari file Excel melalui com interop) adalah menulis metode pembuatan tes yang menjalankan parser dan kode output ke konsol menciptakan kembali struktur data parser kembali . Saya kemudian menyalin output dari generator ke unit test yang lebih konvensional. Ini memungkinkan pengurangan ketergantungan silang di mana parser hanya perlu bekerja dengan benar ketika tes dibuat tidak setiap kali mereka dijalankan. (Tidak membuang-buang waktu beberapa detik / tes untuk membuat / menghancurkan proses Excel adalah bonus yang bagus.)
Dan Neely
+1 untuk pendekatan @ DanNeely. Kami menggunakan sesuatu yang mirip untuk menyimpan beberapa versi serial model data kami sebagai data uji, sehingga kami dapat memastikan kode baru masih dapat bekerja dengan data yang lebih lama.
Chris Hayes
6

Tentu saja tidak apa-apa!

Anda selalu memerlukan uji fungsional / integrasi yang menggunakan jalur kode lengkap. Dan jalur kode lengkap dalam hal ini berarti termasuk evaluasi kode yang dihasilkan. Itu adalah Anda menguji bahwa parsing x = 2 + 3 * amenghasilkan kode yang jika dijalankan dengan a = 5akan diatur xke 17dan jika dijalankan dengan a = -2akan ditetapkanx ke-4 .

Di bawah ini, Anda harus melakukan tes unit untuk bit yang lebih kecil asalkan itu benar-benar membantu debug kode . Tes berbutir halus Anda akan memiliki, semakin tinggi probabilitas bahwa setiap perubahan kode perlu mengubah tes juga, karena perubahan antarmuka internal. Tes semacam itu hanya memiliki sedikit nilai jangka panjang dan menambah pekerjaan pemeliharaan. Jadi ada titik pengembalian yang berkurang dan Anda harus berhenti sebelum itu.

Jan Hudec
sumber
4

Tes unit memungkinkan Anda untuk menunjukkan item tertentu yang rusak dan di mana dalam kode mereka pecah. Jadi mereka bagus untuk pengujian berbutir halus. Tes unit yang baik akan membantu mengurangi waktu debugging.

Namun, dari pengalaman saya, tes unit jarang cukup baik untuk benar-benar memverifikasi operasi yang benar. Jadi tes integrasi juga membantu memverifikasi rantai atau urutan operasi. Tes integrasi membuat Anda menjadi bagian dari jalan melalui pengujian fungsional. Seperti yang Anda tunjukkan, karena kerumitan tes integrasi, lebih sulit untuk menemukan titik spesifik dalam kode di mana tes pecah. Ini juga memiliki kerapuhan yang lebih dalam bahwa kegagalan di mana saja dalam rantai akan menyebabkan tes gagal. Namun Anda masih memiliki rantai itu dalam kode produksi, jadi menguji rantai yang sebenarnya masih membantu.

Idealnya Anda memiliki keduanya, tetapi bagaimanapun juga, umumnya memiliki tes otomatis lebih baik daripada tidak memiliki tes.

Peter Smith
sumber
0

Lakukan banyak tes pada parser dan ketika parser melewati tes, simpan output tersebut ke file untuk mengejek parser dan menguji komponen lainnya.

Tulains Córdova
sumber