EUnit

Unit test dengan EUnit: makro assert, generator, dan fixture.

Daftar isi

Topik ini memperkenalkan EUnit, kerangka unit test ringan bawaan OTP: konvensi fungsi uji, makro asersi dari eunit.hrl, generator untuk menghasilkan kasus secara programatik, dan fixture untuk penyiapan-pembongkaran. Contohnya menguji satu fungsi murni sehingga seluruh mekanismenya terlihat tanpa gangguan.

Konvensi dan makro asersi#

EUnit menemukan uji lewat nama fungsi: fungsi yang diakhiri _test() adalah satu kasus uji, sedangkan fungsi yang diakhiri _test_() adalah generator yang mengembalikan kumpulan kasus. Makro-makro dari eunit.hrl menjadi bahasa asersinya:

erlang kalk_eunit.erl
-module(kalk_eunit).
-include_lib("eunit/include/eunit.hrl").
-export([tambah/2]).

tambah(A, B) -> A + B.

tambah_test_() ->
    [?_assertEqual(5, tambah(2, 3)),
     ?_assert(tambah(0, 0) =:= 0),
     ?_assertEqual(-1, tambah(2, -3))].

Generator tambah_test_/0 mengembalikan list tiga kasus. Makro ?_assertEqual(Harapan, Ekspresi) menyamakan keduanya dan mencetak kedua nilai saat gagal, sedangkan ?_assert(Ekspresi) cukup menuntut kebenaran. Bentuk dengan garis bawah di awal menghasilkan objek uji yang dieksekusi EUnit, bukan pemanggilan langsung, dan inilah yang membuat satu fungsi generator bisa menghasilkan banyak kasus.

Menjalankan dan membaca hasil#

Uji dijalankan dari shell dengan satu fungsi, dan hasilnya dirangkum dalam satu baris:

erlang Menjalankan EUnit dari shell
Erlang/OTP 29 [erts-17.1.1] [source] [64-bit] [smp:2:2] [ds:2:2:10] [async-threads:1] [jit:ns]

Eshell V17.1.1 (press Ctrl+G to abort, type help(). for help)
1> c(kalk_eunit).
{ok,kalk_eunit}
2> eunit:test(kalk_eunit).
  All 3 tests passed.
ok

Pemanggilan eunit:test/1 menerima nama modul dan menjalankan seluruh ujinya, dan kembaliannya tetap ok sebagai tanda kerangka berjalan. Pada proyek rebar3, perintah rebar3 eunit menemukan seluruh modul uji dan menjalankannya sekali jalan, sehingga shell manual di atas hanya untuk eksplorasi. Kasus yang gagal dicetak lengkap dengan nama kasus dan kedua nilai yang diperbandingkan, yang membuat kegagalan terbaca tanpa perlu menebak.

Generator dan fixture#

Kekuatan EUnit berada pada komposisinya. Generator dapat memakai list comprehension untuk menghasilkan kasus dari data, misalnya [?_assertEqual(N * 2, tambah(N, N)) || N <- [1, 2, 3]], dan uji dapat dikelompokkan dengan {title, Kasus} agar laporannya terbaca. Fixture menyatakan penyiapan dan pembongkaran yang berlaku pada sekumpulan uji:

erlang Fixture dengan setup dan cleanup
teardown_test_() ->
    {foreach,
     fun() -> ets:new(uji, [named_table, public]) end,
     fun(_) -> ets:delete(uji) end,
     [?_assert(is_process(whereis(?MODULE)) =:= false)]}.

Bentuk foreach menjalankan fungsi penyiapan sebelum tiap kasus dan pembongkaran sesudahnya, sehingga tiap kasus memperoleh keadaan yang sama dan tidak saling memengaruhi. Fixture lainnya seperti setup dan all menutupi pola satu kali penyiapan, dan semuanya hanya komposisi fungsi, tanpa bahasa tambahan apa pun.

Struktur laporan kegagalan#

Kasus yang gagal dilaporkan dengan kelengkapan yang sama. Modul dengan ekspektasi yang salah memperlihatkan bentuknya:

text Keluaran saat asersi gagal
  {error,{assertEqual_failed,[{module,kalk_gagal},
                              {line,9},
                              {expression,"tambah ( 2 , 3 )"},
                              {expected,6},
                              {value,5}]}}

=======================================================
  Failed: 1.  Skipped: 0.  Passed: 0.
error

Lima bidang itu menjawab semuanya: modul, baris, ekspresi yang diperiksa, nilai yang diharapkan, dan nilai nyata. Rangkuman Failed: 1. Skipped: 0. Passed: 0. di akhir menjadi kebiasaan baca yang sama pada seluruh jalur laporan EUnit, dan nilai kembalian error membuat skrip CI tahu uji gagal tanpa menguraikan teksnya.

Menjalankan sebagian dan mengelompokkan#

eunit:test/1 menerima lebih dari nama modul. Bentuk {modul, [fungsi]} menjalankan sebagian fungsi uji saja untuk siklus perbaikan yang cepat, sedangkan bentuk tuple {title, Kasus} membungkus kumpulan kasus dengan judul yang tampil di laporan. Modul bantu yang dibutuhkan uji dideklarasikan dengan cara yang sama seperti modul biasa, dan seluruh bentuk tersebut tetap berupa list sehingga dapat disarangkan sesuai kebutuhan. Kebiasaan yang sehat pada proyek besar adalah tetap menjalankan seluruh modul uji lewat rebar3 eunit di akhir, dengan pemanggilan subset dari shell hanya untuk menuntaskan satu kegagalan.

Menyusun uji berlapis#

Komposisi list EUnit memungkinkan uji disarangkan sesuai struktur yang dibutuhkan. Bentuk yang umum adalah fixture yang membungkus kumpulan kasus dengan judul:

erlang Fixture yang membungkus kumpulan kasus
antrean_test_() ->
    {setup,
     fun() -> ets:new(antrean_uji, [named_table, public]) end,
     fun(_) -> ets:delete(antrean_uji) end,
     [{"kosong", ?_assertEqual([], ets:lookup(antrean_uji, x))},
      {"isi",    ?_assertEqual(true, ets:insert(antrean_uji, {x, 1}))}]}.

Fixture setup/3 menyiapkan sekali untuk seluruh kelompok, dan laporannya menampilkan judul tiap kelompok, sehingga kegagalan terbaca sebagai antrean_test: kosong (modul). Penyarangan boleh berlanjut sesuai kebutuhan, dan seluruh struktur tetap data biasa, sebagaimana seluruh EUnit.

Kebiasaan yang menjaga uji EUnit tetap bernilai seiring modul tumbuh adalah menjaga uji tetap murni dan cepat. Kasus yang menyentuh proses, berkas, atau jaringan sebaiknya pindah ke Common Test, karena kebutuhan penyiapan dan pembongkarannya sudah berbeda skala, dan EUnit yang tetap di lingkup murni berjalan dalam hitungan milidetik sehingga tidak pernah menjadi alasan untuk menunda. Uji yang menuntut error juga ditulis eksplisit dengan ?assertError dan ?assertExit, sehingga kegagalan yang dijanjikan bukan kegagalan uji, dan laporan tetap bersih atas seluruh cabang yang sengaja diuji.

Ringkasan#

  • Fungsi berakhiran _test() adalah kasus tunggal dan test() adalah generator, dengan makro ?_assertEqual dan ?_assert sebagai asersi utama.
  • eunit:test/1 menjalankan satu modul dari shell, sedangkan rebar3 eunit menemukan dan menjalankan seluruh uji proyek.
  • Generator dapat menghasilkan kasus dari data secara programatik, dan fixture foreach menjamin penyiapan serta pembongkaran per kasus.
  • Kegagalan dicetak lengkap dengan nama kasus dan nilai yang diperbandingkan.

Referensi#