Unit menguji metode pribadi dalam c ++ menggunakan kelas teman

15

Saya tahu ini adalah praktik yang diperdebatkan, tetapi anggaplah ini adalah pilihan terbaik bagi saya. Saya bertanya-tanya tentang apa teknik sebenarnya untuk melakukan ini. Pendekatan yang saya lihat adalah ini:

1) Buat kelas teman dari kelas yang metode yang ingin saya uji.

2) Di kelas teman, buat metode publik yang memanggil metode pribadi dari kelas yang diuji.

3) Uji metode publik dari kelas teman.

Berikut adalah contoh sederhana untuk menggambarkan langkah-langkah di atas:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

Edit:

Saya melihat bahwa dalam diskusi mengikuti salah satu jawaban orang bertanya-tanya tentang basis kode saya.

Kelas saya memiliki metode yang dipanggil dengan metode lain; tidak satu pun dari metode ini harus dipanggil di luar kelas, sehingga harus bersifat pribadi. Tentu saja mereka dapat dimasukkan ke dalam satu metode, tetapi secara logis mereka jauh lebih baik terpisah. Metode ini cukup rumit untuk menjamin pengujian unit, dan karena masalah kinerja saya kemungkinan besar harus faktor ulang metode ini, maka akan lebih baik untuk memiliki tes untuk memastikan bahwa re-factoring saya tidak merusak apa pun. Saya bukan satu-satunya yang bekerja di tim, meskipun saya satu-satunya yang mengerjakan proyek ini termasuk tes.

Setelah mengatakan hal di atas, pertanyaan saya bukan tentang apakah itu praktik yang baik untuk menulis unit test untuk metode pribadi, meskipun saya menghargai umpan baliknya.

Akavall
sumber
5
Berikut adalah saran yang dipertanyakan untuk pertanyaan yang dipertanyakan. Saya tidak suka kopling teman karena kode yang dirilis harus tahu tentang tes. Jawaban Nir di bawah ini adalah salah satu cara untuk meringankannya, tetapi saya masih tidak suka mengubah kelas agar sesuai dengan ujian. Karena saya tidak terlalu bergantung pada pewarisan, saya terkadang hanya membuat metode pribadi yang dilindungi dan memiliki kelas tes mewarisi dan mengekspos sesuai kebutuhan. Saya mengharapkan setidaknya tiga "boo desis" untuk komentar ini, tetapi kenyataannya adalah bahwa API publik dan API pengujian dapat berbeda dan masih berbeda dari API pribadi. Ah.
J Trana
4
@JTrana: Mengapa tidak menuliskannya sebagai jawaban yang tepat?
Bart van Ingen Schenau
Baik. Ini bukan salah satu dari yang Anda merasa sangat bangga, tapi mudah-mudahan ini akan membantu.
J Trana

Jawaban:

23

Alternatif untuk teman (dalam arti tertentu) yang sering saya gunakan adalah pola yang saya kenal sebagai access_by. Sederhana saja:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

Sekarang, anggaplah kelas B terlibat dalam pengujian A. Anda dapat menulis ini:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

Anda kemudian dapat menggunakan spesialisasi access_by ini untuk memanggil metode pribadi A. Pada dasarnya, apa yang dilakukan adalah meletakkan tanggung jawab untuk menyatakan persahabatan ke file header kelas yang ingin memanggil metode pribadi A. Ini juga memungkinkan Anda menambahkan teman ke A tanpa mengubah sumber A. Secara idiomatis, ini juga menunjukkan kepada siapa pun yang membaca sumber A bahwa A tidak menunjukkan B teman sejati dalam arti memperluas antarmuka. Sebaliknya, antarmuka A selesai seperti yang diberikan dan B membutuhkan akses khusus ke A (pengujian menjadi contoh yang baik, saya juga menggunakan pola ini ketika menerapkan boost python binding, terkadang fungsi yang perlu pribadi di C ++ berguna untuk mengekspos ke lapisan python untuk implementasi).

Nir Friedman
sumber
Ingin tahu tentang kasus penggunaan yang valid untuk membuat friend access_by, bukankah yang pertama bukan cukup teman - menjadi struct bersarang akan memiliki akses ke segala sesuatu dalam A? misalnya. coliru.stacked-crooked.com/a/663dd17ed2acd7a3
tangy
10

Jika sulit untuk diuji, itu ditulis dengan buruk

Jika Anda memiliki kelas dengan metode pribadi yang cukup kompleks untuk menjamin tes mereka sendiri, kelas tersebut melakukan terlalu banyak. Di dalam ada kelas lain yang berusaha keluar.

Ekstrak metode pribadi yang ingin Anda uji ke kelas baru (atau kelas) dan buat publik. Uji kelas baru.

Selain membuat kode lebih mudah untuk diuji, refactoring ini akan membuat kode lebih mudah dipahami dan dipelihara.

kevin cline
sumber
1
Saya sepenuhnya setuju dengan jawaban ini, jika Anda tidak dapat menguji metode pribadi Anda sepenuhnya dengan menguji metode publik, ada sesuatu yang tidak benar dan refactoring metode pribadi ke kelas mereka sendiri akan menjadi solusi yang baik.
David Perfors
4
Dalam basis kode saya, kelas memiliki metode yang sangat kompleks yang menginisialisasi grafik komputasi. Ini memanggil beberapa sub-fungsi secara berurutan untuk mencapai berbagai aspek ini. Setiap sub-fungsi cukup rumit, dan jumlah total kode sangat rumit. Namun, sub-fungsi tidak ada artinya jika tidak dipanggil pada kelas ini dan dalam urutan yang benar. Semua pengguna peduli tentang grafik komputasi yang sepenuhnya diinisialisasi; perantara tidak berharga bagi pengguna. Tolong, saya ingin mendengar bagaimana saya harus memperbaiki ini, dan mengapa itu lebih masuk akal daripada hanya menguji metode pribadi.
Nir Friedman
1
@Nir: Lakukan hal sepele: ekstrak kelas dengan semua metode itu publik, dan buat kelas Anda yang ada menjadi fasad di sekitar kelas baru.
kevin cline
Jawaban ini benar tetapi sangat tergantung pada data yang Anda kerjakan. Dalam kasus saya, saya tidak diberikan data pengujian yang sebenarnya sehingga saya harus membuat beberapa dengan mengamati data waktu nyata dan kemudian "menyuntikkan" ke dalam aplikasi saya dan melihat bagaimana kelanjutannya. Sepotong data terlalu rumit untuk ditangani sehingga akan jauh lebih mudah untuk secara artifisial membuat data pengujian parsial yang hanya sebagian dari data realtime untuk menargetkan masing-masing daripada benar-benar mereproduksi data realtime. Fungsi pribadi tidak kompleks cukup untuk meminta implementasi beberapa kelas kecil lainnya (dengan fungsi masing-masing)
rbaleksandar
4

Anda seharusnya tidak menguji metode pribadi. Titik. Kelas-kelas yang menggunakan kelas Anda hanya peduli dengan metode yang disediakannya, bukan yang digunakannya di bawah tenda untuk bekerja.

Jika Anda khawatir tentang cakupan kode Anda, Anda perlu menemukan konfigurasi yang memungkinkan Anda untuk menguji metode pribadi dari salah satu panggilan metode publik. Jika Anda tidak bisa melakukan itu, apa gunanya memiliki metode itu? Itu hanya kode yang tidak terjangkau.

Ampt
sumber
6
Inti dari metode pribadi adalah untuk memudahkan pengembangan (melalui pemisahan masalah, atau mempertahankan KERING, atau sejumlah hal) tetapi dimaksudkan untuk menjadi tidak permanen. Mereka pribadi karena alasan itu. Mereka dapat muncul, hilang, atau berubah fungsi secara drastis dari satu implementasi ke yang berikutnya, itulah sebabnya mengapa mengikat mereka ke unit test tidak selalu praktis, atau bahkan bermanfaat.
Ampt
8
Ini mungkin tidak selalu praktis atau berguna, tetapi itu jauh dari mengatakan bahwa Anda tidak boleh menguji mereka. Anda berbicara tentang metode pribadi seolah-olah mereka adalah metode pribadi orang lain; "Mereka bisa muncul, menghilang ...". Tidak, mereka tidak bisa. Jika Anda menguji unit secara langsung, itu hanya karena Anda memeliharanya sendiri. Jika Anda mengubah implementasi, Anda mengubah tes. Singkatnya, pernyataan selimut Anda tidak bisa dibenarkan. Meskipun ada baiknya untuk memperingatkan OP tentang hal ini, pertanyaannya masih dibenarkan, dan jawaban Anda tidak benar-benar menjawabnya.
Nir Friedman
2
Izinkan saya juga mencatat: OP mengatakan di muka dia sadar bahwa ini adalah praktik yang diperdebatkan. Jadi jika dia tetap ingin melakukannya, mungkin dia benar-benar punya alasan bagus untuk itu? Kami berdua tidak tahu detail basis kode-nya. Dalam kode saya bekerja dengan, kami memiliki beberapa programmer yang sangat berpengalaman dan ahli, dan mereka pikir itu berguna untuk menguji metode pribadi dalam beberapa kasus.
Nir Friedman
2
-1, jawaban seperti "Anda seharusnya tidak menguji metode pribadi." IMHO tidak membantu. Topik kapan menguji dan kapan tidak menguji metode pribadi telah cukup dibahas di situs ini. OP telah menunjukkan bahwa dia menyadari diskusi ini, dan jelas dia sedang mencari solusi dengan asumsi bahwa dalam kasusnya pengujian metode pribadi adalah cara untuk pergi.
Doc Brown
3
Saya pikir masalahnya di sini adalah bahwa ini mungkin masalah XY yang sangat klasik karena OP berpikir dia perlu menguji unit metode pribadinya karena satu dan lain alasan, ketika pada kenyataannya dia bisa mendekati pengujian dari sudut pandang yang lebih pragmatis, melihat pribadi metode sebagai fungsi pembantu hanya untuk yang umum, yang merupakan kontrak dengan pengguna akhir kelas.
Ampt
3

Ada beberapa opsi untuk melakukan ini, tetapi perlu diingat bahwa mereka (pada dasarnya) memodifikasi antarmuka publik dari modul Anda, untuk memberi Anda akses ke detail implementasi internal (secara efektif mengubah pengujian unit menjadi dependensi klien yang sangat erat, di mana Anda harus memiliki tidak ada ketergantungan sama sekali).

  • Anda bisa menambahkan deklarasi teman (kelas atau fungsi) ke kelas yang diuji.

  • Anda dapat menambahkan #define private publicke awal file pengujian Anda, sebelum #includemembuka kode yang diuji. Jika kode yang diuji adalah pustaka yang sudah dikompilasi, ini bisa membuat header tidak lagi cocok dengan kode biner yang sudah dikompilasi (dan menyebabkan UB).

  • Anda bisa menyisipkan makro di kelas yang diuji dan memutuskan di kemudian hari apa arti makro itu (dengan definisi berbeda untuk kode pengujian). Ini akan memungkinkan Anda untuk menguji internal, tetapi itu juga akan memungkinkan kode klien pihak ketiga untuk meretas ke kelas Anda (dengan membuat definisi sendiri dalam deklarasi yang Anda tambahkan).

utnapistim
sumber
2

Berikut adalah saran yang dipertanyakan untuk pertanyaan yang dipertanyakan. Saya tidak suka kopling teman karena kode yang dirilis harus tahu tentang tes. Jawaban Nir adalah salah satu cara untuk meringankannya, tetapi saya masih tidak suka mengubah kelas agar sesuai dengan ujian.

Karena saya tidak terlalu bergantung pada pewarisan, saya kadang-kadang hanya membuat metode pribadi yang dilindungi dan memiliki kelas uji mewarisi dan mengekspos sesuai kebutuhan. Kenyataannya adalah bahwa API publik dan API pengujian dapat berbeda dan masih berbeda dari API pribadi, yang membuat Anda terikat.

Berikut adalah contoh praktis dari jenis yang saya gunakan untuk trik ini. Saya menulis kode tertanam, dan kami mengandalkan mesin negara cukup sedikit. API eksternal tidak selalu perlu tahu tentang keadaan mesin keadaan internal, tetapi pengujian harus (bisa dibilang) menguji kesesuaian dengan diagram mesin keadaan dalam dokumen desain. Saya mungkin mengekspos pengambil "keadaan saat ini" sebagai dilindungi dan kemudian memberikan akses tes untuk itu, memungkinkan saya untuk menguji mesin negara lebih lengkap. Saya sering menemukan jenis kelas ini sulit untuk diuji sebagai kotak hitam.

J Trana
sumber
Walaupun ini adalah pendekatan Java, ini cukup standar untuk membuat fungsi privat menjadi level default, alih-alih mengizinkan kelas lain dalam paket yang sama (kelas uji) untuk dapat melihatnya.
0

Anda dapat menulis kode dengan banyak solusi untuk menghentikan Anda harus menggunakan teman.

Anda dapat menulis kelas dan tidak pernah memiliki metode pribadi sama sekali. Yang perlu Anda lakukan adalah membuat fungsi implementasi dalam unit kompilasi, biarkan kelas Anda memanggil mereka dan mengirimkan data anggota yang mereka butuhkan untuk mengakses.

Dan ya itu berarti Anda dapat mengubah tanda tangan atau menambahkan metode "implementasi" baru tanpa mengubah tajuk Anda di masa mendatang.

Anda harus mempertimbangkan apakah layak atau tidak. Dan banyak hal akan sangat tergantung pada siapa yang akan melihat tajuk Anda.

Jika saya menggunakan perpustakaan pihak ke-3 saya lebih suka tidak melihat deklarasi teman untuk penguji unit mereka. Saya juga tidak ingin membangun perpustakaan mereka dan menjalankan tes mereka ketika saya melakukannya. Sayangnya terlalu banyak perpustakaan open source pihak ke-3 yang saya buat melakukan itu.

Pengujian adalah pekerjaan penulis perpustakaan, bukan penggunanya.

Namun tidak semua kelas terlihat oleh pengguna perpustakaan Anda. Banyak kelas adalah "implementasi" dan Anda menerapkannya cara terbaik untuk memastikan mereka berfungsi dengan baik. Pada itu, Anda mungkin masih memiliki metode dan anggota pribadi tetapi ingin penguji unit untuk mengujinya. Jadi lanjutkan dan lakukan dengan cara itu jika itu akan mengarah ke kode yang kuat lebih cepat, yang mudah untuk mempertahankan bagi mereka yang perlu melakukannya.

Jika pengguna kelas Anda semua berada dalam perusahaan atau tim Anda sendiri, Anda juga dapat sedikit lebih santai tentang strategi itu, dengan asumsi itu diizinkan oleh standar pengkodean perusahaan Anda.

Uang tunai
sumber