Apa tujuan dari benda tiruan?

167

Saya baru dalam pengujian unit, dan saya terus menerus mendengar kata-kata 'benda tiruan' yang sering dilontarkan. Dalam istilah awam, dapatkah seseorang menjelaskan benda tiruan apa, dan apa yang biasanya mereka gunakan saat menulis tes unit?

agentbanks217
sumber
12
Mereka adalah alat untuk overengineering hal-hal besar dengan fleksibilitas yang tidak Anda butuhkan untuk masalah yang dihadapi.
dsimcha
2
kemungkinan duplikat dari What is Mocking?
nawfal

Jawaban:

361

Karena Anda mengatakan Anda baru dalam pengujian unit dan meminta objek tiruan dalam "istilah awam", saya akan mencoba contoh awam.

Pengujian Unit

Bayangkan pengujian unit untuk sistem ini:

cook <- waiter <- customer

Secara umum mudah membayangkan pengujian komponen tingkat rendah seperti cook:

cook <- test driver

Penguji hanya memesan hidangan yang berbeda dan memverifikasi juru masak mengembalikan hidangan yang benar untuk setiap pesanan.

Lebih sulit untuk menguji komponen tengah, seperti pelayan, yang memanfaatkan perilaku komponen lain. Penguji naif mungkin menguji komponen pelayan dengan cara yang sama seperti kami menguji komponen juru masak:

cook <- waiter <- test driver

Penguji akan memesan hidangan yang berbeda dan memastikan pelayan mengembalikan hidangan yang benar. Sayangnya, itu berarti bahwa pengujian komponen pelayan ini mungkin tergantung pada perilaku komponen koki yang benar. Ketergantungan ini bahkan lebih buruk jika komponen koki memiliki karakteristik uji-tidak ramah, seperti perilaku non-deterministik (menu termasuk kejutan koki sebagai hidangan), banyak ketergantungan (koki tidak akan memasak tanpa seluruh stafnya), atau banyak sumber daya (beberapa hidangan membutuhkan bahan-bahan mahal atau membutuhkan waktu satu jam untuk memasak).

Karena ini adalah tes pelayan, idealnya, kami ingin menguji hanya pelayan, bukan koki. Secara khusus, kami ingin memastikan pelayan menyampaikan pesanan pelanggan kepada juru masak dengan benar dan mengirimkan makanan juru masak kepada pelanggan dengan benar.

Pengujian unit berarti menguji unit secara independen, jadi pendekatan yang lebih baik adalah mengisolasi komponen yang diuji (pelayan) menggunakan apa yang disebut Fowler sebagai uji ganda (boneka, bertopik, palsu, pura-pura) .

    -----------------------
   |                       |
   v                       |
test cook <- waiter <- test driver

Di sini, test cook adalah "bersekongkol" dengan driver tes. Idealnya, sistem yang diuji dirancang sehingga juru masak uji dapat dengan mudah diganti ( disuntikkan ) untuk bekerja dengan pelayan tanpa mengubah kode produksi (misalnya tanpa mengubah kode pelayan).

Objek Mock

Sekarang, juru masak uji (tes ganda) dapat diimplementasikan dengan berbagai cara:

  • juru masak palsu - seseorang yang berpura-pura menjadi juru masak dengan menggunakan makan malam beku dan microwave,
  • koki rintisan - penjual hot dog yang selalu memberi Anda hot dog, apa pun yang Anda pesan, atau
  • seorang juru masak tiruan - seorang polisi yang menyamar mengikuti naskah yang berpura-pura menjadi koki dalam operasi sengatan.

Lihat artikel Fowler untuk lebih spesifik tentang palsu vs bertopik vs tiruan vs boneka , tetapi untuk sekarang, mari kita fokus pada juru masak tiruan.

    -----------------------
   |                       |
   v                       |
mock cook <- waiter <- test driver

Sebagian besar unit menguji komponen pelayan berfokus pada bagaimana pelayan berinteraksi dengan komponen koki. Pendekatan berbasis tiruan berfokus pada menentukan secara lengkap apa interaksi yang benar dan mendeteksi ketika berjalan serba salah.

Objek tiruan mengetahui terlebih dahulu apa yang seharusnya terjadi selama pengujian (mis. Panggilan metodenya mana yang akan dipanggil, dll.) Dan objek tiruan tahu bagaimana seharusnya bereaksi (misalnya apa nilai pengembalian yang diberikan). Mock akan menunjukkan apakah apa yang sebenarnya terjadi berbeda dari apa yang seharusnya terjadi. Objek tiruan kustom dapat dibuat dari awal untuk setiap kasus uji untuk mengeksekusi perilaku yang diharapkan untuk kasus uji tersebut, tetapi kerangka kerja mengejek berusaha untuk memungkinkan spesifikasi perilaku seperti itu secara jelas dan mudah ditunjukkan secara langsung dalam kasus uji.

Percakapan seputar tes berbasis mock mungkin terlihat seperti ini:

driver tes untuk mengejek juru masak : mengharapkan pesanan hot dog dan memberinya hot dog dummy ini sebagai tanggapan

driver tes (berpose sebagai pelanggan) kepada pelayan : Saya ingin hot dog, silakan
pelayan untuk mengejek juru masak : 1 hot dog, silakan
mengejek koki untuk pelayan : pesan: 1 hot dog siap (memberikan hot dog dummy untuk pelayan)
pelayan untuk menguji pengemudi : inilah hot dog Anda (memberikan hot dog dummy untuk menguji pengemudi)

test driver : UJI BERHASIL!

Tetapi karena pelayan kami baru, inilah yang bisa terjadi:

driver tes untuk mengejek juru masak : mengharapkan pesanan hot dog dan memberinya hot dog dummy ini sebagai tanggapan

pengemudi tes (berpose sebagai pelanggan) kepada pelayan : Saya ingin hot dog, silakan
pelayan untuk mengejek juru masak : 1 hamburger tolong
tiruan juru masak menghentikan tes: Saya diberitahu untuk mengharapkan pesanan hot dog!

pengemudi ujian mencatat masalahnya: UJI GAGAL! - pelayan mengubah urutan

atau

driver tes untuk mengejek juru masak : mengharapkan pesanan hot dog dan memberinya hot dog dummy ini sebagai tanggapan

driver tes (berpose sebagai pelanggan) kepada pelayan : Saya ingin hot dog, silakan
pelayan untuk mengejek juru masak : 1 hot dog, silakan
mengejek koki untuk pelayan : pesan: 1 hot dog siap (memberikan hot dog dummy untuk pelayan)
pelayan untuk menguji pengemudi : di sini adalah kentang goreng Anda (memberikan kentang goreng dari beberapa pesanan lain untuk menguji driver)

test driver mencatat kentang goreng yang tak terduga: UJI GAGAL! pelayan mengembalikan hidangan yang salah

Mungkin sulit untuk secara jelas melihat perbedaan antara benda tiruan dan bertopik tanpa contoh berbasis rintisan yang kontras untuk pergi dengan ini, tetapi jawaban ini sudah terlalu lama :-)

Juga perhatikan bahwa ini adalah contoh yang cukup sederhana dan bahwa kerangka kerja mengejek memungkinkan untuk beberapa spesifikasi yang cukup canggih dari perilaku yang diharapkan dari komponen untuk mendukung tes komprehensif. Ada banyak materi tentang objek tiruan dan kerangka kerja mengejek untuk informasi lebih lanjut.

Bert F
sumber
12
Ini adalah penjelasan yang bagus, tetapi bukankah Anda sedikit banyak menguji implementasi pelayan? Dalam kasus Anda, mungkin baik-baik saja karena Anda memeriksa bahwa ia menggunakan API yang benar, tetapi bagaimana jika ada cara berbeda untuk melakukannya, dan pelayan mungkin memilih satu atau yang lain? Saya pikir titik pengujian unit adalah untuk menguji API, dan bukan implementasinya. (Ini adalah pertanyaan yang selalu saya tanyakan ketika saya membaca tentang mengejek.)
davidtbernal
8
Terima kasih. Saya tidak bisa mengatakan apakah kami sedang menguji "implementasi" tanpa melihat (atau mendefinisikan) spesifikasi pelayan. Anda mungkin berasumsi bahwa pelayan diperbolehkan memasak sendiri atau mengisi pesanan, tetapi saya berasumsi bahwa spesifikasi pelayan termasuk menggunakan koki yang dituju - lagipula, koki produksi adalah koki gourmet yang mahal, dan kami d lebih suka pelayan kami menggunakannya. Tanpa spek itu, saya kira saya harus menyimpulkan Anda benar - pelayan dapat mengisi pesanan namun ingin agar "benar". OTOH, tanpa spek, tes tidak ada artinya. [Lanjutan ...]
Bert F
8
TIDAK PERNAH, Anda membuat poin bagus yang mengarah ke topik fantastis pengujian kotak putih vs kotak hitam. Saya tidak berpikir ada konsensus industri yang mengatakan pengujian unit harus kotak hitam, bukan kotak putih ("uji API, bukan implementasinya"). Saya pikir bahwa pengujian unit terbaik mungkin perlu kombinasi keduanya untuk menyeimbangkan kerapuhan pengujian terhadap cakupan kode dan kelengkapan kasus uji.
Bert F
1
Jawaban ini menurut saya tidak cukup teknis. Saya ingin tahu mengapa saya harus menggunakan objek tiruan ketika saya dapat menggunakan objek nyata.
Niklas R.
1
Penjelasan hebat !! Terima kasih!! @BertF
Bharath Murali
28

Objek Mock adalah objek yang menggantikan objek nyata. Dalam pemrograman berorientasi objek, objek tiruan adalah objek simulasi yang meniru perilaku objek nyata dengan cara yang terkontrol.

Seorang programmer komputer biasanya membuat objek tiruan untuk menguji perilaku beberapa objek lain, dengan cara yang sama seperti seorang perancang mobil menggunakan tiruan uji tabrakan untuk mensimulasikan perilaku dinamis manusia dalam dampak kendaraan.

http://en.wikipedia.org/wiki/Mock_object

Objek tiruan memungkinkan Anda mengatur skenario pengujian tanpa membawa sumber daya besar dan sulit seperti database. Alih-alih memanggil basis data untuk pengujian, Anda dapat mensimulasikan basis data Anda menggunakan objek tiruan dalam pengujian unit Anda. Ini membebaskan Anda dari beban harus mengatur dan meruntuhkan database nyata, hanya untuk menguji satu metode di kelas Anda.

Kata "Mock" kadang-kadang keliru digunakan secara bergantian dengan "Stub." Perbedaan antara kedua kata tersebut dijelaskan di sini. Pada dasarnya, tiruan adalah objek rintisan yang juga mencakup harapan (yaitu "pernyataan") untuk perilaku yang tepat dari objek / metode yang diuji.

Sebagai contoh:

class OrderInteractionTester...
  public void testOrderSendsMailIfUnfilled() {
    Order order = new Order(TALISKER, 51);
    Mock warehouse = mock(Warehouse.class);
    Mock mailer = mock(MailService.class);
    order.setMailer((MailService) mailer.proxy());

    mailer.expects(once()).method("send");
    warehouse.expects(once()).method("hasInventory")
      .withAnyArguments()
      .will(returnValue(false));

    order.fill((Warehouse) warehouse.proxy());
  }
}

Perhatikan bahwa objek warehousedan mailertiruan diprogram dengan hasil yang diharapkan.

Robert Harvey
sumber
2
Definisi yang Anda berikan tidak sedikit berbeda dari "objek rintisan," dan karena itu tidak menjelaskan apa objek tiruan itu.
Brent Arias
Koreksi lain "kata 'Mock' kadang - kadang keliru digunakan secara bergantian dengan 'rintisan'".
Brent Arias
@Myst: Penggunaan dua kata ini tidak universal; bervariasi di antara penulis. Fowler mengatakan demikian, dan artikel Wikipedia mengatakan demikian. Namun, silakan edit dalam perubahan dan hapus downvote Anda. :)
Robert Harvey
1
Saya setuju dengan Robert: penggunaan kata "mock" cenderung bervariasi di seluruh industri, tetapi tidak ada definisi yang ditetapkan menurut pengalaman saya kecuali bahwa biasanya BUKAN objek yang diuji, melainkan ada untuk memfasilitasi pengujian di mana menggunakan yang sebenarnya objek atau semua bagian itu akan sangat merepotkan dan konsekuensi kecil.
mkelley33
15

Benda tiruan adalah benda tiruan yang meniru perilaku benda nyata. Biasanya Anda menulis objek tiruan jika:

  • Objek nyata terlalu kompleks untuk dimasukkan ke dalam pengujian unit (Misalnya komunikasi jaringan, Anda dapat memiliki objek tiruan yang disimulasikan oleh rekan lainnya)
  • Hasil dari objek Anda tidak deterministik
  • Objek nyata belum tersedia
Dani Cricco
sumber
12

Objek Mock adalah salah satu jenis Tes Ganda . Anda menggunakan mockobjects untuk menguji dan memverifikasi protokol / interaksi kelas yang diuji dengan kelas lain.

Biasanya Anda akan jenis 'program' atau 'catatan' harapan: metode panggilan yang Anda harapkan kelas Anda lakukan ke objek yang mendasarinya.

Katakanlah misalnya kita sedang menguji metode layanan untuk memperbarui bidang dalam Widget. Dan itu dalam arsitektur Anda ada WidgetDAO yang berkaitan dengan database. Berbicara dengan database lambat dan pengaturannya dan pembersihan setelah itu rumit, jadi kami akan mengejek WidgetDao.

mari kita pikirkan apa yang harus dilakukan oleh layanan: itu harus mendapatkan Widget dari database, melakukan sesuatu dengannya dan menyimpannya lagi.

Jadi dalam bahasa pseudo dengan pseudo-mock library kita akan memiliki sesuatu seperti:

Widget sampleWidget = new Widget();
WidgetDao mock = createMock(WidgetDao.class);
WidgetService svc = new WidgetService(mock);

// record expected calls on the dao
expect(mock.getById(id)).andReturn(sampleWidget);   
expect(mock.save(sampleWidget);

// turn the dao in replay mode
replay(mock);

svc.updateWidgetPrice(id,newPrice);

verify(mock);    // verify the expected calls were made
assertEquals(newPrice,sampleWidget.getPrice());

Dengan cara ini kita dapat dengan mudah menguji pengembangan drive dari kelas yang bergantung pada kelas lain.

Peter Tillemans
sumber
11

Saya sangat merekomendasikan artikel hebat dari Martin Fowler yang menjelaskan apa sebenarnya ejekan itu dan bagaimana perbedaannya dengan bertopik.

Adam Byrtek
sumber
10
Bukan ramah pemula, bukan?
Robert Harvey
@Robert Harvey: Mungkin, bagaimanapun juga baik untuk melihat bahwa itu membantu dalam mengklarifikasi jawaban Anda :)
Adam Byrtek
Artikel Martin Fowler ditulis dengan cara RFC: kering dan dingin.
revo
9

Saat unit menguji beberapa bagian dari program komputer, Anda idealnya hanya ingin menguji perilaku bagian tertentu itu.

Sebagai contoh, lihat pseudo-code di bawah ini dari bagian imajiner dari program yang menggunakan program lain untuk memanggil print sesuatu:

If theUserIsFred then
    Call Printer(HelloFred)
Else
   Call Printer(YouAreNotFred)
End

Jika Anda menguji ini, Anda terutama ingin menguji bagian yang melihat apakah pengguna adalah Fred atau tidak. Anda tidak benar-benar ingin menguji Printerbagian dari semuanya. Itu akan menjadi ujian lain.

Ini adalah di mana benda Mock datang. Mereka berpura-pura menjadi jenis lain hal. Dalam hal ini Anda akan menggunakan Mock Printersehingga akan bertindak seperti printer sungguhan, tetapi tidak akan melakukan hal-hal yang tidak nyaman seperti mencetak.


Ada beberapa jenis objek pura-pura yang bisa Anda gunakan yang bukan Mock. Hal utama yang membuat Mocks Mocks adalah bahwa mereka dapat dikonfigurasi dengan perilaku dan harapan.

Ekspektasi memungkinkan Mock Anda meningkatkan kesalahan saat digunakan secara tidak benar. Jadi, dalam contoh di atas, Anda dapat memastikan bahwa Printer dipanggil dengan HelloFred di dalam case test "user is Fred". Jika itu tidak terjadi, Mock Anda dapat memperingatkan Anda.

Perilaku di Mocks berarti bahwa, misalnya, kode Anda melakukan sesuatu seperti:

If Call Printer(HelloFred) Returned SaidHello Then
    Do Something
End

Sekarang Anda ingin menguji apa yang kode Anda lakukan ketika Printer dipanggil dan mengembalikan SaidHello, sehingga Anda dapat mengatur Mock untuk mengembalikan SaidHello ketika dipanggil dengan HelloFred.

Salah satu sumber yang bagus untuk hal ini adalah Martin Fowlers memposting Mocks Aron't Stubs

David Hall
sumber
7

Objek tiruan dan rintisan adalah bagian penting dari pengujian unit. Bahkan mereka jauh untuk memastikan Anda menguji unit , bukan kelompok unit.

Singkatnya, Anda menggunakan bertopik untuk mematahkan ketergantungan (System Under Test) SUT pada objek dan tiruan lain untuk melakukan itu dan memverifikasi bahwa SUT disebut metode / properti tertentu pada dependensi. Ini kembali ke prinsip dasar pengujian unit - bahwa pengujian harus mudah dibaca, cepat dan tidak memerlukan konfigurasi, yang mungkin menyiratkan semua kelas nyata.

Secara umum, Anda dapat memiliki lebih dari satu rintisan dalam ujian Anda, tetapi Anda seharusnya hanya memiliki satu tiruan. Ini karena tujuan mock adalah untuk memverifikasi perilaku dan pengujian Anda seharusnya hanya menguji satu hal.

Skenario sederhana menggunakan C # dan Moq:

public interface IInput {
  object Read();
}
public interface IOutput {
  void Write(object data);
}

class SUT {
  IInput input;
  IOutput output;

  public SUT (IInput input, IOutput output) {
    this.input = input;
    this.output = output;
  }

  void ReadAndWrite() { 
    var data = input.Read();
    output.Write(data);
  }
}

[TestMethod]
public void ReadAndWriteShouldWriteSameObjectAsRead() {
  //we want to verify that SUT writes to the output interface
  //input is a stub, since we don't record any expectations
  Mock<IInput> input = new Mock<IInput>();
  //output is a mock, because we want to verify some behavior on it.
  Mock<IOutput> output = new Mock<IOutput>();

  var data = new object();
  input.Setup(i=>i.Read()).Returns(data);

  var sut = new SUT(input.Object, output.Object);
  //calling verify on a mock object makes the object a mock, with respect to method being verified.
  output.Verify(o=>o.Write(data));
}

Dalam contoh di atas saya menggunakan Moq untuk menunjukkan stub dan ejekan. Moq menggunakan kelas yang sama untuk keduanya - Mock<T>yang membuatnya sedikit membingungkan. Apapun, saat runtime, tes akan gagal jika output.Writetidak dipanggil dengan data parameter, sedangkan kegagalan untuk menelepon input.Read()tidak akan gagal itu.

Igor Zevaka
sumber
4

Seperti jawaban lain yang disarankan melalui tautan ke " Mocks Aron't Stubs ", mock adalah bentuk "test double" untuk digunakan sebagai pengganti objek nyata. Apa yang membuatnya berbeda dari bentuk tes ganda lainnya, seperti objek rintisan, adalah bahwa tes ganda lainnya menawarkan verifikasi keadaan (dan simulasi opsional) sedangkan tiruan menawarkan verifikasi perilaku (dan simulasi opsional).

Dengan sebuah rintisan, Anda dapat memanggil beberapa metode pada rintisan dalam urutan apa pun (atau bahkan secara repitious) dan menentukan keberhasilan jika rintisan tersebut telah mengambil nilai atau keadaan yang Anda maksudkan. Sebaliknya, objek tiruan mengharapkan fungsi yang sangat spesifik untuk dipanggil, dalam urutan tertentu, dan bahkan beberapa kali tertentu. Tes dengan objek tiruan akan dianggap "gagal" hanya karena metode dipanggil dalam urutan atau hitungan yang berbeda - bahkan jika objek tiruan memiliki keadaan yang benar ketika tes berakhir!

Dengan cara ini, objek tiruan sering dianggap lebih erat digabungkan ke kode SUT daripada objek rintisan. Itu bisa menjadi hal yang baik atau buruk, tergantung pada apa yang Anda coba verifikasi.

Brent Arias
sumber
3

Bagian dari titik menggunakan objek tiruan adalah bahwa mereka tidak harus benar-benar diimplementasikan sesuai dengan spesifikasi. Mereka hanya bisa memberikan tanggapan bodoh. Misalnya jika Anda harus mengimplementasikan komponen A dan B, dan keduanya "memanggil" (berinteraksi satu sama lain), maka Anda tidak dapat menguji A sampai B diimplementasikan, dan sebaliknya. Dalam pengembangan uji coba, ini adalah masalah. Jadi Anda membuat mock ( "bodoh") obyek untuk A dan B, yang sangat sederhana, tetapi mereka memberikan beberapa jenis respon ketika mereka berinteraksi dengan. Dengan begitu, Anda bisa menerapkan dan menguji A menggunakan objek tiruan untuk B.

Lars
sumber
1

Untuk php dan phpunit dijelaskan dengan baik dalam phpunit documentaion. Lihat disini dokumentasi phpunit

Dalam kata sederhana objek mengejek hanyalah objek tiruan dari objek asli Anda dan mengembalikan nilai kembalinya, nilai pengembalian ini dapat digunakan di kelas uji

Gautam Rai
sumber
0

Ini adalah salah satu perspektif utama dari tes unit. ya, Anda mencoba menguji unit kode tunggal Anda dan hasil pengujian Anda seharusnya tidak relevan dengan perilaku kacang atau objek lainnya. jadi Anda harus mengejeknya dengan menggunakan benda-benda Mock dengan beberapa respons yang disederhanakan.

Mohsen Msr
sumber