Bagaimana cara menguji fungsi yang direactored ke pola strategi?

10

Jika saya memiliki fungsi dalam kode saya seperti:

class Employee{

    public string calculateTax(string name, int salary)
    {
        switch (name)
        {
            case "Chris":
                doSomething($salary);
            case "David":
                doSomethingDifferent($salary);
            case "Scott":
               doOtherThing($salary);               
       }
}

Biasanya saya akan menolak ini untuk menggunakan Ploymorphism menggunakan kelas pabrik dan pola strategi:

public string calculateTax(string name)
{
    InameHandler nameHandler = NameHandlerFactory::getHandler(name);
    nameHandler->calculateTax($salary);
}

Sekarang jika saya menggunakan TDD maka saya akan memiliki beberapa tes yang berfungsi pada aslinya calculateTax()sebelum refactoring.

ex:

calculateTax_givenChrisSalaryBelowThreshold_Expect111(){}    
calculateTax_givenChrisSalaryAboveThreshold_Expect111(){}

calculateTax_givenDavidSalaryBelowThreshold_Expect222(){}   
calculateTax_givenDavidSalaryAboveThreshold_Expect222(){} 

calculateTax_givenScottSalaryBelowThreshold_Expect333(){}
calculateTax_givenScottSalaryAboveThreshold_Expect333(){}

Setelah refactoring saya akan memiliki kelas Pabrik NameHandlerFactorydan setidaknya 3 implementasi InameHandler.

Bagaimana saya harus melanjutkan untuk memperbaiki tes saya? Haruskah saya menghapus unit test claculateTax()dari EmployeeTestsdan membuat kelas Test untuk setiap implementasi InameHandler?

Haruskah saya menguji kelas Pabrik juga?

Songo
sumber

Jawaban:

6

Tes lama baik-baik saja untuk memverifikasi yang calculateTaxmasih berfungsi sebagaimana mestinya. Namun, Anda tidak memerlukan banyak kasus uji untuk ini, hanya 3 (atau mungkin lebih, jika Anda ingin menguji penanganan kesalahan juga, menggunakan nilai tak terduga name).

Setiap kasus individu (saat ini diterapkan dalam doSomethinget al.) Harus memiliki serangkaian tes sendiri juga, yang menguji rincian bagian dalam dan kasus khusus terkait dengan setiap implementasi. Dalam pengaturan baru tes ini dapat / harus dikonversi menjadi tes langsung pada kelas Strategi masing-masing.

Saya lebih suka untuk menghapus tes unit lama hanya jika kode mereka latihan, dan fungsi yang diterapkannya, benar-benar tidak ada lagi. Jika tidak, pengetahuan yang disandikan dalam tes-tes ini masih relevan, hanya tes-tes yang perlu di refactored sendiri.

Memperbarui

Mungkin ada beberapa duplikasi antara tes calculateTax(sebut saja tes tingkat tinggi ) dan tes untuk strategi perhitungan individu ( tes tingkat rendah ) - tergantung pada implementasi Anda.

Saya kira implementasi asli dari tes Anda menegaskan hasil perhitungan pajak tertentu, secara implisit memverifikasi bahwa strategi perhitungan spesifik digunakan untuk memproduksinya. Jika Anda menyimpan skema ini, Anda akan memiliki duplikasi. Namun, seperti yang disarankan oleh @Kristof, Anda dapat mengimplementasikan tes tingkat tinggi menggunakan mock juga, untuk memverifikasi hanya bahwa strategi (mock) yang tepat telah dipilih dan dijalankan oleh calculateTax. Dalam hal ini tidak akan ada duplikasi antara tes tingkat tinggi dan rendah.

Jadi jika refactoring tes yang terkena dampak tidak terlalu mahal, saya lebih suka pendekatan yang terakhir. Namun, dalam kehidupan nyata, ketika melakukan beberapa refactoring besar-besaran, saya mentolerir sejumlah kecil duplikasi kode tes jika menghemat waktu saya cukup :-)

Haruskah saya menguji kelas Pabrik juga?

Sekali lagi, itu tergantung. Perhatikan bahwa tes calculateTaxsecara efektif menguji pabrik. Jadi, jika kode pabrik adalah switchblok sepele seperti kode Anda di atas, tes ini mungkin yang Anda butuhkan. Tetapi jika pabrik melakukan beberapa hal yang lebih rumit, Anda mungkin ingin mendedikasikan beberapa tes khusus untuk itu. Itu semua bermuara pada seberapa banyak tes yang Anda butuhkan untuk yakin bahwa kode tersebut benar-benar berfungsi. Jika, setelah membaca kode - atau menganalisis data cakupan kode - Anda melihat jalur eksekusi yang belum diuji, berikan beberapa tes lagi untuk melaksanakannya. Kemudian ulangi ini sampai Anda benar-benar yakin dengan kode Anda.

Péter Török
sumber
Saya memodifikasi kode sedikit agar lebih dekat dengan kode praktis saya yang sebenarnya. Sekarang input kedua salaryke fungsi calculateTax()telah ditambahkan. Dengan cara ini saya pikir saya akan menduplikasi kode tes untuk fungsi asli dan 3 implementasi dari kelas strategi.
Songo
@Songo, silakan lihat pembaruan saya.
Péter Török
5

Saya akan mulai dengan mengatakan bahwa saya bukan ahli dalam pengujian TDD atau unit, tapi inilah cara saya akan menguji ini (saya akan menggunakan kode pseudo-like):

CalculateTaxDelegatesToNameHandler()
{
    INameHandlerFactory fakeNameHandlerFactory = Fake(INameHandlerFactory);
    INameHandler fakeNameHandler = Fake(INameHandler);

    A.Call.To(fakeNameHandlerFactory.getHandler("John")).Returns(fakeNameHandler);

    Employee employee = new Employee(fakeNameHandlerFactory);
    employee.CalculateTax("John");

    Assert.That.WasCalled(fakeNameHandler.calculateTax());
}

Jadi saya akan menguji bahwa calculateTax()metode kelas karyawan dengan benar meminta NameHandlerFactoryuntuk NameHandlerdan kemudian memanggil calculateTax()metode yang dikembalikan NameHandler.

Kristof Claes
sumber
hmmmm jadi maksud Anda saya harus membuat tes sebagai tes perilaku (menguji bahwa fungsi-fungsi tertentu dipanggil) dan membuat pernyataan nilai pada kelas yang didelegasikan?
Songo
Ya, itulah yang akan saya lakukan. Saya memang akan menulis tes terpisah untuk NameHandlerFactory dan NameHandler. Ketika Anda memilikinya, tidak ada alasan untuk menguji fungsionalitasnya lagi dalam Employee.calculateTax()metode ini. Dengan begitu Anda tidak perlu menambahkan tes Karyawan tambahan saat Anda memperkenalkan NameHandler baru.
Kristof Claes
3

Anda mengambil satu kelas (karyawan yang melakukan segalanya) dan membuat 3 kelompok kelas: pabrik, karyawan (yang hanya berisi strategi) dan strategi.

Jadi buat 3 kelompok tes:

  1. Uji pabrik secara terpisah. Apakah ini menangani input dengan benar. Apa yang terjadi ketika Anda melewati orang yang tidak dikenal?
  2. Tes karyawan dalam isolasi. Bisakah Anda menetapkan strategi sewenang-wenang dan berhasil seperti yang Anda harapkan? Apa yang terjadi jika tidak ada strategi atau pengaturan pabrik? (jika itu mungkin dalam kode)
  3. Uji strategi secara terpisah. Apakah masing-masing melakukan strategi yang Anda harapkan? Apakah mereka menangani input batas ganjil secara konsisten?

Anda tentu saja dapat membuat tes otomatis untuk seluruh shebang, tetapi ini sekarang lebih seperti tes integrasi dan harus diperlakukan seperti itu.

Telastyn
sumber
2

Sebelum menulis kode apa pun, saya akan mulai dengan tes untuk Pabrik. Mengejek hal-hal yang saya butuhkan saya akan memaksakan diri untuk berpikir tentang implementasi dan penggunaan.

Daripada saya akan mengimplementasikan Pabrik dan melanjutkan dengan tes untuk setiap implementasi dan akhirnya implementasi sendiri untuk tes tersebut.

Akhirnya saya akan menghapus tes lama.

Patkos Csaba
sumber
2

Pendapat saya adalah bahwa Anda tidak boleh melakukan apa pun, artinya Anda tidak perlu menambahkan tes baru.

Saya menekankan bahwa ini adalah pendapat, dan itu sebenarnya tergantung pada cara Anda memahami harapan dari objek. Apakah Anda pikir pengguna kelas ingin menyediakan strategi untuk perhitungan pajak? Jika dia tidak peduli, maka tes harus mencerminkan itu, dan perilaku yang tercermin dari tes unit harus bahwa mereka seharusnya tidak peduli bahwa kelas sudah mulai menggunakan objek strategi untuk menghitung pajak.

Saya benar-benar mengalami masalah ini beberapa kali ketika menggunakan TDD. Saya pikir alasan utamanya adalah bahwa objek strategi bukanlah ketergantungan alami, sebagai lawan dari ketergantungan arsitektur seperti sumber daya eksternal (file, DB, layanan jarak jauh, dll.). Karena ini bukan ketergantungan alami, saya biasanya tidak mendasarkan perilaku kelas saya pada strategi ini. Naluriku adalah bahwa aku hanya boleh mengubah tesku jika harapan dari kelasku telah berubah.

Ada pos yang bagus dari Paman Bob, yang berbicara persis tentang masalah ini ketika menggunakan TDD.

Saya berpikir bahwa kecenderungan untuk menguji setiap kelas yang terpisah adalah apa yang membunuh TDD. Seluruh keindahan TDD adalah bahwa Anda menggunakan tes untuk memacu skema desain dan bukan sebaliknya.

Rafi Goldfarb
sumber