Halo lagi! Kurang dari satu minggu yang tersisa sebelum dimulainya kelas dalam grup di kursus
"Pengembang C ++" . Dalam hal ini, kami terus membagikan materi yang bermanfaat yang diterjemahkan secara khusus untuk siswa kursus ini.

Pengujian unit terhadap kode Anda dengan templat mengingatkan dirinya sendiri dari waktu ke waktu. (Anda menguji templat Anda, bukan?) Beberapa templat mudah diuji. Beberapa tidak. Terkadang ada ketidakjelasan tentang implementasi kode-mock (stub) dalam templat yang diuji. Saya telah mengamati beberapa alasan mengapa kode embedding menjadi rumit.
Di bawah ini saya memberikan beberapa contoh dengan sekitar peningkatan kompleksitas implementasi kode.
- Templat mengambil argumen tipe dan objek dengan tipe yang sama dengan referensi dalam konstruktor.
- Templat mengambil argumen tipe. Membuat salinan argumen konstruktor atau tidak menerimanya.
- Templat mengambil argumen tipe dan membuat beberapa templat yang saling berhubungan tanpa fungsi virtual.
Mari kita mulai dengan yang sederhana.
Templat mengambil argumen tipe dan objek dengan tipe yang sama dengan referensi dalam konstruktor
Kasus ini tampaknya sederhana, karena unit test hanya membuat instance dari templat pengujian dengan tipe rintisan. Beberapa pernyataan dapat diperiksa untuk kelas mock. Dan itu saja.
Secara alami, pengujian dengan hanya satu argumen jenis mengatakan apa-apa tentang sisa jumlah tak terbatas jenis yang dapat diteruskan ke templat. Cara yang elegan untuk mengatakan hal yang sama: pola dihubungkan oleh penjumlahan umum, jadi kita mungkin harus menjadi sedikit lebih berwawasan untuk pengujian yang lebih ilmiah. Lebih lanjut tentang ini nanti.
Sebagai contoh:
template <class T> class TemplateUnderTest { T *t_; public: TemplateUnderTest(T *t) : t_(t) {} void SomeMethod() { t->DoSomething(); t->DoSomeOtherThing(); } }; struct MockT { void DoSomething() {
Templat mengambil argumen tipe. Membuat salinan argumen konstruktor atau tidak menerimanya
Dalam hal ini, akses ke objek di dalam templat mungkin tidak dimungkinkan karena hak akses. Anda bisa menggunakan kelas
friend
.
template <class T> class TemplateUnderTest { T t_; friend class UnitTest; public: void SomeMethod() { t.DoSomething(); t.DoSomeOtherThing(); } }; class UnitTest { void Test2() { TemplateUnderTest<MockT> test; test.SomeMethod(); assert(DoSomethingWasCalled(test.t_));
UnitTest :: Test2
memiliki akses ke badan TemplateUnderTest dan dapat memeriksa pernyataan pada salinan internal MockT.
Templat mengambil argumen tipe dan membuat beberapa templat yang saling berhubungan tanpa fungsi virtual
Untuk kasus ini, saya akan melihat contoh dunia nyata:
Asynchronous Google RPC .
Dalam C ++, async gRPC memiliki sesuatu yang disebut CallData, yang, seperti namanya, menyimpan
data yang terkait dengan panggilan RPC . Templat CallData dapat menangani beberapa jenis RPC yang berbeda. Jadi wajar jika diimplementasikan dengan tepat oleh templat.
CallData generik menerima dua jenis argumen: Permintaan dan Respons. Ini bisa terlihat seperti ini:
template <class Request, class Response> class CallData { grpc::ServerCompletionQueue *cq_; grpc::ServerContext context_; grpc::ServerAsyncResponseWriter<Response> responder_;
Tes unit untuk templat CallData harus memeriksa perilaku HandleRequest dan HandleResponse. Fungsi-fungsi ini memanggil sejumlah fungsi anggota. Oleh karena itu, memverifikasi kesehatan panggilan mereka sangat penting untuk kesehatan CallData. Namun, ada triknya.
- Beberapa jenis dari namespace grpc dibuat secara internal dan tidak melewati konstruktor.
ServerAsyncResponseWriter
dan ServerContext
, misalnya. grpc :: ServerCompletionQueue
diteruskan ke konstruktor sebagai argumen, tetapi tidak memiliki fungsi virtual. Hanya destruktor virtual.grpc :: ServerContext
dibuat secara internal dan tidak memiliki fungsi virtual.
Pertanyaannya adalah bagaimana cara menguji CallData tanpa menggunakan gRPC penuh dalam tes? Bagaimana cara mensimulasikan ServerCompletionQueue? Bagaimana cara mensimulasikan ServerAsyncResponseWriter, yang merupakan templat? dan seterusnya ...
Tanpa fungsi virtual, mengganti perilaku pengguna menjadi tugas yang menakutkan. Jenis hardcoded, seperti grpc :: ServerAsyncResponseWriter, tidak dapat dimodelkan karena mereka, hmm, hardcoded dan tidak diimplementasikan.
Ada sedikit akal dalam menyampaikannya sebagai argumen konstruktor. Bahkan jika Anda melakukan ini, itu mungkin tidak masuk akal, karena mereka mungkin kelas akhir atau tidak memiliki fungsi virtual.
Jadi apa yang kita lakukan?
Solusi: Ciri-ciri

Alih-alih menanamkan perilaku kustom dengan mewarisi dari tipe generik (seperti yang dilakukan dalam pemrograman berorientasi objek), INSERT THE TYPE. Kami menggunakan sifat untuk ini. Kami mengkhususkan diri dalam sifat-sifat dalam berbagai cara tergantung pada jenis kode itu: kode produksi atau kode pengujian unit.
Pertimbangkan
CallDataTraits
template <class CallData> class CallDataTraits { using ServerCompletionQueue = grpc::ServerCompletionQueue; using ServerContext = grpc::ServerContext; using ServerAsyncResponseWriter = grpc::ServerAsyncResponseWrite<typename CallData::ResponseType>; };
Ini adalah templat utama untuk sifat yang digunakan untuk kode produksi. Mari kita gunakan dalam CallDatatemplate.
Melihat kode di atas, jelas bahwa kode aplikasi masih menggunakan tipe dari namespace grpc. Namun, kami dapat dengan mudah mengganti tipe grpc dengan tipe dummy. Lihat di bawah.
Ciri memungkinkan kita untuk memilih jenis yang diterapkan di CallData, tergantung pada situasinya. Metode ini tidak memerlukan kinerja tambahan, karena tidak ada fungsi virtual yang tidak perlu dibuat untuk menambah fungsionalitas. Teknik ini juga dapat digunakan di kelas akhir.
Bagaimana Anda suka bahannya? Tulis komentar. Dan sampai jumpa di pintu terbuka ;-)