Haruskah Anda membuat kode data Anda di semua tes unit?


33

Kebanyakan tutorial / contoh pengujian unit di luar sana biasanya melibatkan pendefinisian data yang akan diuji untuk setiap tes individu. Saya kira ini adalah bagian dari teori "semuanya harus diuji dalam isolasi".

Namun saya telah menemukan bahwa ketika berhadapan dengan aplikasi multitier dengan banyak DI , kode yang diperlukan untuk mengatur setiap tes menjadi sangat panjang lebar. Sebaliknya saya telah membangun sejumlah kelas testbase yang sekarang dapat saya warisi yang memiliki banyak perancah tes pra-dibangun.

Sebagai bagian dari ini, saya juga sedang membangun dataset palsu yang mewakili database aplikasi yang sedang berjalan, meskipun biasanya hanya satu atau dua baris di setiap "tabel".

Apakah ini praktik yang diterima untuk menentukan sebelumnya, jika tidak semua, maka sebagian besar data uji di semua tes unit?

Memperbarui

Dari komentar di bawah ini rasanya saya melakukan lebih banyak integrasi daripada pengujian unit.

Proyek saya saat ini adalah ASP.NET MVC, menggunakan Unit Kerangka Kerja atas Entity Code, dan Moq untuk pengujian. Saya telah mengejek UoW, dan repositori, tapi saya menggunakan kelas logika bisnis nyata, dan menguji tindakan pengontrol. Tes akan sering memeriksa bahwa UoW telah dilakukan, misalnya:

[TestClass]
public class SetupControllerTests : SetupControllerTestBase {
  [TestMethod]
  public void UserInvite_ExistingUser_DoesntInsertNewUser() {
    // Arrange
    var model = new Mandy.App.Models.Setup.UserInvite() {
      Email = userData.First().Email
    };

    // Act
    setupController.UserInvite(model);

    // Assert
    mockUserSet.Verify(m => m.Add(It.IsAny<UserProfile>()), Times.Never);
    mockUnitOfWork.Verify(m => m.Commit(), Times.Once);
  }
}

SetupControllerTestBaseadalah membangun UoW tiruan, dan instantiating userLogic.

Banyak tes yang mengharuskan memiliki pengguna atau produk yang sudah ada dalam database, jadi saya sudah mempopulasikan apa yang dikembalikan UoW tiruan, dalam contoh ini userData, yang hanya merupakan IList<User>dengan satu catatan pengguna tunggal.


4
Masalah dengan tutorial / contoh adalah mereka harus sederhana, tetapi Anda tidak dapat menunjukkan solusi untuk masalah kompleks pada contoh sederhana. Mereka harus disertai dengan "studi kasus" yang menggambarkan bagaimana alat ini digunakan dalam proyek nyata dengan ukuran yang masuk akal, tetapi jarang.
Jan Hudec

Mungkin Anda bisa menambahkan beberapa contoh kecil kode yang Anda miliki yang tidak Anda sukai sepenuhnya.
Luc Franken

Jika Anda memerlukan banyak kode pengaturan untuk menjalankan tes, Anda berisiko menjalankan tes fungsional. Jika tes gagal ketika Anda mengubah kode tetapi tidak ada yang salah dengan kode tersebut. Ini jelas merupakan tes fungsional.
Reactgular

Buku "xUnit Test Patterns" membuat kasus yang kuat untuk perlengkapan dan bantuan yang dapat digunakan kembali. Kode tes harus sama terpelihara dengan kode lainnya.
Chuck Krutsinger

Jawaban:


25

Pada akhirnya, Anda ingin menulis kode sesedikit mungkin untuk mendapatkan hasil sebanyak mungkin. Memiliki banyak kode yang sama dalam beberapa pengujian a) cenderung menghasilkan pengkodean salin-tempel dan b) berarti bahwa jika suatu metode tanda tangan berubah, Anda akhirnya harus memperbaiki banyak tes yang rusak.

Saya menggunakan pendekatan memiliki kelas TestHelper standar yang memberi saya banyak tipe data yang saya gunakan secara rutin, jadi saya bisa membuat set entitas standar atau kelas DTO untuk pengujian saya untuk kueri dan tahu persis apa yang akan saya dapatkan setiap kali. Jadi saya bisa menelepon TestHelper.GetFooRange( 0, 100 )untuk mendapatkan kisaran 100 objek Foo dengan semua kelas / bidang dependensinya diatur.

Khususnya di mana ada hubungan kompleks yang dikonfigurasi dalam sistem tipe ORM yang perlu hadir untuk hal-hal agar berjalan dengan benar, tetapi tidak berarti signifikan untuk pengujian ini yang dapat menghemat banyak waktu.

Dalam situasi di mana saya menguji dekat dengan tingkat data, saya kadang-kadang membuat versi uji dari kelas repositori saya yang dapat ditanyakan dengan cara yang sama (sekali lagi ini dalam lingkungan tipe ORM, dan itu tidak akan relevan terhadap suatu database nyata), karena mengejek jawaban yang tepat untuk pertanyaan banyak pekerjaan dan seringkali hanya memberikan manfaat kecil.

Ada beberapa hal yang harus diperhatikan, meskipun dalam unit test:

  • Pastikan tiruan Anda adalah tiruan . Kelas-kelas yang melakukan operasi di sekitar kelas yang diuji harus objek tiruan jika Anda melakukan pengujian unit. Kelas DTO / jenis entitas Anda bisa menjadi hal yang nyata, tetapi jika kelas melakukan operasi, Anda harus mengejeknya - jika tidak, kode pendukung berubah dan tes Anda mulai gagal, Anda harus mencari lebih lama untuk mengetahui perubahan mana yang sebenarnya menyebabkan masalah.
  • Pastikan Anda menguji kelas Anda . Kadang-kadang jika seseorang melihat melalui serangkaian tes unit, menjadi jelas bahwa setengah dari tes sebenarnya menguji kerangka mengejek lebih dari kode aktual yang seharusnya mereka uji.
  • Jangan menggunakan kembali benda tiruan / pendukung Ini adalah masalah besar - ketika seseorang mulai mencoba menjadi pintar dengan tes unit pendukung kode, sangat mudah untuk secara tidak sengaja membuat objek yang bertahan di antara pengujian, yang dapat memiliki efek yang tidak terduga. Sebagai contoh, kemarin saya memiliki tes yang lulus ketika dijalankan sendiri, lulus ketika semua tes di kelas dijalankan, tetapi gagal ketika seluruh rangkaian tes dijalankan. Ternyata ada objek statis yang licik dalam tes pembantu bahwa, ketika saya membuatnya, pasti tidak akan pernah menyebabkan masalah. Ingat saja: Pada awal tes, semuanya dibuat, pada akhir tes semuanya hancur.

10

Apa pun yang membuat niat ujian Anda lebih mudah dibaca.

Sebagai aturan umum:

Jika data adalah bagian dari tes (mis. Tidak boleh mencetak baris dengan status 7) maka kode dalam tes, sehingga jelas apa yang dimaksudkan penulis terjadi.

Jika data hanya pengisi untuk memastikan ada sesuatu untuk dikerjakan (mis. Tidak boleh menandai catatan sebagai lengkap jika layanan pemrosesan melempar pengecualian) maka tentu saja memiliki metode BuildDummyData atau kelas uji yang menjaga data yang tidak relevan keluar dari tes .

Tetapi perhatikan bahwa saya berjuang untuk memikirkan contoh yang baik dari yang terakhir. Jika Anda memiliki banyak dari ini dalam fixture unit-test, Anda mungkin memiliki masalah yang berbeda untuk dipecahkan ... mungkin metode yang diuji terlalu kompleks.


+1 Saya setuju. Ini berbau seperti apa yang dia uji adalah untuk digabungkan dengan ketat untuk pengujian unit.
Reactgular

5

Berbagai metode pengujian

Pertama-tama tentukan apa yang Anda lakukan: Pengujian unit atau pengujian integrasi . Jumlah lapisan tidak relevan untuk pengujian unit karena Anda hanya menguji satu kelas yang paling mungkin. Sisanya yang kau tiru. Untuk pengujian integrasi, Anda dapat menguji beberapa lapisan. Jika Anda memiliki tes unit yang baik, triknya adalah membuat tes integrasi tidak terlalu rumit.

Jika pengujian unit Anda baik, Anda tidak perlu mengulangi pengujian semua detail saat melakukan pengujian integrasi.

Istilah yang kami gunakan, itu sedikit tergantung platform, tetapi Anda dapat menemukannya di hampir semua platform pengujian / pengembangan:

Contoh aplikasi

Tergantung pada teknologi yang Anda gunakan, nama mungkin berbeda, tetapi saya akan menggunakan ini sebagai contoh:

Jika Anda memiliki aplikasi CRUD sederhana dengan Model produk, ProductsController, dan tampilan indeks yang menghasilkan tabel HTML dengan produk:

Hasil akhir dari aplikasi ini memperlihatkan tabel HTML dengan daftar semua produk yang aktif.

Pengujian unit

Model

Model yang dapat Anda uji cukup mudah. Ada berbagai metode untuk itu; kami menggunakan perlengkapan. Saya pikir itu yang Anda sebut "set data palsu". Jadi sebelum setiap tes dijalankan, kita membuat tabel, dan memasukkan data asli. Sebagian besar platform memiliki metode untuk ini. Misalnya, di kelas pengujian Anda, metode setUp () yang dijalankan sebelum setiap tes.

Kemudian kami menjalankan pengujian kami, misalnya: produk testGetAllActive .

Jadi kami menguji langsung ke database uji. Kami tidak mencemooh sumber data; kami membuatnya selalu sama. Sebagai contoh, ini memungkinkan kami untuk menguji dengan versi baru dari database, dan masalah kueri akan muncul.

Di dunia nyata Anda tidak dapat selalu mengikuti 100% tanggung jawab tunggal . Jika Anda ingin melakukan ini lebih baik, Anda bisa menggunakan sumber data yang Anda tiru. Bagi kami (kami menggunakan ORM) yang rasanya seperti menguji teknologi yang sudah ada. Juga tes menjadi jauh lebih kompleks, dan mereka tidak benar-benar menguji kueri. Jadi kita tetap seperti ini.

Data kode keras disimpan secara terpisah dalam perlengkapan. Jadi fixture-nya seperti file SQL dengan tabel buat pernyataan dan sisipan untuk catatan yang kita gunakan. Kami menjaga mereka tetap kecil kecuali ada kebutuhan nyata untuk menguji dengan banyak catatan.

class ProductModel {
  public function getAllActive() {
    return $this->find('all', array('conditions' => array('active' => 1)));
  }
}

Pengendali

Pengontrol membutuhkan lebih banyak pekerjaan, karena kami tidak ingin menguji model dengan itu. Jadi yang kita lakukan adalah mengejek model. Itu berarti: Kami menguji: index () metode yang harus mengembalikan daftar catatan.

Jadi kita mengejek metode model getAllActive () keluar dan menambahkan data tetap di dalamnya (dua catatan misalnya). Sekarang kami menguji data yang dikirim oleh pengontrol ke tampilan, dan kami membandingkan jika kami benar-benar mendapatkan dua catatan itu kembali.

function testProductIndexLoggedIn() {
  $this->setLoggedIn();
  $this->ProductsController->mock('ProductModel', 'index', function(return array(your records) ));
  $result=$this->ProductsController->index();
  $this->assertEquals(2, count($result['products']));
}

Cukup. Kami mencoba menambahkan fungsionalitas sebagai sedikit ke controller karena itu membuat pengujian sulit. Tapi tentu saja selalu ada beberapa kode di dalamnya. Misalnya, kami menguji persyaratan seperti: Tampilkan dua catatan itu hanya jika Anda masuk.

Jadi, pengontrol membutuhkan satu tiruan secara normal dan sepotong kecil data yang dikodekan. Untuk sistem login mungkin yang lain. Dalam pengujian kami, kami memiliki metode pembantu untuk itu: setLoggedIn (). Itu membuatnya mudah untuk menguji dengan login atau tanpa login.

class ProductsController {
  public function index() {
    if($this->loggedIn()) {
      $this->set('products', $this->ProductModel->getAllActive());
    }
  }
}

Tampilan

Pengujian tampilan sulit. Pertama kita memisahkan logika yang berulang. Kami memasukkannya ke dalam Pembantu dan menguji kelas-kelas itu dengan ketat. Kami mengharapkan output yang sama selalu. Misalnya, generateHtmlTableFromArray ().

Kemudian kami memiliki beberapa pandangan spesifik proyek. Kami tidak menguji itu. Tidak benar-benar diinginkan untuk menguji unit tersebut. Kami menyimpannya untuk pengujian integrasi. Karena kami mengambil banyak kode untuk dilihat, kami memiliki risiko yang lebih rendah di sini.

Jika Anda mulai menguji yang kemungkinan Anda perlu mengubah tes Anda setiap kali Anda mengubah sepotong HTML yang tidak berguna untuk sebagian besar proyek.

echo $this->tableHelper->generateHtmlTableFromArray($products);

Tes integrasi

Bergantung pada platform Anda di sini, Anda dapat bekerja dengan cerita pengguna, dll. Ini bisa berbasis web seperti Selenium atau solusi serupa lainnya.

Secara umum kami hanya memuat basis data dengan perlengkapan dan menegaskan data mana yang harus tersedia. Untuk pengujian integrasi penuh, kami biasanya menggunakan persyaratan yang sangat global. Jadi: Atur produk ke aktif lalu periksa apakah produk tersedia.

Kami tidak menguji semuanya lagi, seperti apakah bidang yang tepat tersedia. Kami menguji persyaratan yang lebih besar di sini. Karena kami tidak ingin menduplikasi pengujian kami dari pengontrol atau tampilan. Jika ada sesuatu yang benar-benar kunci / inti dari aplikasi Anda atau untuk alasan keamanan (periksa kata sandi TIDAK tersedia) maka kami menambahkannya untuk memastikan itu benar.

Data kode keras disimpan di dalam fixture.

function testIntegrationProductIndexLoggedIn() {
  $this->setLoggedIn();
  $result=$this->request('products/index');

  $expected='<table';
  $this->assertContains($expected, $result);

  // Some content from the fixture record
  $expected='<td>Product 1 name</td>';
  $this->assertContains($expected, $result);
}

Ini adalah jawaban yang bagus, untuk pertanyaan yang sama sekali berbeda.
pdr

Terima kasih untuk umpan baliknya. Anda mungkin benar bahwa saya tidak menyebutkannya terlalu spesifik. Alasan jawaban verbal adalah karena saya melihat salah satu hal yang paling sulit ketika menguji dalam pertanyaan yang diajukan. Tinjauan tentang bagaimana pengujian dalam isolasi cocok dengan berbagai jenis pengujian. Itu sebabnya saya menambahkan di setiap bagian bagaimana data ditangani (atau dipisahkan). Akan melihat apakah saya bisa membuatnya lebih jelas.
Luc Franken

Jawabannya telah diperbarui dengan beberapa contoh kode untuk menjelaskan cara menguji tanpa memanggil semua jenis kelas lainnya.
Luc Franken

4

Jika Anda menulis tes yang melibatkan banyak DI dan perkabelan, hingga menggunakan sumber data "nyata", Anda mungkin meninggalkan area pengujian unit biasa dan memasuki domain pengujian integrasi.

Untuk tes integrasi, saya pikir, bukan ide yang buruk untuk memiliki logika pengaturan data yang umum. Tujuan utama dari tes tersebut adalah untuk membuktikan bahwa semuanya sudah terkonfigurasi dengan benar. Ini agak independen dari data konkret yang dikirim melalui sistem Anda.

Di sisi lain, untuk tes unit, saya akan merekomendasikan agar target kelas tes menjadi kelas "nyata" dan mengolok-olok semua yang lain. Maka Anda harus benar-benar meng-kode data uji untuk memastikan Anda menutupi sebanyak mungkin jalur khusus / bug sebelumnya.

Untuk menambahkan elemen semi-hard-kode / acak ke dalam tes, saya ingin memperkenalkan pabrik model acak. Dalam tes menggunakan instance dari model saya, saya kemudian menggunakan pabrik-pabrik ini untuk membuat objek model yang valid, tetapi benar-benar acak dan kemudian hard-code hanya properti yang menarik untuk pengujian yang ada. Dengan cara ini Anda menentukan semua data yang relevan secara langsung dalam pengujian Anda, sambil menghemat Anda juga perlu menentukan semua data yang tidak relevan dan (sampai tingkat tertentu) menguji bahwa tidak ada dependensi yang tidak diinginkan pada bidang model lain.


-1

Saya pikir itu cukup umum untuk kode hard sebagian besar data untuk tes Anda.

Pertimbangkan situasi sederhana di mana kumpulan data tertentu menyebabkan bug terjadi. Anda mungkin secara khusus membuat unit test untuk data tersebut untuk melakukan perbaikan dan memastikan bahwa bug tidak kembali. Seiring waktu tes Anda akan memiliki satu set data yang mencakup sejumlah kasus uji.

Data uji yang ditentukan sebelumnya juga memungkinkan Anda untuk membangun satu set data yang mencakup berbagai situasi yang luas dan diketahui.

Yang mengatakan, saya pikir ada nilai juga dalam memiliki beberapa data acak dalam tes Anda.


Apakah Anda benar-benar membaca pertanyaan dan bukan hanya judulnya?
Jakob

nilai dalam memiliki beberapa data acak dalam tes Anda - Ya, karena tidak ada yang seperti mencoba untuk mencari tahu apa yang terjadi dalam tes satu kali gagal setiap minggu.
pdr

Ada nilai dalam memiliki data acak dalam tes Anda untuk tes hazing / fuzzing / input. Tetapi tidak dalam tes unit Anda, itu akan menjadi mimpi buruk.
glenatron
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.