Bagaimana saya bisa menyatukan metode pengujian yang menggunakan metode statis?

8

Mari kita asumsikan saya menulis metode ekstensi dalam C # untuk bytearray yang mengkodekannya menjadi string hex, sebagai berikut:

public static class Extensions
{
    public static string ToHex(this byte[] binary)
    {
        const string chars = "0123456789abcdef";
        var resultBuilder = new StringBuilder();
        foreach(var b in binary)
        {
            resultBuilder.Append(chars[(b >> 4) & 0xf]).Append(chars[b & 0xf]);
        }
        return resultBuilder.ToString();
    }
}

Saya bisa menguji metode di atas menggunakan NUnit sebagai berikut:

[Test]
public void TestToHex_Works()
{
    var bytes = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
    Assert.AreEqual("0123456789abcdef", bytes.ToHex());
}

Jika saya menggunakan bagian Extensions.ToHexdalam proyek saya, mari kita asumsikan dalam Foo.Dometode sebagai berikut:

public class Foo
{
    public bool Do(byte[] payload)
    {
        var data = "ES=" + payload.ToHex() + "ff";
        // ...
        return data.Length > 5;
    }
    // ...
}

Maka semua tes Foo.Doakan tergantung pada keberhasilan TestToHex_Works.

Menggunakan fungsi bebas di C ++ hasilnya akan sama: tes yang menguji metode yang menggunakan fungsi bebas akan tergantung pada keberhasilan tes fungsi gratis.

Bagaimana saya bisa menangani situasi seperti itu? Bisakah saya menyelesaikan dependensi tes ini? Apakah ada cara yang lebih baik untuk menguji cuplikan kode di atas?

Akira
sumber
4
Then all tests of Foo.Do will depend on the success of TestToHex_works- Jadi? Anda tidak memiliki kelas yang bergantung pada keberhasilan kelas lain?
Robert Harvey
5
Saya tidak pernah mengerti obsesi ini dengan fungsi bebas / statis dan apa yang disebut non-testability. Jika fungsi bebas bebas dari efek samping, itu adalah hal termudah di planet ini untuk menguji dan membuktikannya bekerja. Anda telah menunjukkan ini dengan cukup efektif dalam pertanyaan Anda sendiri. Bagaimana Anda menguji metode bebas efek samping biasa (yang tidak bergantung pada status kelas) dalam instance objek? Saya tahu Anda memiliki beberapa di antaranya.
Robert Harvey
2
Satu-satunya downside dari kode ini menggunakan fungsi statis adalah bahwa Anda tidak dapat dengan mudah menggunakan sesuatu selain toHex(atau menukar implementasi). Terlepas dari itu semuanya baik-baik saja. Kode Anda yang dikonversi menjadi hex diuji, sekarang ada kode lain yang menggunakan kode yang diuji sebagai utilitas untuk mencapai tujuannya sendiri.
Steve Chamaillard
3
Saya benar-benar kehilangan apa masalahnya di sini. Jika ToHex tidak berfungsi, maka jelas Do tidak akan berfungsi.
Simon B
5
Tes untuk Foo.Do () seharusnya tidak tahu atau peduli bahwa ia memanggil ToHex () di bawah penutup, itu adalah detail implementasi.
17 dari 26

Jawaban:

37

Maka semua tes Foo.Doakan tergantung pada keberhasilanTestToHex _Works.

Iya. Itu sebabnya Anda memiliki tes untukTextToHex . Jika tes tersebut lulus, fungsi tersebut memenuhi spesifikasi yang ditentukan dalam tes tersebut. Jadi Foo.Dobisa dengan aman menyebutnya dan tidak khawatir. Sudah tertutup.

Anda bisa menambahkan antarmuka, menjadikan metode metode instan dan menyuntikkannya Foo . Maka Anda bisa mengejek TextToHex. Tetapi sekarang Anda harus menulis tiruan, yang dapat berfungsi secara berbeda. Jadi, Anda akan memerlukan tes "integrasi" untuk menyatukan keduanya untuk memastikan bagian-bagiannya benar-benar bekerja sama. Apa yang telah dicapai selain membuat segalanya lebih rumit?

Gagasan bahwa unit test harus menguji bagian-bagian kode Anda secara terpisah dari bagian-bagian lain adalah kekeliruan. "Unit" dalam tes unit adalah unit eksekusi yang terisolasi. Jika dua tes dapat dijalankan secara bersamaan tanpa mempengaruhi satu sama lain, maka keduanya dijalankan secara terpisah dan demikian juga dengan unit test. Fungsi statis yang cepat, tidak memiliki pengaturan yang rumit dan tidak memiliki efek samping seperti contoh Anda baik-baik saja untuk digunakan langsung dalam unit test. Jika Anda memiliki kode yang lambat, rumit untuk diatur atau memiliki efek samping, maka tiruan berguna. Mereka harus dihindari di tempat lain.

David Arno
sumber
2
Anda membuat argumen yang cukup bagus untuk pengembangan "test-first". Mengejek ada sebagian karena kode ditulis sedemikian rupa sehingga terlalu sulit untuk diuji, dan jika Anda menulis tes terlebih dahulu, Anda memaksakan diri untuk menulis kode yang lebih mudah diuji.
Robert Harvey
3
Saya memilih murni untuk merekomendasikan tidak mengejek fungsi murni. Melihat itu berkali-kali.
Jared Smith
2

Menggunakan fungsi bebas di C ++ hasilnya akan sama: tes yang menguji metode yang menggunakan fungsi bebas akan tergantung pada keberhasilan tes fungsi gratis.

Bagaimana saya bisa menangani situasi seperti itu? Bisakah saya menyelesaikan dependensi tes ini? Apakah ada cara yang lebih baik untuk menguji cuplikan kode di atas?

Yah, saya tidak melihat ketergantungan di sini. Setidaknya bukan jenis yang memaksa kita untuk melakukan satu tes sebelum yang lain. Ketergantungan yang kami bangun di antara tes (tidak peduli jenisnya) adalah satu keyakinan .

Kami membuat kode (uji-pertama atau tidak) dan kami memastikan tes lulus. Kemudian, kita berada dalam posisi membangun lebih banyak kode. Semua kode yang dibangun di atas yang pertama ini dibangun di atas kepercayaan dan kepastian. Ini kurang lebih apa yang @DavidArno jelaskan (sangat baik) dalam jawabannya.

Iya. Itu sebabnya Anda memiliki tes untuk X. Jika tes tersebut lulus, fungsi tersebut memenuhi spesifikasi yang ditentukan dalam tes tersebut. Jadi Y dapat memanggilnya dengan aman dan tidak khawatir. Sudah tertutup.

Tes unit harus dijalankan dalam urutan apa pun, kapan saja, di lingkungan apa pun, dan secepat mungkin. Apakah TestToHex_Worksdieksekusi yang pertama atau yang terakhir seharusnya tidak membuat Anda khawatir.

Jika TestToHex_Worksgagal karena kesalahan dalam ToHex, semua tes bergantung ToHexakan berakhir dengan hasil yang berbeda dan gagal (idealnya). Kuncinya di sini adalah mendeteksi hasil yang berbeda. Kami melakukannya membuat unit test menjadi deterministik. Seperti yang Anda lakukan di sini

var bytes = new byte[] { 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef };
Assert.AreEqual("0123456789abcdef", bytes.ToHex());

Tes unit lebih lanjut yang mengandalkan ToHexjuga harus bersifat deterministik. Jika semuanya ToHexberjalan dengan baik, hasilnya harus menjadi yang diharapkan. Jika Anda mendapatkan yang berbeda, ada yang salah, di suatu tempat dan ini yang Anda inginkan dari unit test, untuk mendeteksi perubahan halus ini dan gagal dengan cepat.

Laiv
sumber
1

Agak sulit dengan contoh sekecil itu, tapi mari kita pertimbangkan kembali apa yang kita lakukan:

Mari kita asumsikan saya menulis metode ekstensi dalam c # untuk byte array yang mengkodekannya menjadi string hex, sebagai berikut:

Mengapa Anda menulis metode ekstensi untuk melakukan ini? Kecuali jika Anda menulis pustaka untuk menyandikan array byte ke dalam string, ini tidak mungkin menjadi persyaratan yang Anda coba penuhi. Apa yang Anda lakukan di sini berusaha memenuhi persyaratan "Validasi beberapa payload yang merupakan array byte" - jadi unit test yang Anda implementasikan harus "Diberikan payload X yang valid, metode saya mengembalikan true" dan "Diberikan sebuah payload tidak valid Y, metode saya mengembalikan false ".

Jadi, ketika Anda awalnya menerapkan ini, Anda mungkin melakukan hal-hal "byte-to-hex" dalam metode. Dan itu tidak masalah. Kemudian nanti Anda mendapatkan beberapa persyaratan lain (mis. "Tampilkan payload sebagai string hex") yang, saat Anda mengimplementasikannya, Anda sadar juga mengharuskan Anda untuk mengkonversi array byte ke string hex. Pada saat itu Anda membuat metode ekstensi Anda, refactor metode yang lama, dan menyebutnya dari kode baru Anda. Tes Anda untuk fungsionalitas validasi tidak boleh berubah. Tes Anda untuk menampilkan payload harus sama apakah kode byte-ke-hex inline atau dalam metode statis. Metode statis adalah detail implementasi yang tidak perlu Anda pedulikan saat menulis tes. Kode dalam metode statis akan diuji oleh konsumennya. Saya tidak akan

Chris Cooper
sumber
"ini sepertinya bukan persyaratan yang Anda coba penuhi. Sepertinya yang Anda lakukan di sini adalah berusaha memenuhi persyaratan" - Anda membuat banyak asumsi tentang tujuan fungsi yang kemungkinan besar hanya dipilih sebagai contoh yang representatif, dan bukan sesuatu dari program yang lebih besar.
whatsisname
"Unit" dalam "unit testing" tidak berarti fungsi tunggal, atau kelas. Ini berarti unit fungsionalitas. Kecuali jika Anda secara khusus menulis pustaka yang dikonversi byte ke string, bagian itu sangat detail implementasi dari beberapa perilaku yang lebih berguna. Perilaku yang bermanfaat itu adalah bagian yang ingin Anda uji, karena itulah yang Anda pedulikan benar. Di dunia yang ideal Anda ingin dapat menemukan perpustakaan yang sudah ada untuk melakukan bin-to-hex (atau apa pun itu, jangan terpaku pada spesifikasinya), tukar keluar untuk implementasi Anda dan tidak mengubah apa pun tes.
Chris Cooper
Iblis ada dalam perinciannya. Pengujian unit "perincian implementasi" masih merupakan hal yang sangat berguna untuk dilakukan dan bukan sesuatu untuk dilewatkan.
whatsisname
0

Persis. Dan ini adalah salah satu masalah dengan metode statis, yang lain adalah bahwa OOP adalah alternatif yang jauh lebih baik dalam kebanyakan situasi.

Ini juga salah satu alasan Ketergantungan Injeksi digunakan.

Dalam kasus Anda, Anda dapat memilih konverter konkret yang Anda masukkan ke kelas yang membutuhkan konversi beberapa nilai menjadi heksadesimal. sebuah antarmuka , dilaksanakan oleh kelas beton ini, akan menentukan kontrak yang diperlukan untuk mengkonversi nilai-nilai.

Anda tidak hanya dapat menguji kode Anda dengan lebih mudah, tetapi Anda juga dapat menukar implementasi nanti (karena ada banyak cara lain yang memungkinkan untuk mengubah nilai menjadi heksadesimal, beberapa dari mereka menghasilkan output yang berbeda).

Arseni Mourzenko
sumber
2
Bagaimana itu tidak membuat kode tergantung pada keberhasilan tes konverter? Menyuntikkan kelas, antarmuka, atau apa pun yang Anda inginkan tidak membuatnya berfungsi secara ajaib. Jika Anda berbicara tentang efek samping maka ya akan ada satu ton nilai yang menyuntikkan palsu bukan pengkodean keras implementasi, tetapi masalah itu tidak pernah ada sejak awal.
Steve Chamaillard
1
@ArseniMourzenko jadi ketika menyuntikkan kelas kalkulator yang menerapkan multiplymetode, Anda akan mengejek ini? Yang berarti untuk melakukan refactor (dan menggunakan operator * sebagai gantinya), Anda harus mengubah setiap deklarasi tiruan tunggal dalam kode Anda? Tidak terlihat seperti kode mudah bagi saya. Yang saya katakan adalah toHexsangat sederhana dan tidak memiliki efek samping sama sekali jadi saya tidak melihat alasan untuk mengejek atau mematikan ini. Itu terlalu banyak biaya untuk sama sekali tanpa keuntungan. Tidak mengatakan kita tidak harus menyuntikkannya, tapi itu tergantung penggunaan.
Steve Chamaillard
2
@ Ewan, jika saya memodifikasi sepotong kode dan kemudian menghadapi " lautan merah ", saya bisa berpikir oh tidak, di mana saya mulai ?. Tapi mungkin saya bisa kembali ke potongan kode yang baru saja saya modifikasi dan melihat tesnya terlebih dahulu untuk melihat apa yang saya rusak. : p
David Arno
3
@ArseniMourzenko, jika DI membuat desain Anda jauh lebih baik, mengapa saya tidak melihat argumen untuk jenis suntikan seperti Stringdan intjuga? Atau kelas utilitas dasar dari perpustakaan standar?
Bart van Ingen Schenau
4
Jawaban Anda sepenuhnya mengabaikan aspek praktis. Ada banyak situasi dengan metode statis yang cepat, mandiri, dan tidak mungkin cukup untuk pernah berubah, bahwa dengan upaya menyuntikkan mereka atau melompat melalui lingkaran apa pun selain panggilan fungsi biasa, adalah buang-buang waktu yang sia-sia dan upaya.
whatsisname