Unit menguji kelas yang melacak status

Saya mengabstraksi bagian pelacakan riwayat kelas saya sehingga terlihat seperti ini:

private readonly Stack<MyObject> _pastHistory = new Stack<MyObject>();

internal virtual Boolean IsAnyHistory { get { return _pastHistory.Any(); } }

internal virtual void AddObjectToHistory(MyObject myObject)
{
  if (myObject == null) throw new ArgumentNullException("myObject");
  _pastHistory.Push(myObject);
}

internal virtual MyObject RemoveLastObject()
{
  if(!IsAnyHistory) throw new InvalidOperationException("There is no previous history.");
  return _pastHistory.Pop();
}

Masalah saya adalah saya ingin menguji unit bahwa Hapus akan mengembalikan objek yang Ditambahkan terakhir.

  • AddObjectToHistory
  • RemoveObjectToHistory -> mengembalikan apa yang dimasukkan melalui AddObjectToHistory

Namun, sebenarnya bukan unit test jika saya harus menelepon Add terlebih dahulu? Namun, satu-satunya cara yang dapat saya lakukan untuk melakukan ini dengan cara pengujian unit yang sebenarnya adalah dengan meneruskan objek Stack di konstruktor ATAU mengejek IsAnyHistory...tetapi mengejek SUT saya juga aneh. Jadi, pertanyaan saya adalah, dari sudut pandang dogmatis, apakah ini unit test? Jika tidak, bagaimana cara membersihkannya...apakah injeksi konstruktor satu-satunya cara saya? Sepertinya sulit untuk melewati benda sederhana? Bolehkah mendorong benda sederhana ini keluar untuk disuntikkan?


person Justin Pihony    schedule 24.06.2013    source sumber
comment
FYI, jika tumpukannya kosong, maka IsAnyHistory akan melempar InvalidOperationException. Anda harus memeriksa Count daripada menggunakan Peek.   -  person Mike Zboray    schedule 24.06.2013
comment
@mikez Hrmmm, saya salah membaca dokumentasi di sana. Terima kasih atas hasil tangkapannya   -  person Justin Pihony    schedule 24.06.2013


Jawaban (5)


Ada dua pendekatan untuk skenario tersebut:

  1. Mengganggu desain, seperti membuat _pastHistory internal/protected atau menyuntikkan tumpukan
  2. Gunakan metode lain (mungkin diuji unit) untuk melakukan verifikasi

Seperti biasa, tidak ada aturan emas, meskipun menurut saya Anda biasanya harus menghindari situasi di mana pengujian unit memaksa perubahan desain (karena perubahan tersebut kemungkinan besar akan menimbulkan ambiguitas/pertanyaan yang tidak perlu pada kode konsumen).

Meskipun demikian, pada akhirnya Andalah yang harus mempertimbangkan seberapa besar Anda ingin kode pengujian unit mengganggu desain (kasus pertama) atau mengubah definisi pengujian unit sempurna (kasus kedua).

Biasanya, menurut saya kasus kedua jauh lebih menarik - tidak mengacaukan kode kelas asli dan kemungkinan besar Anda sudah Add menguji - itu aman untuk diandalkan.

person k.m    schedule 24.06.2013
comment
Saya akan membiarkan pertanyaan ini terbuka selama sekitar satu hari, tetapi saya condong ke arah itu karena Add sedang diuji, dan tidak bergantung pada keadaan eksternal. Saya hanya curiga terhadap tes yang rapuh jika menambahkan perubahan dalam beberapa kapasitas...tapi saya kira dokumentasi Hapus adalah sesuatu seperti: mengembalikan apa yang terakhir ditambahkan....jadi hampir memerlukan penambahan. Terima kasih! - person Justin Pihony; 24.06.2013

Saya pikir ini masih tes unit, dengan asumsi MyObject adalah objek sederhana. Saya sering membuat parameter masukan ke metode pengujian unit.

Saya menggunakan kriteria pengujian unit Michael Feather:

Suatu pengujian bukan merupakan pengujian unit jika:

  • Itu berbicara ke database
  • Ini berkomunikasi melalui jaringan
  • Ini menyentuh sistem file
  • Itu tidak dapat berjalan pada waktu yang sama dengan pengujian unit Anda yang lain
  • Anda harus melakukan hal-hal khusus pada lingkungan Anda (seperti mengedit file konfigurasi) untuk menjalankannya.

Tes yang melakukan hal-hal ini tidaklah buruk. Seringkali mereka layak untuk ditulis, dan mereka dapat ditulis dalam unit test harness. Namun, penting untuk dapat memisahkannya dari pengujian unit yang sebenarnya sehingga kita dapat menyimpan serangkaian pengujian yang dapat dijalankan dengan cepat setiap kali kita melakukan perubahan.

person neontapir    schedule 24.06.2013
comment
Hrmmm, tapi kendala saya adalah saya harus mengandalkan unit kerja lain (AddObjectToHistory) untuk menguji unit kerja saya yang sebenarnya. Jadi, bagi saya rasanya lebih seperti pengujian unit. Jika penambahan pernah berubah, maka pengujian penghapusan saya gagal, sehingga membuat pengujian rapuh terhadap unit lain...Saya benar-benar dapat melihat kedua belah pihak...hanya mencari konsensus keseluruhan - person Justin Pihony; 24.06.2013
comment
Itu tidak jelas dari pertanyaan Anda. Saya rasa Anda ingin dapat memasukkan Stack yang mewakili keadaan awal yang Anda uji, yang masih termasuk dalam pedoman ini. - person neontapir; 24.06.2013
comment
Memperbarui pertanyaan saya untuk memperjelas - person Justin Pihony; 24.06.2013

2 sen saya... bagaimana klien mengetahui apakah penghapusan berhasil atau tidak? Bagaimana seharusnya 'klien' berinteraksi dengan objek ini? Apakah klien akan memasukkan tumpukan ke pelacak riwayat? Perlakukan pengujian hanya sebagai pengguna/konsumen/klien lain dari subjek pengujian.. menggunakan interaksi yang persis sama seperti dalam produksi nyata. Saya belum pernah mendengar aturan apa pun yang menyatakan bahwa Anda tidak diperbolehkan memanggil banyak metode pada objek yang diuji.

Untuk simulasi, tumpukan tidak kosong. Saya baru saja menelepon Tambah - 99% kasus. Saya akan menahan diri untuk tidak menghancurkan enkapsulasi objek itu.. Perlakukan objek seperti manusia (saya rasa saya membacanya di Object Thinking). Suruh mereka melakukan sesuatu.. jangan mendobrak masuk dan masuk.

misalnya Jika Anda ingin seseorang memiliki sejumlah uang di dompetnya,

  • cara sederhananya adalah dengan memberi mereka uang dan membiarkan mereka memasukkannya ke dalam dompet mereka secara internal.
  • membuang dompetnya dan memasukkannya ke dalam dompet di sakunya.

Saya suka Opsi1. Lihat juga bagaimana hal ini membebaskan Anda dari detail implementasi (yang menyebabkan kerapuhan dalam pengujian). Katakanlah besok orang tersebut memutuskan untuk menggunakan dompet online. Pendekatan terakhir akan mematahkan pengujian Anda - pengujian tersebut perlu diperbarui untuk mendorong dompet online sekarang - meskipun perilaku objek tidak rusak.

Contoh lain yang pernah saya lihat adalah untuk menguji Repository.GetX() di mana orang membobol DB untuk memasukkan catatan dengan SQL sekarang dalam pengujian unit.. yang akan jauh lebih bersih dan lebih mudah untuk memanggil Repository.AddX(x ) Pertama. Isolasi memang diinginkan, namun jangan sampai mengesampingkan pragmatisme.

Saya harap saya tidak tampil terlalu kuat di sini.. sungguh menyakitkan bagi saya melihat API objek 'diubah agar dapat diuji' hingga tidak lagi menyerupai 'hal paling sederhana yang dapat berfungsi'.

person Gishu    schedule 25.06.2013
comment
Saya setuju dengan Anda, dan jika saya melakukan injeksi, saya mungkin tidak meneruskan tumpukan, melainkan meneruskan jenis repositori...yang masih tampak berlebihan. Meski begitu, ini hampir sama dengan jawaban Jimmy - person Justin Pihony; 25.06.2013
comment
Untuk sebagian besar, kecuali bahwa pengujian tersebut harus benar-benar meniru konsumen sebenarnya. Mereka tidak boleh menguji melalui jalur yang memudahkan pengujian tetapi berbeda dari aplikasi dalam mode produksi. - person Gishu; 25.06.2013

Saya pikir Anda mencoba untuk menjadi terlalu spesifik dengan definisi Anda tentang pengujian unit. Anda harus menguji perilaku publik kelas Anda, bukan detail implementasinya.

Dari cuplikan kode Anda, sepertinya yang perlu Anda perhatikan hanyalah apakah a) memanggil AddObjectToHistory menyebabkan IsAnyHistory mengembalikan nilai true dan b) RemoveLastObject pada akhirnya menyebabkan IsAnyHistory mengembalikan false.

person rossisdead    schedule 24.06.2013
comment
Saya sedang berpikir untuk menjadi terlalu spesifik. Namun, saya tidak begitu percaya karena saya ingin menguji apakah RemoveLastObject mengembalikan objek terakhir itu. Sepertinya itu bukan tes yang terlalu spesifik. Ini sedang menguji apakah outputnya benar - person Justin Pihony; 24.06.2013
comment
Maaf, melewatkan bagian di mana RemoveLastObject mengembalikan objek. Anda harus mengujinya persis seperti yang diharapkan untuk digunakan. Jika satu-satunya cara untuk memasukkan objek ke dalam riwayat adalah dengan memanggil AddObjectToHistory, maka Anda harus memanggilnya dalam pengujian Anda. Tidak perlu menambahkan injeksi konstruktor kecuali Anda ingin mengizinkannya terlebih dahulu (dan kemudian harus menguji semua itu juga) - person rossisdead; 24.06.2013

Sebagaimana dinyatakan dalam jawaban lain, saya pikir pilihan Anda dapat dipecah seperti itu.

  1. Anda mengambil pendekatan dogmatis terhadap metodologi pengujian Anda dan menambahkan injeksi konstruktor untuk objek tumpukan sehingga Anda dapat memasukkan objek tumpukan palsu Anda sendiri dan menguji metode Anda.

  2. Anda menulis pengujian terpisah untuk menambah dan menghapus, pengujian penghapusan akan menggunakan metode penambahan tetapi menganggapnya sebagai bagian dari penyiapan pengujian. Selama tes penambahan Anda lolos, penghapusan Anda juga harus demikian.

person Eogcloud    schedule 24.06.2013