Dengan asumsi saya memiliki struktur kelas berikut (terlalu disederhanakan):
class Base
{
public:
Base(int valueForFoo) : foo(valueForFoo) { };
virtual ~Base() = 0;
int doThings() { return foo; };
int doOtherThings() { return 42; };
protected:
int foo;
}
class BarDerived : public Base
{
public:
BarDerived() : Base(12) { };
~BarDerived() { };
int doBarThings() { return foo + 1; };
}
class BazDerived : public Base
{
public:
BazDerived() : Base(25) { };
~BazDerived() { };
int doBazThings() { return 2 * foo; };
}
Seperti yang Anda lihat, doThings
fungsi di kelas Basis mengembalikan hasil yang berbeda di setiap kelas yang diturunkan karena nilai yang berbeda dari foo
, sementara doOtherThings
fungsi berperilaku identik di semua kelas .
Ketika saya ingin mengimplementasikan unit test untuk kelas-kelas ini, penanganan doThings
, doBarThings
/ doBazThings
jelas bagi saya - mereka perlu dibahas untuk setiap kelas turunan. Tetapi bagaimana seharusnya doOtherThings
ditangani? Apakah praktik yang baik pada dasarnya menduplikasi kasus uji di kedua kelas turunan? Masalahnya menjadi lebih buruk jika ada setengah lusin fungsi suka doOtherThings
, dan lebih banyak turunannya kelas .
c++
unit-testing
testing
CharonX
sumber
sumber
virtual
?BarDerived
danBase
mungkin pernah menjadi kelas yang sama. Ketika fungsi serupa ditambahkan, bagian umum dipindahkan ke kelas Base, dengan spesialisasi yang berbeda diimplementasikan pada setiap kelas turunan.Jawaban:
Dalam pengujian
BarDerived
Anda, Anda ingin membuktikan bahwa semua metode (publik)BarDerived
bekerja dengan benar (untuk situasi yang telah Anda uji). Demikian pula untukBazDerived
.Fakta bahwa beberapa metode diimplementasikan dalam kelas dasar tidak mengubah tujuan pengujian ini untuk
BarDerived
danBazDerived
. Itu mengarah pada kesimpulan yangBase::doOtherThings
harus diuji baik dalam konteksBarDerived
danBazDerived
dan bahwa Anda mendapatkan tes yang sangat mirip untuk fungsi itu.Keuntungan dari pengujian
doOtherThings
untuk setiap kelas turunan adalah bahwa jika persyaratan untukBarDerived
perubahan seperti ituBarDerived::doOtherThings
harus kembali 24, maka kegagalan tes dalamBazDerived
testcase memberi tahu Anda bahwa Anda mungkin melanggar persyaratan dari kelas lain.sumber
Saya biasanya mengharapkan Base memiliki spesifikasi sendiri, yang dapat Anda verifikasi untuk implementasi yang sesuai, termasuk kelas turunannya.
sumber
Anda memiliki konflik di sini.
Anda ingin menguji nilai kembali dari
doThings()
, yang bergantung pada literal (nilai const).Setiap tes yang Anda tulis untuk ini secara inheren akan mendidih menguji nilai const , yang tidak masuk akal.
Untuk menunjukkan kepada Anda contoh yang lebih masuk akal (saya lebih cepat dengan C #, tetapi prinsipnya sama)
Kelas ini dapat diuji secara bermakna:
Ini lebih masuk akal untuk diuji. Keluarannya didasarkan pada input yang Anda pilih untuk diberikan. Dalam kasus seperti itu, Anda dapat memberikan kelas input yang dipilih secara sewenang-wenang, mengamati hasilnya, dan menguji apakah kelas itu sesuai dengan harapan Anda.
Beberapa contoh prinsip pengujian ini. Perhatikan bahwa contoh saya selalu memiliki kendali langsung atas apa input dari metode yang dapat diuji.
Semua tes ini dapat memasukkan nilai apa pun yang mereka inginkan , selama harapan mereka sesuai dengan apa yang mereka masukkan.
Tetapi jika output Anda ditentukan oleh nilai konstan, maka Anda harus membuat ekspektasi konstan, dan kemudian menguji apakah yang pertama cocok dengan yang kedua. Yang konyol, ini selalu atau tidak akan pernah terjadi; tak satu pun dari keduanya merupakan hasil yang bermakna.
Beberapa contoh prinsip pengujian ini. Perhatikan bahwa contoh-contoh ini tidak memiliki kendali atas setidaknya satu dari nilai-nilai yang sedang dibandingkan.
Tes-tes ini untuk satu nilai tertentu. Jika Anda mengubah nilai ini, tes Anda akan gagal. Intinya, Anda tidak menguji apakah logika Anda berfungsi untuk input yang valid, Anda hanya menguji apakah seseorang dapat memprediksi hasil yang secara akurat tidak dapat mereka kendalikan.
Itu pada dasarnya menguji pengetahuan penulis tes tentang logika bisnis. Itu tidak menguji kode, tetapi penulis sendiri.
Mengembalikan ke contoh Anda:
Kenapa
BarDerived
harus selalufoo
sama dengan12
? Apa artinya ini?Dan mengingat bahwa Anda sudah memutuskan ini, apa yang Anda coba dapatkan dengan menulis tes yang menegaskan bahwa
BarDerived
selalufoo
sama dengan12
?Ini menjadi lebih buruk jika Anda mulai memfaktorkan yang
doThings()
dapat ditimpa dalam kelas turunan. Bayangkan jikaAnotherDerived
ditimpadoThings()
sehingga selalu kembalifoo * 2
. Sekarang, Anda akan memiliki kelas yang hardcoded sebagaiBase(12)
, yangdoThings()
nilainya 24. Meskipun secara teknis dapat diuji, itu tidak memiliki makna kontekstual. Tes ini tidak dapat dipahami.Saya benar-benar tidak bisa memikirkan alasan untuk menggunakan pendekatan nilai hardcoded ini. Bahkan jika ada use case yang valid, saya tidak mengerti mengapa Anda mencoba menulis tes untuk mengonfirmasi nilai hardcoded ini . Tidak ada untungnya dengan menguji jika nilai konstan sama dengan nilai konstan yang sama.
Kegagalan tes apa pun secara inheren membuktikan bahwa tes itu salah . Tidak ada hasil di mana kegagalan pengujian membuktikan bahwa logika bisnis salah. Anda secara efektif tidak dapat mengkonfirmasi tes mana yang dibuat untuk mengonfirmasi di tempat pertama.
Masalahnya tidak ada hubungannya dengan warisan, jika Anda bertanya-tanya. Anda kebetulan menggunakan nilai const di konstruktor kelas dasar, tetapi Anda bisa menggunakan nilai const ini di tempat lain dan kemudian itu tidak akan terkait dengan kelas yang diwarisi.
Edit
Ada kasus di mana nilai hardcoded tidak menjadi masalah. (sekali lagi, maaf untuk sintaks C # tetapi prinsipnya masih sama)
Sementara nilai konstan (
2
/3
) masih mempengaruhi nilai outputGetMultipliedValue()
, konsumen kelas Anda masih memiliki kendali atas itu juga!Dalam contoh ini, tes yang berarti masih dapat ditulis:
base(value, 2)
cocok dengan const ininputValue * 2
.Poin pertama tidak relevan untuk diuji. Yang kedua adalah!
sumber
<
dan>
kawat gigi yang merangkum tag HTML. Sayangnya (karena kegilaan) browser "khusus" menggunakan![{
dan}]!
sebagai gantinya, dan Intitech memutuskan bahwa Anda perlu mendukung browser ini di penulis HTML Anda. Misalnya Anda memilikigetHeaderStart()
dangetHeaderEnd()
fungsi yang - sampai sekarang - dikembalikan<HEADER>
dan<\HEADER>
.<
, yang lain dengan![{
. Tapi itu agak buruk. Jadi Anda memiliki fungsi masing-masing insert karakter (s) ditetapkan dalam variabel di tempat<
dan>
akan pergi, dan membuat kelas turunan yang - tergantung jika mereka standar sesuai atau gila, memberikan keperluan ini,<HEADER>
atau![{HEADER}]!
BaikgetHeaderStart()
fungsi tergantung pada input dan bergantung pada set konstan selama konstruksi kelas turunan. Namun, saya akan merasa tidak enak jika Anda mengatakan kepada saya bahwa mengujinya tidak masuk akal ...getHeaderStart()
masuk akal atau tidak?