Kesulitan dengan TDD & Refactoring (Atau - Mengapa Ini Lebih Menyakitkan Daripada Seharusnya?)


20

Saya ingin belajar sendiri untuk menggunakan pendekatan TDD dan saya punya proyek yang ingin saya kerjakan untuk sementara waktu. Itu bukan proyek besar jadi saya pikir itu akan menjadi kandidat yang baik untuk TDD. Namun, saya merasa ada sesuatu yang salah. Izinkan saya memberi contoh:

Pada tingkat tinggi, proyek saya merupakan tambahan untuk Microsoft OneNote yang memungkinkan saya melacak dan mengelola Proyek dengan lebih mudah. Sekarang, saya juga ingin menjaga logika bisnis untuk ini sebagai dipisahkan dari OneNote mungkin jika saya memutuskan untuk membangun penyimpanan kustom saya sendiri dan kembali suatu hari nanti.

Pertama saya mulai dengan tes penerimaan kata dasar sederhana untuk menguraikan apa yang saya ingin fitur pertama saya lakukan. Itu terlihat seperti ini (mematikannya untuk singkatnya):

  1. Klik pengguna membuat proyek
  2. Jenis pengguna dalam judul proyek
  3. Verifikasi bahwa proyek dibuat dengan benar

Melewati hal-hal UI dan beberapa perencanaan perantara saya datang ke unit test pertama saya:

[TestMethod]
public void CreateProject_BasicParameters_ProjectIsValid()
{
    var testController = new Controller();
    Project newProject = testController(A.Dummy<String>());
    Assert.IsNotNull(newProject);
}

Sejauh ini baik. Merah, hijau, refactor, dll. Baiklah sekarang sebenarnya perlu menyimpan barang. Memotong beberapa langkah di sini saya berakhir dengan ini.

[TestMethod]
public void CreateProject_BasicParameters_ProjectMatchesExpected()
{
    var fakeDataStore = A.Fake<IDataStore>();
    var testController = new Controller(fakeDataStore);
    String expectedTitle = fixture.Create<String>("Title");
    Project newProject = testController(expectedTitle);

    Assert.AreEqual(expectedTitle, newProject.Title);
}

Saya masih merasa baik pada saat ini. Saya belum memiliki penyimpanan data yang konkret, tetapi saya membuat antarmuka seperti yang saya perkirakan akan terlihat.

Saya akan melewati beberapa langkah di sini karena posting ini sudah cukup lama, tetapi saya mengikuti proses yang sama dan akhirnya saya mendapatkan tes ini untuk penyimpanan data saya:

[TestMethod]
public void SaveNewProject_BasicParameters_RequestsNewPage()
{
    /* snip init code */
    testDataStore.SaveNewProject(A.Dummy<IProject>());
    A.CallTo(() => oneNoteInterop.SavePage()).MustHaveHappened();
}

Ini bagus sampai saya mencoba mengimplementasikannya:

public String SaveNewProject(IProject project)
{
    Page projectPage = oneNoteInterop.CreatePage(...);
}

Dan ada masalah di mana "..." berada. Saya menyadari sekarang pada titik INI bahwa CreatePage memerlukan ID bagian. Saya tidak menyadari hal ini kembali ketika saya berpikir pada level controller karena saya hanya peduli dengan pengujian bit yang relevan dengan controller. Namun, jauh-jauh di sini saya sekarang menyadari bahwa saya harus meminta pengguna untuk lokasi menyimpan proyek. Sekarang saya harus menambahkan ID lokasi ke datastore, lalu menambahkan satu ke proyek, lalu menambahkan satu ke controller, dan menambahkannya ke SEMUA tes yang sudah ditulis untuk semua hal itu. Ini menjadi sangat cepat dan membosankan dan saya merasa seperti saya akan menangkap ini lebih cepat jika saya membuat sketsa desain sebelumnya daripada membiarkannya dirancang selama proses TDD.

Bisakah seseorang tolong jelaskan kepada saya jika saya melakukan kesalahan dalam proses ini? Apakah ada jenis refactoring yang bisa dihindari? Atau ini biasa? Jika itu umum, adakah cara untuk membuatnya lebih tidak menyakitkan?

Terima kasih semuanya!


Anda akan mendapatkan beberapa komentar yang sangat mendalam jika Anda memposting topik ini di forum diskusi ini: groups.google.com/forum /#!forum/... yang khusus untuk topik TDD.
Chuck Krutsinger

1
Jika Anda perlu menambahkan sesuatu ke semua tes Anda, sepertinya tes Anda ditulis dengan buruk. Anda harus memperbaiki tes Anda dan mempertimbangkan untuk menggunakan fixture yang masuk akal.
Dave Hillier

Jawaban:


19

Meskipun TDD (benar) disebut-sebut sebagai cara untuk merancang dan mengembangkan perangkat lunak Anda, masih merupakan ide yang baik untuk memikirkan desain dan arsitektur sebelumnya. IMO, "membuat sketsa desain sebelumnya" adalah permainan yang adil. Namun, sering kali ini akan berada pada level yang lebih tinggi daripada keputusan desain yang akan Anda tuntun melalui TDD.

Memang benar bahwa ketika segalanya berubah, Anda biasanya harus memperbarui tes. Tidak ada cara untuk menghilangkan ini sepenuhnya, tetapi ada beberapa hal yang dapat Anda lakukan untuk membuat tes Anda kurang rapuh dan meminimalkan rasa sakit.

  1. Sebisa mungkin, jauhkan detail implementasi dari tes Anda. Ini berarti hanya menguji melalui metode publik, dan jika memungkinkan mendukung verifikasi berbasis interaksi negara . Dengan kata lain, jika Anda menguji hasil sesuatu daripada langkah - langkah untuk sampai ke sana, tes Anda seharusnya tidak terlalu rapuh.

  2. Minimalkan duplikasi dalam kode pengujian Anda, sama seperti yang Anda lakukan dalam kode produksi. Posting ini adalah referensi yang bagus. Dalam contoh Anda, sepertinya menyakitkan untuk menambahkan IDproperti ke konstruktor Anda karena Anda memanggil konstruktor secara langsung dalam beberapa tes yang berbeda. Sebagai gantinya, cobalah mengekstraksi pembuatan objek ke metode atau menginisialisasi sekali untuk setiap tes dalam metode inisialisasi tes.


Saya sudah membaca manfaat berbasis negara vs berbasis interaksi dan memahaminya sebagian besar waktu. Namun, saya tidak melihat bagaimana itu mungkin dalam setiap kasus tanpa mengekspos properti SECARA EKSPLISIT untuk tes. Ambil contoh saya di atas. Saya tidak yakin bagaimana memeriksa apakah datastore benar-benar dipanggil tanpa menggunakan pernyataan untuk "MustHaveBeenCalled". Adapun poin 2, Anda benar sekali. Saya akhirnya melakukan itu setelah semua pengeditan, tetapi saya hanya ingin memastikan pendekatan saya secara umum konsisten dengan praktik TDD yang diterima. Terima kasih!
Landon

@Landon Ada beberapa kasus di mana pengujian interaksi lebih tepat. Misalnya, memverifikasi bahwa panggilan dilakukan ke basis data atau layanan web. Pada dasarnya, setiap kali Anda perlu mengisolasi tes Anda, terutama dari layanan eksternal.
jhewlett

@Landon I´ma "meyakinkan klasikis", jadi saya tidak terlalu berpengalaman dengan pengujian berbasis interation ... Tapi Anda tidak perlu membuat pernyataan untuk "MustHaveBeenCalled". Jika Anda menguji sebuah penyisipan, Anda dapat menggunakan kueri untuk melihat apakah itu dimasukkan. PS: Saya menggunakan bertopik karena pertimbangan kinerja ketika menguji segala sesuatu kecuali lapisan basis data.
Hbas

@jhewlett Itulah kesimpulan yang telah saya sampaikan juga. Terima kasih!
Landon

@Hbas Tidak ada database untuk ditanyakan. Saya setuju itu akan menjadi cara paling lurus ke depan jika saya memilikinya, tapi saya menambahkan ini ke buku catatan OneNote. Yang terbaik yang bisa saya lakukan adalah menambahkan metode Get ke kelas pembantu interop saya untuk mencoba menarik halaman. Saya BISA menulis tes untuk melakukan itu, tetapi saya merasa seperti saya akan menguji dua hal sekaligus: Apakah saya menyimpan ini? dan Apakah kelas helper saya mengambil halaman dengan benar? Meskipun, saya kira pada beberapa titik pengujian Anda mungkin harus bergantung pada kode lain yang diuji di tempat lain. Terima kasih!
Landon

10

... Saya tidak bisa tidak merasa seperti saya akan menangkap ini lebih cepat jika saya membuat sketsa desain sebelumnya daripada membiarkannya dirancang selama proses TDD ...

Mungkin tidak

Di satu sisi, TDD bekerja dengan baik, memberi Anda tes otomatis saat Anda membangun fungsionalitas, dan segera rusak ketika Anda harus mengubah antarmuka.

Di sisi lain, mungkin jika Anda mulai dengan fitur tingkat tinggi (SaveProject) alih-alih fitur tingkat rendah (CreateProject), Anda akan melihat parameter yang hilang lebih cepat.

Kemudian lagi, mungkin Anda tidak akan melakukannya. Ini adalah eksperimen yang tidak dapat diulang.

Tetapi jika Anda mencari pelajaran untuk waktu berikutnya: mulailah dari atas. Dan pikirkan desainnya sebanyak yang Anda mau dulu.


0

https://frontendmasters.com/courses/angularjs-and-code-testability/ Dari sekitar 2:22:00 sampai akhir (sekitar 1 jam). Maaf bahwa videonya tidak gratis, tetapi saya belum menemukan yang gratis menjelaskannya dengan baik.

Salah satu presentasi terbaik dari penulisan kode yang dapat diuji adalah dalam pelajaran ini. Ini adalah kelas AngularJS, tetapi bagian pengujian ada di sekitar kode java, terutama karena apa yang dia bicarakan tidak ada hubungannya dengan bahasa, dan segala sesuatu yang berkaitan dengan menulis kode yang dapat diuji baik di tempat pertama.

Keajaibannya adalah menulis kode yang dapat diuji, daripada menulis tes kode. Ini bukan tentang menulis kode yang berpura-pura menjadi pengguna.

Dia juga menghabiskan waktu menulis spec dalam bentuk pernyataan uji.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.