Unit menguji metode batal?

170

Apa cara terbaik untuk menguji unit metode yang tidak mengembalikan apa pun? Khususnya dalam c #.

Apa yang saya benar-benar coba untuk menguji adalah metode yang mengambil file log dan mem-parsing untuk string tertentu. String kemudian dimasukkan ke dalam basis data. Tidak ada yang belum pernah dilakukan sebelumnya tetapi menjadi SANGAT baru untuk TDD saya bertanya-tanya apakah mungkin untuk menguji ini atau itu sesuatu yang tidak benar-benar diuji.

jdiaz
sumber
55
Tolong jangan gunakan istilah "TDD" jika itu bukan yang Anda lakukan. Anda sedang melakukan Pengujian Unit, bukan TDD. Jika Anda melakukan TDD, Anda tidak akan pernah memiliki pertanyaan seperti "bagaimana menguji suatu metode." Tes akan ada terlebih dahulu, dan kemudian pertanyaannya adalah, "bagaimana agar tes ini lulus?" Tetapi jika Anda sedang melakukan TDD, kode Anda akan ditulis untuk ujian (bukan sebaliknya), dan pada dasarnya Anda akan menjawab pertanyaan Anda sendiri. Kode Anda akan diformat secara berbeda sebagai hasil dari TDD, dan masalah ini tidak akan pernah terjadi. Hanya mengklarifikasi.
Suamere

Jawaban:

151

Jika suatu metode tidak mengembalikan apa pun, itu salah satu dari yang berikut ini

  • imperative - Anda meminta objek untuk melakukan sesuatu pada dirinya sendiri .. mis. ubah status (tanpa mengharapkan konfirmasi .. diasumsikan bahwa itu akan dilakukan)
  • informasi - hanya memberi tahu seseorang bahwa sesuatu terjadi (tanpa mengharapkan tindakan atau tanggapan) masing-masing.

Metode imperatif - Anda dapat memverifikasi apakah tugas itu benar-benar dilakukan. Verifikasi apakah perubahan status benar-benar terjadi. misalnya

void DeductFromBalance( dAmount ) 

dapat diuji dengan memverifikasi apakah saldo posting pesan ini memang kurang dari nilai awal oleh dAmount

Metode informasi - jarang sebagai anggota antarmuka publik objek ... karenanya tidak biasanya diuji unit. Namun jika Anda harus, Anda dapat memverifikasi apakah penanganan yang dilakukan pada pemberitahuan terjadi. misalnya

void OnAccountDebit( dAmount )  // emails account holder with info

dapat diuji dengan memverifikasi apakah email sedang dikirim

Posting detail lebih lanjut tentang metode Anda yang sebenarnya dan orang-orang akan dapat menjawab dengan lebih baik.
Pembaruan : Metode Anda melakukan 2 hal. Saya sebenarnya akan membaginya menjadi dua metode yang sekarang dapat diuji secara independen.

string[] ExamineLogFileForX( string sFileName );
void InsertStringsIntoDatabase( string[] );

String [] dapat dengan mudah diverifikasi dengan memberikan metode pertama dengan file dummy dan string yang diharapkan. Yang kedua agak rumit .. Anda bisa menggunakan Mock (google atau mencari stackoverflow pada mocking frameworks) untuk meniru DB atau menekan DB yang sebenarnya dan memverifikasi apakah string dimasukkan di lokasi yang tepat. Periksa utas ini untuk beberapa buku bagus ... Saya akan merekomendasikan kembali Pengujian Unit Pragmatis jika Anda berada dalam krisis.
Dalam kode itu akan digunakan seperti

InsertStringsIntoDatabase( ExamineLogFileForX( "c:\OMG.log" ) );
Gishu
sumber
1
hai gishu, jawaban yang bagus. Contoh yang Anda berikan ... bukankah lebih banyak Tes Integrasi ...? dan jika demikian, pertanyaannya tetap, bagaimana seseorang benar-benar menguji Metode Void .... mungkin itu mustahil?
andy
2
@andy - tergantung pada definisi Anda tentang 'tes integrasi'. Metode imperatif biasanya mengubah status, sehingga Anda dapat diverifikasi dengan tes unit yang menginterogasi keadaan objek. Metode informasi dapat diverifikasi oleh uji unit yang menghubungkan pendengar tiruan / kolaborator untuk memastikan bahwa subjek uji mengeluarkan pemberitahuan yang benar. Saya pikir keduanya dapat diuji secara wajar melalui unit test.
Gishu
@andy database dapat diejek / dipisahkan oleh antarmuka accessor sehingga memungkinkan tindakan untuk diuji oleh data yang diteruskan ke objek tiruan.
Peter Geiger
62

Uji efek sampingnya. Ini termasuk:

  • Apakah ada pengecualian? (Jika memang harus, periksa ya. Jika tidak, coba beberapa kasus sudut yang mungkin jika Anda tidak hati-hati - argumen nol menjadi hal yang paling jelas.)
  • Apakah ini cocok dengan parameternya? (Jika mereka bisa berubah, apakah itu bermutasi ketika itu tidak seharusnya dan sebaliknya?)
  • Apakah itu memiliki efek yang tepat pada keadaan objek / tipe yang Anda panggil?

Tentu saja, ada batasan seberapa banyak Anda dapat menguji. Anda biasanya tidak dapat menguji dengan setiap input yang mungkin, misalnya. Uji secara pragmatis - cukup untuk memberi Anda keyakinan bahwa kode Anda dirancang dengan tepat dan diimplementasikan dengan benar, dan cukup untuk bertindak sebagai dokumentasi tambahan untuk apa yang mungkin diharapkan oleh penelepon.

Jon Skeet
sumber
31

Seperti biasa: uji apa metode yang seharusnya dilakukan!

Haruskah itu mengubah keadaan global (ya, bau kode!) Di suatu tempat?

Haruskah itu memanggil antarmuka?

Haruskah ia melempar pengecualian ketika dipanggil dengan parameter yang salah?

Haruskah tidak ada pengecualian saat dipanggil dengan parameter yang tepat?

Haruskah itu ...?

David Schmitt
sumber
11

Jenis pengembalian tidak valid / Subrutin adalah berita lama. Saya belum membuat tipe Void return (Kecuali saya menjadi sangat malas) dalam waktu 8 tahun (Dari saat jawaban ini, jadi hanya sedikit sebelum pertanyaan ini diajukan).

Alih-alih metode seperti:

public void SendEmailToCustomer()

Buat metode yang mengikuti paradigma int.TryParse () Microsoft:

public bool TrySendEmailToCustomer()

Mungkin tidak ada informasi metode Anda perlu kembali untuk digunakan dalam jangka panjang, tetapi mengembalikan keadaan metode setelah melakukan tugasnya adalah penggunaan yang sangat besar bagi penelepon.

Juga, bool bukan satu-satunya tipe negara. Ada beberapa kali Subroutine yang dibuat sebelumnya benar-benar dapat mengembalikan tiga keadaan yang berbeda (Baik, Normal, Buruk, dll.). Dalam kasus itu, Anda hanya akan menggunakan

public StateEnum TrySendEmailToCustomer()

Namun, sementara Try-Paradigm agak menjawab pertanyaan ini tentang cara menguji void return, ada pertimbangan lain juga. Misalnya, selama / setelah siklus "TDD", Anda akan "Refactoring" dan perhatikan Anda melakukan dua hal dengan metode Anda ... sehingga melanggar "Prinsip Tanggung Jawab Tunggal." Jadi itu harus diurus dulu. Kedua, Anda mungkin telah mengidentifikasi ketergantungan ... Anda menyentuh Data "Persisten".

Jika Anda melakukan hal-hal akses data dalam metode-dalam-pertanyaan, Anda perlu merevisi ke dalam arsitektur n-tier'd atau n-layer'd. Tetapi kita dapat mengasumsikan bahwa ketika Anda mengatakan "string kemudian dimasukkan ke dalam database", Anda sebenarnya berarti Anda memanggil lapisan logika bisnis atau sesuatu. Ya, kami akan menganggap itu.

Ketika objek Anda dipakai, Anda sekarang mengerti bahwa objek Anda memiliki dependensi. Inilah saatnya Anda perlu memutuskan apakah akan melakukan Injeksi Ketergantungan pada Objek, atau pada Metode. Itu berarti Konstruktor Anda atau metode-dalam-pertanyaan memerlukan Parameter baru:

public <Constructor/MethodName> (IBusinessDataEtc otherLayerOrTierObject, string[] stuffToInsert)

Sekarang Anda dapat menerima antarmuka objek tingkat bisnis / data Anda, Anda dapat mengejeknya selama Tes Unit dan tidak memiliki ketergantungan atau takut terhadap pengujian integrasi "Terkadang".

Jadi dalam kode langsung Anda, Anda memasukkan IBusinessDataEtcobjek NYATA . Tetapi dalam Unit Testing Anda, Anda meneruskan IBusinessDataEtcobjek MOCK . Di Mock itu, Anda bisa menyertakan Properti Non-Antarmuka seperti int XMethodWasCalledCountatau sesuatu yang statusnya diperbarui ketika metode antarmuka dipanggil.

Jadi Tes Unit Anda akan melalui Metode Anda -Dalam-Pertanyaan, melakukan logika apa pun yang mereka miliki, dan memanggil satu atau dua, atau serangkaian metode yang dipilih di IBusinessDataEtcobjek Anda . Ketika Anda melakukan Pernyataan Anda di akhir Tes Unit Anda, Anda memiliki beberapa hal untuk diuji sekarang.

  1. Keadaan "Subroutine" yang sekarang menjadi metode Try-Paradigm.
  2. Keadaan IBusinessDataEtcobjek Mock Anda .

Untuk informasi lebih lanjut tentang ide-ide Ketergantungan Injeksi pada tingkat Konstruksi ... karena mereka berkaitan dengan Unit Testing ... lihat pola desain Builder. Ini menambahkan satu lagi antarmuka dan kelas untuk setiap antarmuka / kelas saat ini yang Anda miliki, tetapi mereka sangat kecil dan memberikan peningkatan fungsionalitas BESAR untuk Unit-Testing yang lebih baik.

Suamere
sumber
Bagian pertama dari jawaban hebat ini berfungsi sebagai saran umum yang luar biasa untuk semua programmer pemula / menengah.
pimbrouwers
bukankah seharusnya seperti itu public void sendEmailToCustomer() throws UndeliveredMailException?
A.Emad
1
@ A.Emad Pertanyaan bagus, tapi tidak. Mengontrol aliran kode Anda dengan mengandalkan melemparkan pengecualian adalah praktik buruk yang sudah lama diketahui. Padahal, dalam voidmetode, terutama dalam bahasa Berorientasi Objek, itu telah menjadi satu-satunya pilihan nyata. Alternatif tertua oleh Microsoft adalah Try-Paradigm yang saya diskusikan, dan paradigma gaya fungsional seperti Monads / Maybes. Dengan demikian, Perintah (Dalam CQS) masih dapat mengembalikan informasi status yang berharga alih-alih mengandalkan melempar, yang mirip dengan GOTO(Yang kita tahu buruk). melempar (dan goto) lambat, sulit untuk debug, dan bukan praktik yang baik.
Suamere
Terima kasih sudah membereskan ini. Apakah itu khusus C # atau itu praktik buruk pada umumnya untuk melempar pengecualian bahkan dalam bahasa seperti Java dan C ++?
A.Emad
9

Coba ini:

[TestMethod]
public void TestSomething()
{
    try
    {
        YourMethodCall();
        Assert.IsTrue(true);
    }
    catch {
        Assert.IsTrue(false);
    }
}
Nathan Alard
sumber
1
Ini seharusnya tidak perlu tetapi bisa dilakukan seperti itu
Nathan Alard
Selamat datang di StackOverflow! Silakan pertimbangkan untuk menambahkan beberapa penjelasan ke kode Anda. Terima kasih.
Aurasphere
2
Ini ExpectedAttributedirancang untuk membuat tes ini lebih jelas.
Martin Liversage
8

Anda bahkan dapat mencobanya dengan cara ini:

[TestMethod]
public void ReadFiles()
{
    try
    {
        Read();
        return; // indicates success
    }
    catch (Exception ex)
    {
        Assert.Fail(ex.Message);
    }
}
Reyan Chougle
sumber
1
Kurasa ini cara paling sederhana.
Navin Pandit
5

itu akan memiliki beberapa efek pada objek .... permintaan untuk hasil efek. Jika tidak memiliki efek yang terlihat, pengujian unit tidak layak!

Keith Nicholas
sumber
4

Mungkin metode ini melakukan sesuatu, dan tidak hanya kembali?

Dengan asumsi inilah masalahnya, maka:

  1. Jika itu memodifikasi keadaan objek pemiliknya, maka Anda harus menguji bahwa keadaan berubah dengan benar.
  2. Jika mengambil beberapa objek sebagai parameter dan memodifikasi objek itu, maka Anda harus menguji objek tersebut dengan benar dimodifikasi.
  3. Jika melempar pengecualian adalah kasus tertentu, uji bahwa pengecualian itu benar dilemparkan.
  4. Jika perilakunya bervariasi berdasarkan pada keadaan objeknya sendiri, atau beberapa objek lain, atur status dan uji metode tersebut dengan benar melalui salah satu dari tiga metode pengujian di atas).

Jika Anda memberi tahu kami apa metode ini, saya bisa lebih spesifik.

David Arno
sumber
3

Gunakan Rhino Mocks untuk mengatur panggilan, tindakan, dan pengecualian apa yang mungkin diharapkan. Dengan asumsi Anda dapat mengejek atau mematikan bagian dari metode Anda. Sulit diketahui tanpa mengetahui beberapa spesifik di sini tentang metode, atau bahkan konteks.

merpati
sumber
1
Jawaban untuk bagaimana unit test seharusnya tidak mendapatkan alat pihak ketiga yang melakukannya untuk Anda. Padahal, ketika seseorang tahu bagaimana melakukan tes, menggunakan alat pihak ketiga untuk membuatnya lebih mudah diterima.
Suamere
2

Tergantung pada apa yang dilakukannya. Jika memiliki parameter, masukkan tiruan yang bisa Anda tanyakan nanti jika parameter tersebut dipanggil dengan set parameter yang tepat.

André
sumber
Setuju - memverifikasi perilaku tiruan yang menguji metode ini akan menjadi salah satu cara.
Jeff Schumacher
0

Apa pun contoh yang Anda gunakan untuk memanggil metode void, Anda bisa menggunakan,Verfiy

Sebagai contoh:

Dalam kasus saya ini _Logadalah contoh dan LogMessagemetode yang akan diuji:

try
{
    this._log.Verify(x => x.LogMessage(Logger.WillisLogLevel.Info, Logger.WillisLogger.Usage, "Created the Student with name as"), "Failure");
}
Catch 
{
    Assert.IsFalse(ex is Moq.MockException);
}

Apakah Verifylemparan pengecualian karena kegagalan metode tes akan Gagal?

Shreya Kesharkar
sumber