Haruskah itu "Atur-Tegaskan-Undang-Undang"?

94

Mengenai pola pengujian klasik Arrange-Act-Assert , saya sering menemukan diri saya menambahkan pernyataan balasan yang mendahului Act. Dengan cara ini saya tahu bahwa pernyataan kelulusan benar-benar lulus sebagai hasil dari tindakan tersebut.

Saya menganggapnya sebagai analogi dengan merah dalam merah-hijau-refactor, di mana hanya jika saya telah melihat bilah merah selama pengujian saya, saya tahu bahwa bilah hijau berarti saya telah menulis kode yang membuat perbedaan. Jika saya menulis tes lulus, maka kode apa pun akan memuaskannya; demikian pula, sehubungan dengan Arrange-Assert-Act-Assert, jika pernyataan pertama saya gagal, saya tahu bahwa Undang-undang apa pun akan lolos Assert akhir - sehingga tidak benar-benar memverifikasi apa pun tentang Undang-undang tersebut.

Apakah tes Anda mengikuti pola ini? Mengapa atau mengapa tidak?

Klarifikasi Pembaruan : pernyataan awal pada dasarnya adalah kebalikan dari pernyataan akhir. Ini bukan pernyataan bahwa Atur berhasil; itu adalah pernyataan bahwa Undang-undang belum berhasil.

Carl Manaster
sumber

Jawaban:

121

Ini bukanlah hal yang paling umum untuk dilakukan, tetapi masih cukup umum untuk memiliki namanya sendiri. Teknik ini disebut Guard Assertion . Anda dapat menemukan penjelasan mendetailnya di halaman 490 di buku xUnit Test Patterns yang sangat bagus oleh Gerard Meszaros (sangat disarankan).

Biasanya, saya tidak menggunakan pola ini sendiri, karena saya merasa lebih tepat untuk menulis pengujian khusus yang memvalidasi prasyarat apa pun yang saya rasa perlu dipastikan. Pengujian seperti itu seharusnya selalu gagal jika prasyarat gagal, dan ini berarti saya tidak memerlukannya disematkan di semua pengujian lainnya. Ini memberikan isolasi masalah yang lebih baik, karena satu kasus pengujian hanya memverifikasi satu hal.

Mungkin ada banyak prasyarat yang perlu dipenuhi untuk kasus pengujian tertentu, jadi Anda mungkin memerlukan lebih dari satu Pernyataan Penjaga. Daripada mengulanginya di semua pengujian, memiliki satu (dan satu-satunya) pengujian untuk setiap prasyarat membuat kode pengujian Anda lebih dapat dipertahankan, karena dengan cara itu Anda akan memiliki lebih sedikit pengulangan.

Mark Seemann
sumber
+1, jawaban yang sangat bagus. Bagian terakhir sangat penting, karena ini menunjukkan bahwa Anda dapat menjaga hal-hal sebagai pengujian unit terpisah.
murrekatt
3
Saya biasanya melakukannya dengan cara ini juga, tetapi ada masalah dengan memiliki pengujian terpisah untuk memastikan prasyarat (terutama dengan basis kode besar dengan persyaratan yang berubah) - uji prasyarat akan dimodifikasi seiring waktu dan tidak sinkron dengan 'utama' uji yang mengandaikan prasyarat tersebut. Jadi prasyarat mungkin semuanya baik-baik saja dan hijau tetapi prasyarat tersebut tidak terpenuhi dalam pengujian utama, yang sekarang selalu menunjukkan hijau dan halus. Tetapi jika prasyarat berada dalam pengujian utama, mereka akan gagal. Pernahkah Anda menemukan masalah ini dan menemukan solusi yang bagus untuk itu?
nchaud
2
Jika Anda banyak mengubah tes Anda, Anda mungkin memiliki masalah lain , karena itu akan cenderung membuat tes Anda kurang dapat dipercaya. Bahkan dalam menghadapi persyaratan yang berubah, pertimbangkan untuk mendesain kode dengan cara hanya lampiran .
Mark Seemann
@MarkSeemann Anda benar, bahwa kami harus meminimalkan pengulangan, tetapi di sisi lain mungkin ada banyak hal, yang dapat memengaruhi Atur untuk tes tertentu, meskipun tes untuk Atur sendiri akan lolos. Misalnya, pembersihan untuk uji Atur atau setelah Tes lain rusak dan Atur tidak akan sama seperti dalam uji Atur.
Rekshino
32

Hal ini juga bisa ditetapkan sebagai Arrange- Asumsikan -Act-Tegaskan.

Ada pegangan teknis untuk ini di NUnit, seperti pada contoh di sini: http://nunit.org/index.php?p=theory&r=2.5.7

Ole Lynge
sumber
1
Bagus! Saya suka yang keempat - dan berbeda - dan akurat - "A". Terima kasih!
Carl Manaster
+1, @Ole! Saya suka yang ini juga, untuk kasus-kasus khusus tertentu! Saya akan mencobanya!
John Tobler
8

Berikut contohnya.

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
    range.encompass(7);
    assertTrue(range.includes(7));
}

Bisa jadi saya menulis Range.includes()untuk sekadar membalas dengan benar. Saya tidak melakukannya, tetapi saya dapat membayangkan bahwa saya mungkin akan melakukannya. Atau saya bisa saja salah menuliskannya dengan berbagai cara lain. Saya berharap dan berharap bahwa dengan TDD saya benar-benar melakukannya dengan benar - itu includes()hanya berfungsi - tetapi mungkin saya tidak. Jadi pernyataan pertama adalah pemeriksaan kewarasan, untuk memastikan bahwa pernyataan kedua benar-benar bermakna.

Membaca dengan sendirinya, assertTrue(range.includes(7));mengatakan: "menegaskan bahwa rentang yang dimodifikasi mencakup 7". Baca dalam konteks pernyataan pertama, itu mengatakan: "menegaskan bahwa memanggil encompass () menyebabkannya menyertakan 7. Dan karena mencakup adalah unit yang kita uji, saya pikir itu dari beberapa nilai (kecil).

Saya menerima jawaban saya sendiri; banyak yang lain salah menafsirkan pertanyaan saya tentang pengujian pengaturan. Saya rasa ini sedikit berbeda.

Carl Manaster
sumber
Terima kasih telah kembali dengan memberi contoh, Carl. Nah, di bagian merah dari siklus TDD, hingga encompass () benar-benar melakukan sesuatu; pernyataan pertama tidak ada gunanya, ini hanya duplikasi dari yang kedua. Di hijau, itu mulai berguna. Ini menjadi masuk akal selama refactoring. Mungkin menyenangkan memiliki kerangka kerja UT yang melakukan ini secara otomatis.
Philant
Misalkan Anda TDD kelas Range itu, tidakkah akan ada tes lain yang gagal menguji ctor Range, ketika Anda akan memecahkannya?
Philant
1
@philippe: Saya tidak yakin saya mengerti pertanyaannya. Konstruktor Range dan include () memiliki pengujian unitnya sendiri. Bisakah Anda menjelaskan lebih lanjut?
Carl Manaster
Agar assertFalse (range.includes (7)) pertama gagal, Anda harus memiliki cacat di Range Constructor. Jadi saya bermaksud untuk menanyakan apakah tes untuk konstruktor Range tidak akan rusak pada saat yang sama dengan pernyataan itu. Dan bagaimana dengan menegaskan setelah Undang-undang pada nilai lain: misalnya assertFalse (range.includes (6))?
philant
1
Konstruksi rentang, menurut saya, muncul sebelum fungsi seperti include (). Jadi sementara saya setuju, hanya konstruktor yang salah (atau salah termasuk ()) yang akan menyebabkan pernyataan pertama gagal, pengujian konstruktor tidak akan menyertakan panggilan ke include (). Ya, semua fungsi hingga pernyataan pertama sudah diuji. Tetapi pernyataan negatif awal ini mengomunikasikan sesuatu, dan, menurut saya, sesuatu yang berguna. Bahkan jika setiap pernyataan seperti itu lolos saat awalnya ditulis.
Carl Manaster
7

Sebuah Arrange-Assert-Act-Asserttes selalu bisa refactored menjadi dua tes:

1. Arrange-Assert

dan

2. Arrange-Act-Assert

Tes pertama hanya akan menegaskan apa yang telah diatur di fase Atur, dan tes kedua hanya akan menegaskan apa yang terjadi di fase Act.

Ini memiliki keuntungan memberikan umpan balik yang lebih tepat tentang apakah itu fase Atur atau Tindakan yang gagal, sementara dalam aslinya Arrange-Assert-Act-Assertini digabungkan dan Anda harus menggali lebih dalam dan memeriksa dengan tepat pernyataan apa yang gagal dan mengapa itu gagal untuk mengetahui apakah itu adalah Arrange atau Act yang gagal.

Ini juga memenuhi maksud pengujian unit dengan lebih baik, karena Anda memisahkan pengujian Anda menjadi unit independen yang lebih kecil.

Terakhir, perlu diingat bahwa setiap kali Anda melihat bagian Atur yang serupa dalam pengujian yang berbeda, Anda harus mencoba menarik ini ke dalam metode pembantu bersama, sehingga pengujian Anda lebih KERING dan lebih dapat dipelihara di masa mendatang.

Sammi
sumber
3

Saya sekarang melakukan ini. AAAA dari jenis yang berbeda

Arrange - setup
Act - what is being tested
Assemble - what is optionally needed to perform the assert
Assert - the actual assertions

Contoh tes pembaruan:

Arrange: 
    New object as NewObject
    Set properties of NewObject
    Save the NewObject
    Read the object as ReadObject

Act: 
    Change the ReadObject
    Save the ReadObject

Assemble: 
    Read the object as ReadUpdated

Assert: 
    Compare ReadUpdated with ReadObject properties

Alasan agar ACT tidak memuat pembacaan ReadUpdated adalah karena bukan bagian dari undang-undang tersebut. Tindakan itu hanya mengubah dan menyelamatkan. Jadi sungguh, ARRANGE ReadUpdated untuk penegasan, saya menelepon ASSEMBLE untuk pernyataan. Ini untuk mencegah kebingungan bagian ARRANGE

ASSERT hanya boleh berisi pernyataan. Itu meninggalkan ASSEMBLE antara ACT dan ASSERT yang menyiapkan assert.

Terakhir, jika Anda gagal dalam Atur, tes Anda tidak benar karena Anda harus memiliki tes lain untuk mencegah / menemukan bug sepele ini . Karena untuk skenario yang saya sajikan, seharusnya sudah ada tes lain yang menguji READ dan CREATE. Jika Anda membuat "Penjaga Pernyataan", Anda mungkin melanggar KERING dan membuat pemeliharaan.

Valamas
sumber
1

Melempar pernyataan "pemeriksaan kewarasan" untuk memverifikasi status sebelum Anda melakukan tindakan yang Anda uji adalah teknik lama. Saya biasanya menulisnya sebagai perancah uji untuk membuktikan kepada diri saya sendiri bahwa tes tersebut melakukan apa yang saya harapkan, dan menghapusnya nanti untuk menghindari tes yang berantakan dengan perancah uji. Terkadang, membiarkan perancah membantu ujian berfungsi sebagai naratif.

Dave W. Smith
sumber
1

Saya sudah membaca tentang teknik ini - mungkin dari Anda btw - tapi saya tidak menggunakannya; terutama karena saya terbiasa dengan bentuk triple A untuk pengujian unit saya.

Sekarang, saya menjadi penasaran, dan memiliki beberapa pertanyaan: bagaimana Anda menulis tes Anda, apakah Anda menyebabkan pernyataan ini gagal, mengikuti siklus refactor merah-hijau-merah-hijau, atau apakah Anda menambahkannya setelah itu?

Apakah Anda terkadang gagal, mungkin setelah Anda melakukan refaktorisasi kode? Apa artinya ini untuk Anda? Mungkin Anda dapat membagikan contoh di mana itu membantu. Terima kasih.

dermawan
sumber
Saya biasanya tidak memaksa pernyataan awal gagal - lagipula, seharusnya tidak gagal, seperti seharusnya pernyataan TDD, sebelum metodenya ditulis. Saya tidak menulis itu, ketika saya menulis itu, sebelum , hanya di normal menulis tes, tidak sesudahnya. Sejujurnya, saya tidak dapat mengingatnya gagal - mungkin itu berarti membuang-buang waktu. Saya akan mencoba memberikan contoh, tetapi saya tidak memikirkannya saat ini. Terima kasih atas pertanyaannya; mereka membantu.
Carl Manaster
1

Saya telah melakukan ini sebelumnya ketika menyelidiki tes yang gagal.

Setelah banyak garukan kepala, saya memutuskan bahwa penyebabnya adalah metode yang disebut selama "Atur" tidak berfungsi dengan benar. Kegagalan pengujian menyesatkan. Saya menambahkan Assert setelah mengatur. Ini membuat pengujian gagal di tempat yang menyoroti masalah sebenarnya.

Saya rasa ada juga code bau di sini jika bagian Atur tes terlalu panjang dan rumit.

WW.
sumber
Hal kecil: Saya akan menganggap Arrange yang terlalu rumit lebih merupakan bau desain daripada bau kode - terkadang desainnya sedemikian rupa sehingga hanya Arrange yang rumit yang memungkinkan Anda untuk menguji unit. Saya menyebutkannya karena situasi itu menginginkan perbaikan yang lebih dalam daripada bau kode sederhana.
Carl Manaster
1

Secara umum, saya sangat menyukai "Atur, Bertindak, Tegaskan" dan menggunakannya sebagai standar pribadi saya. Namun, satu hal yang tidak dapat saya ingatkan untuk saya lakukan adalah mengacaukan apa yang telah saya atur ketika penegasan selesai. Dalam kebanyakan kasus, ini tidak menyebabkan banyak gangguan, karena sebagian besar hal secara otomatis menghilang melalui pengumpulan sampah, dll. Jika Anda telah membuat koneksi ke sumber daya eksternal, Anda mungkin ingin menutup koneksi tersebut setelah selesai. dengan pernyataan Anda atau Anda banyak yang memiliki server atau sumber daya mahal di luar sana yang berpegang pada koneksi atau sumber daya penting yang seharusnya dapat diberikan kepada orang lain. Ini sangat penting jika Anda adalah salah satu pengembang yang tidak menggunakan TearDown atau TestFixtureTearDownuntuk membersihkan setelah satu atau lebih tes. Tentu saja, "Atur, Bertindak, Tegaskan" tidak bertanggung jawab atas kegagalan saya untuk menutup apa yang saya buka; Saya hanya menyebutkan "gotcha" ini karena saya belum menemukan sinonim "A-word" yang baik untuk "dispose" untuk direkomendasikan! Ada saran?

John Tobler
sumber
1
@carlmanaster, Anda sebenarnya cukup dekat untuk saya! Saya menempelkannya di TestFixture saya berikutnya untuk mencobanya untuk ukuran. Ini seperti pengingat kecil untuk melakukan apa yang seharusnya diajarkan ibumu: "Jika kamu membukanya, tutup! Jika kamu mengacaukannya, bersihkan!" Mungkin orang lain bisa meningkatkannya tapi setidaknya itu dimulai dengan "a!" Terima kasih atas saran Anda!
John Tobler
1
@carlmanaster, saya mencoba "Annul". Ini lebih baik daripada "membongkar," dan memang berhasil, tapi saya masih mencari kata "A" lain yang menempel di kepala saya sesempurna "Atur, Bertindak, Tegaskan." Mungkin "Musnahkan ?!"
John Tobler
1
Jadi sekarang, saya punya "Atur, Asumsikan, Bertindak, Tegaskan, Hancurkan." Hmmm! Saya terlalu rumit, ya? Mungkin lebih baik aku menciumnya dan kembali ke "Atur, Bertindak, dan Tegaskan!"
John Tobler
1
Mungkin menggunakan R untuk Reset? Aku tahu ini bukan A, tapi kedengarannya seperti kata bajak laut: Aaargh! dan Atur ulang sajak dengan Tegaskan: o
Marcel Valdez Orozco
1

Lihat entri Wikipedia di Design by Contract . Holy Trinity Arrange-Act-Assert adalah upaya untuk menyandikan beberapa konsep yang sama dan tentang membuktikan kebenaran program. Dari artikel:

The notion of a contract extends down to the method/procedure level; the
contract for each method will normally contain the following pieces of
information:

    Acceptable and unacceptable input values or types, and their meanings
    Return values or types, and their meanings
    Error and exception condition values or types that can occur, and their meanings
    Side effects
    Preconditions
    Postconditions
    Invariants
    (more rarely) Performance guarantees, e.g. for time or space used

Ada pertukaran antara jumlah upaya yang dihabiskan untuk menyiapkan ini dan nilai yang ditambahkannya. AAA adalah pengingat yang berguna untuk langkah-langkah minimum yang diperlukan, tetapi tidak boleh menghalangi siapa pun untuk membuat langkah tambahan.

David Clarke
sumber
0

Bergantung pada lingkungan / bahasa pengujian Anda, tetapi biasanya jika sesuatu di bagian Atur gagal, pengecualian dilemparkan dan pengujian gagal menampilkannya alih-alih memulai bagian Undang-undang. Jadi tidak, saya biasanya tidak menggunakan bagian Assert kedua.

Juga, jika bagian Atur Anda cukup kompleks dan tidak selalu mengeluarkan pengecualian, Anda mungkin mempertimbangkan untuk membungkusnya di dalam beberapa metode dan menulis tes sendiri untuk itu, sehingga Anda dapat yakin itu tidak akan gagal (tanpa melempar pengecualian).

schnaader
sumber
0

Saya tidak menggunakan pola itu, karena menurut saya melakukan sesuatu seperti:

Arrange
Assert-Not
Act
Assert

Mungkin tidak ada gunanya, karena seharusnya Anda tahu bagian Atur Anda berfungsi dengan benar, yang berarti bahwa apa pun yang ada di bagian Atur harus diuji juga atau cukup sederhana sehingga tidak memerlukan pengujian.

Menggunakan contoh jawaban Anda:

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7)); // <-- Pointless and against DRY if there 
                                    // are unit tests for Range(int, int)
    range.encompass(7);
    assertTrue(range.includes(7));
}
Marcel Valdez Orozco
sumber
Saya khawatir Anda tidak terlalu memahami pertanyaan saya. Penegasan awal bukanlah tentang pengujian Atur; itu hanya memastikan bahwa Undang-undang itulah yang membuat negara ditegaskan di akhir.
Carl Manaster
Dan maksud saya adalah, apa pun yang Anda masukkan di bagian Assert-Not, sudah tersirat di bagian Atur, karena kode di bagian Atur sudah benar-benar diuji dan Anda sudah tahu apa fungsinya.
Marcel Valdez Orozco
Tapi saya percaya ada nilai di bagian Assert-Not, karena Anda mengatakan: Mengingat bagian Atur meninggalkan 'dunia' di 'negara bagian' maka 'Tindakan' saya akan meninggalkan 'dunia' di 'negara baru' ini ; dan jika implementasi kode bagian Atur bergantung pada, berubah, maka pengujian juga akan rusak. Tetapi sekali lagi, itu mungkin bertentangan dengan KERING, karena Anda (harus) juga memiliki tes untuk kode apa pun yang Anda bergantung pada bagian Atur.
Marcel Valdez Orozco
Mungkin dalam proyek di mana ada beberapa tim (atau tim besar) yang mengerjakan proyek yang sama, klausul seperti itu akan sangat berguna, jika tidak, saya merasa tidak perlu dan berlebihan.
Marcel Valdez Orozco
Mungkin klausa seperti itu akan lebih baik dalam tes Integrasi, Tes Sistem atau Tes Penerimaan, di mana bagian Atur biasanya bergantung pada lebih dari satu komponen, dan ada lebih banyak faktor yang dapat menyebabkan keadaan awal 'dunia' berubah secara tidak terduga. Tapi saya tidak melihat tempatnya di tes Unit.
Marcel Valdez Orozco
0

Jika Anda benar-benar ingin menguji semuanya dalam contoh, coba tes lainnya ... seperti:

public void testIncludes7() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
}

public void testIncludes5() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(5));
}

public void testIncludes0() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(0));
}

public void testEncompassInc7() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(7));
}

public void testEncompassInc5() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(5));
}

public void testEncompassInc0() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(0));
}

Karena jika tidak, Anda kehilangan begitu banyak kemungkinan kesalahan ... misalnya setelah mencakup, kisaran hanya mencakup 7, dll ... Ada juga tes untuk jarak jangkauan (untuk memastikan itu tidak juga mencakup nilai acak), dan serangkaian pengujian lainnya sepenuhnya untuk mencoba mencakup 5 dalam rentang ... apa yang akan kami harapkan - pengecualian dalam cakupan, atau rentang tidak diubah?

Pokoknya, intinya adalah jika ada asumsi dalam tindakan yang ingin Anda uji, uji sendiri, ya?

Andrew
sumber
0

Saya menggunakan:

1. Setup
2. Act
3. Assert 
4. Teardown

Karena penyiapan yang bersih sangat penting.

kame
sumber