Praktik Terbaik Pengembangan Berbasis Pengujian Menggunakan C # dan RhinoMocks [ditutup]

87

Untuk membantu tim saya menulis kode yang dapat diuji, saya membuat daftar sederhana praktik terbaik ini untuk membuat basis kode C # kami lebih dapat diuji. (Beberapa poin merujuk pada batasan Rhino Mocks, kerangka kerja yang mengejek untuk C #, tetapi aturan tersebut mungkin berlaku lebih umum juga.) Apakah ada yang memiliki praktik terbaik yang mereka ikuti?

Untuk memaksimalkan testabilitas kode, ikuti aturan berikut:

  1. Tulis tes dulu, lalu kodenya. Alasan: Ini memastikan bahwa Anda menulis kode yang dapat diuji dan setiap baris kode mendapat tes tertulis untuknya.

  2. Kelas desain menggunakan injeksi ketergantungan. Alasan: Anda tidak bisa mengejek atau menguji apa yang tidak bisa dilihat.

  3. Pisahkan kode UI dari perilakunya menggunakan Model-View-Controller atau Model-View-Presenter. Alasan: Memungkinkan logika bisnis untuk diuji sementara bagian yang tidak dapat diuji (UI) diminimalkan.

  4. Jangan menulis metode atau kelas statis. Alasan: Metode statis sulit atau tidak mungkin untuk diisolasi dan Rhino Mocks tidak dapat mengejeknya.

  5. Programkan antarmuka, bukan kelas. Alasan: Menggunakan antarmuka menjelaskan hubungan antar objek. Antarmuka harus mendefinisikan layanan yang dibutuhkan objek dari lingkungannya. Selain itu, antarmuka dapat dengan mudah diejek menggunakan Rhino Mocks dan kerangka kerja tiruan lainnya.

  6. Pisahkan dependensi eksternal. Alasan: Dependensi eksternal yang belum terselesaikan tidak dapat diuji.

  7. Tandai sebagai virtual metode yang ingin Anda tiru. Alasan: Rhino Mocks tidak dapat meniru metode non-virtual.

Kevin Albrecht
sumber
Ini adalah daftar yang berguna. Kami saat ini menggunakan NUnit dan Rhino.Mocks, dan itu bagus untuk menguraikan kriteria ini untuk anggota tim yang kurang akrab dengan sisi pengujian unit ini.
Chris Ballard

Jawaban:

58

Pasti daftar yang bagus. Berikut beberapa pemikirannya:

Tulis tes dulu, lalu kodenya.

Saya setuju, pada level tinggi. Tapi, saya akan lebih spesifik: "Tulis tes dulu, lalu tulis kode secukupnya untuk lulus tes, dan ulangi." Jika tidak, saya takut pengujian unit saya akan terlihat lebih seperti pengujian integrasi atau penerimaan.

Kelas desain menggunakan injeksi ketergantungan.

Sepakat. Ketika sebuah objek membuat dependensinya sendiri, Anda tidak memiliki kendali atasnya. Inversi Kontrol / Injeksi Ketergantungan memberi Anda kontrol itu, memungkinkan Anda untuk mengisolasi objek yang diuji dengan tiruan / stub / dll. Ini adalah cara Anda menguji objek secara terpisah.

Pisahkan kode UI dari perilakunya menggunakan Model-View-Controller atau Model-View-Presenter.

Sepakat. Perhatikan bahwa bahkan penyaji / pengontrol dapat diuji menggunakan DI / IoC, dengan memberikan tampilan dan model stubbed / mocked. Lihat Presenter First TDD untuk lebih lanjut tentang itu.

Jangan menulis metode atau kelas statis.

Tidak yakin saya setuju dengan yang ini. Dimungkinkan untuk menguji unit metode / kelas statis tanpa menggunakan tiruan. Jadi, mungkin ini adalah salah satu aturan khusus Rhino Mock yang Anda sebutkan.

Programkan antarmuka, bukan kelas.

Saya setuju, tetapi untuk alasan yang sedikit berbeda. Antarmuka memberikan banyak fleksibilitas bagi pengembang perangkat lunak - lebih dari sekadar dukungan untuk berbagai kerangka objek tiruan. Misalnya, tidak mungkin mendukung DI dengan baik tanpa antarmuka.

Pisahkan dependensi eksternal.

Sepakat. Sembunyikan dependensi eksternal di balik fasad atau adaptor Anda sendiri (sebagaimana mestinya) dengan antarmuka. Ini akan memungkinkan Anda untuk mengisolasi perangkat lunak Anda dari ketergantungan eksternal, baik itu layanan web, antrian, database atau yang lainnya. Ini sangat penting ketika tim Anda tidak mengontrol ketergantungan (alias eksternal).

Tandai sebagai virtual metode yang ingin Anda tiru.

Itu batasan dari Rhino Mocks. Dalam lingkungan yang lebih memilih stub kode tangan daripada kerangka objek tiruan, itu tidak diperlukan.

Dan, beberapa hal baru yang perlu dipertimbangkan:

Gunakan pola desain yang kreatif. Ini akan membantu dengan DI, tetapi juga memungkinkan Anda untuk mengisolasi kode itu dan mengujinya secara independen dari logika lain.

Menulis tes menggunakan teknik Bill Wake's Arrange / Act / Assert . Teknik ini memperjelas konfigurasi apa yang diperlukan, apa yang sebenarnya sedang diuji, dan apa yang diharapkan.

Jangan takut untuk mengolok-olok / bertopik Anda sendiri. Seringkali, Anda akan menemukan bahwa menggunakan kerangka objek tiruan membuat pengujian Anda sangat sulit untuk dibaca. Dengan menggulirkan milik Anda sendiri, Anda akan memiliki kendali penuh atas tiruan / rintisan Anda, dan Anda akan dapat membuat tes Anda tetap terbaca. (Lihat kembali poin sebelumnya.)

Hindari godaan untuk memfaktor ulang duplikasi dari pengujian unit Anda menjadi kelas dasar abstrak, atau metode penyiapan / pembongkaran. Melakukannya akan menyembunyikan kode konfigurasi / pembersihan dari pengembang yang mencoba melakukan pengujian unit. Dalam hal ini, kejelasan setiap pengujian individu lebih penting daripada memfaktorkan ulang duplikasi.

Menerapkan Integrasi Berkelanjutan. Check-in kode Anda di setiap "bilah hijau". Bangun perangkat lunak Anda dan jalankan rangkaian lengkap pengujian unit Anda pada setiap check-in. (Tentu, ini bukan praktik pengkodean, per se; tetapi ini adalah alat yang luar biasa untuk menjaga perangkat lunak Anda tetap bersih dan terintegrasi sepenuhnya.)

aridlehoover
sumber
3
Saya biasanya menemukan bahwa jika tes sulit dibaca, itu bukan kesalahan kerangka kerja tetapi kode yang diuji. Jika SUT rumit untuk diatur, maka mungkin harus dipecah menjadi lebih banyak konsep.
Steve Freeman
10

Jika Anda bekerja dengan .Net 3.5, Anda mungkin ingin melihat ke perpustakaan mocking Moq - menggunakan pohon ekspresi dan lambda untuk menghapus idiom balasan catatan non-intuitif dari sebagian besar perpustakaan tiruan lainnya.

Periksa ini quickstart untuk melihat berapa banyak lebih intuitif tes kasus menjadi, di sini adalah contoh sederhana:

// ShouldExpectMethodCallWithVariable
int value = 5;
var mock = new Mock<IFoo>();

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2);

Assert.AreEqual(value * 2, mock.Object.Duplicate(value));
zadam
sumber
5
Saya rasa Rhino Mocks versi baru juga berfungsi seperti ini
George Mauer
6

Ketahui perbedaan antara palsu, tiruan dan rintisan dan kapan harus menggunakannya.

Hindari terlalu menentukan interaksi menggunakan ejekan. Ini membuat tes menjadi rapuh .

Hamish Smith
sumber
3

Ini adalah posting yang sangat membantu!

Saya akan menambahkan bahwa selalu penting untuk memahami Konteks dan Sistem yang Sedang Diuji (SUT). Mengikuti prinsip-prinsip TDD ke surat itu jauh lebih mudah ketika Anda menulis kode baru di lingkungan di mana kode yang ada mengikuti prinsip yang sama. Tetapi ketika Anda menulis kode baru di lingkungan lama non TDD, Anda menemukan bahwa upaya TDD Anda dapat dengan cepat menggelembung jauh melampaui perkiraan dan ekspektasi Anda.

Bagi sebagian dari Anda, yang tinggal di dunia akademis, jadwal dan pengiriman mungkin tidak penting, tetapi dalam lingkungan di mana perangkat lunak adalah uang, memanfaatkan upaya TDD Anda secara efektif sangatlah penting.

TDD sangat tunduk pada Hukum Pengembalian Marginal yang Berkurang . Singkatnya, upaya Anda menuju TDD semakin berharga sampai Anda mencapai titik pengembalian maksimum, setelah itu, waktu yang diinvestasikan ke TDD memiliki nilai yang semakin sedikit.

Saya cenderung percaya bahwa nilai utama TDD ada di batas (kotak hitam) serta dalam pengujian kotak putih sesekali di area kritis misi sistem.


sumber
2

Alasan sebenarnya untuk memprogram dengan antarmuka bukanlah untuk membuat hidup lebih mudah bagi Rhino, tetapi untuk memperjelas hubungan antar objek dalam kode. Antarmuka harus mendefinisikan layanan yang dibutuhkan objek dari lingkungannya. Sebuah kelas menyediakan implementasi khusus dari layanan itu. Baca buku "Desain Objek" Rebecca Wirfs-Brock tentang Peran, Tanggung Jawab, dan Kolaborator.

Steve Freeman
sumber
Setuju ... Saya akan memperbarui pertanyaan saya untuk mencerminkan itu.
Kevin Albrecht
1

Daftar yang bagus. Salah satu hal yang mungkin ingin Anda buat - dan saya tidak bisa memberi Anda banyak saran karena saya baru mulai memikirkannya sendiri - adalah saat kelas harus berada di perpustakaan, namespace, namespace bersarang yang berbeda. Anda bahkan mungkin ingin mengetahui daftar pustaka dan namespace sebelumnya dan mengamanatkan bahwa tim harus bertemu dan memutuskan untuk menggabungkan dua / menambahkan yang baru.

Oh, baru saja memikirkan sesuatu yang saya lakukan yang mungkin Anda inginkan juga. Saya biasanya memiliki perpustakaan tes unit dengan perlengkapan tes per kebijakan kelas di mana setiap tes masuk ke namespace yang sesuai. Saya juga cenderung memiliki pustaka pengujian lain (tes integrasi?) Yang lebih bergaya BDD . Ini memungkinkan saya untuk menulis tes untuk menentukan metode apa yang harus dilakukan serta apa yang harus dilakukan aplikasi secara keseluruhan.

George Mauer
sumber
Saya juga melakukan bagian pengujian gaya BDD serupa (selain kode pengujian unit) dalam proyek pribadi.
Kevin Albrecht
0

Ini satu lagi yang saya pikir ingin saya lakukan.

Jika Anda berencana untuk menjalankan pengujian dari unit test Gui sebagai lawan dari TestDriven.Net atau NAnt maka saya merasa lebih mudah untuk mengatur jenis proyek pengujian unit ke aplikasi konsol daripada perpustakaan. Ini memungkinkan Anda menjalankan pengujian secara manual dan melewatinya dalam mode debug (yang sebenarnya dapat dilakukan TestDriven.Net untuk Anda).

Selain itu, saya selalu ingin memiliki proyek Playground yang terbuka untuk menguji bit kode dan ide yang tidak saya kenal. Ini tidak harus diperiksa ke dalam kendali sumber. Lebih baik lagi, ini harus berada dalam repositori kontrol sumber terpisah hanya di mesin pengembang.

George Mauer
sumber