Pengujian

Menyusun pengujian dengan pustaka standar Test, mulai dari @test hingga pengorganisasian test set.

Daftar isi

Pustaka standar Test menyediakan pernyataan uji (assertion) untuk memastikan perilaku kode sesuai harapan. Pustaka ini disertakan sejak instalasi Julia, sehingga pengujian dapat ditulis tanpa memasang apa pun. Topik ini membahas @test dan @testset, pengujian atas error dengan @test_throws, penyarangan test set, serta penyusunan berkas runtests.jl sebagai titik masuk pengujian sebuah proyek. Setelah topik ini dipraktikkan, perubahan kode dapat diverifikasi secara otomatis setiap kali pengujian dijalankan.

Pernyataan uji pertama#

@test menerima ekspresi dan menyatakan bahwa ekspresi tersebut harus bernilai true. Satu pernyataan uji jarang berdiri sendiri, karena pengujian yang terorganisasi memerlukan kelompok dengan nama dan keluaran yang ringkas. @testset mengelompokkan beberapa @test di bawah satu judul, menghitung hasilnya, dan melaporkan ringkasan saat kelompok selesai dijalankan.

julia Contoh: testset dengan hasil yang dihitung
using Test

hasil = redirect_stdout(devnull) do
    @testset "Aritmetika dasar" begin
        @test 2 + 2 == 4
        @test 10 % 3 == 1
        @test sqrt(9) == 3.0
    end
end
println("ujian lulus: ", hasil.n_passed)
text Keluaran
ujian lulus: 3

Pada contoh di atas, keluaran ringkasan @testset sengaja dialihkan ke devnull dan yang dicetak hanyalah jumlah ujian yang lulus. Alasannya bersifat praktis, yaitu ringkasan lengkap mencantumkan kolom waktu yang nilainya berubah pada setiap eksekusi, sehingga hanya bagian yang stabil yang ditampilkan. Bentuk ringkasan lengkap ditunjukkan pada transkrip berikut, yang dijalankan langsung pada REPL.

text Transkrip REPL
julia> using Test

julia> @testset "Aritmetika dasar" begin
           @test 2 + 2 == 4
           @test 10 % 3 == 1
       end
Test Summary:    | Pass  Total  Time
Aritmetika dasar |    2      2  0.0s
Test.DefaultTestSet("Aritmetika dasar", false, true, false, "none", 1.793931004405056e9, Random.Xoshiro(0xfe5bc389c9ad0583, 0x8da6a4e334f096cc, 0x23b5af431d425afb, 0xcf5e81bcdbf1b411, 0x0571aaacfe667d45), 2, 1.79393100442509e9, 0x01, ReentrantLock(), Any[])

Kolom Pass menyatakan banyaknya ujian yang lulus, Total menyatakan seluruh ujian yang dijalankan, dan Time menyatakan lamanya eksekusi. Bila ada ujian yang tidak lulus, kolom Fail muncul dan barisnya memuat ekspresi yang gagal beserta nilai perbandingannya, sehingga penyebab kegagalan terbaca langsung dari keluaran pengujian.

Menguji error dengan @test_throws#

Kebalikan ujian yang harus lulus adalah ujian yang menuntut error muncul. @test_throws TipeError ekspresi lulus apabila ekspresi melempar error dengan tipe yang disebutkan, dan gagal apabila error tidak muncul atau tipenya berbeda. Pola ini menjaga agar kontrak fungsi terhadap masukan yang tidak sah tetap teruji, misalnya pembagian dengan nol atau daftar kosong.

julia Contoh: menguji error yang diharapkan
using Test

function rata_rata(nilai)
    isempty(nilai) && throw(ArgumentError("daftar tidak boleh kosong"))
    return sum(nilai) / length(nilai)
end

hasil = redirect_stdout(devnull) do
    @testset "Kontrak rata_rata" begin
        @test rata_rata([2, 4, 6]) == 4.0
        @test_throws ArgumentError rata_rata(Float64[])
    end
end
println("ujian lulus: ", hasil.n_passed)
text Keluaran
ujian lulus: 2

Selain @test_throws, pustaka Test menyediakan @test_broken untuk ujian yang diketahui gagal dan @test_skip untuk ujian yang sengaja dilewati. Keduanya muncul pada kolom ringkasan sebagai Broken atau Skip, sehingga keadaan pengujian yang belum selesai tetap terlihat alih-alih tersembunyi di balik ujian yang semuanya lulus.

Test set yang tersarang#

@testset dapat berada di dalam @testset lain. Setiap test set anak membentuk baris ringkasannya sendiri, sedangkan test set induk mengumpulkan hasil seluruh anaknya. Penyarangan ini dipakai untuk memisahkan kelompok kecil untuk satu kemampuan, misalnya kasus biasa dan kasus khusus pada satu fungsi, tanpa kehilangan hitungan keseluruhan.

julia Contoh: testset bersarang
using Test

hasil = redirect_stdout(devnull) do
    @testset "Sifat daftar" begin
        @testset "jumlah dan panjang" begin
            @test sum([1, 2, 3]) == 6
            @test length([1, 2, 3]) == 3
        end
        @testset "pengurutan" begin
            @test sort([3, 1, 2]) == [1, 2, 3]
        end
    end
end
println("total ujian lulus: ", sum(ts.n_passed for ts in hasil.results if ts isa Test.DefaultTestSet))
text Keluaran
total ujian lulus: 3

Objek hasil yang dikembalikan @testset induk memuat test set anak pada field results, sehingga hasil per kelompok dapat dibaca kembali dari program. Bila salah satu test set anak gagal, induk juga dilaporkan gagal, dan baris ringkasan induknya menunjukkan jumlah gagal dari seluruh keturunannya. Struktur ini membuat satu perintah pengujian cukup untuk memeriksa seluruh proyek.

Berkas runtests.jl#

Konvensi proyek Julia menempatkan pengujian pada folder test/ dengan berkas runtests.jl sebagai titik masuk. Perintah Pkg.test() menjalankan berkas itu di dalam lingkungan proyek, sedangkan pada pekerjaan yang tidak berbentuk package lengkap, berkas yang sama cukup dieksekusi langsung dengan julia runtests.jl. Berkas berikut menjadi contoh titik masuk yang menguji dua kemampuan fungsi sederhana.

julia runtests.jl
using Test

celsius_ke_fahrenheit(c) = c * 9 / 5 + 32

function rata_rata(nilai)
    isempty(nilai) && throw(ArgumentError("daftar tidak boleh kosong"))
    return sum(nilai) / length(nilai)
end

@testset "Konversi suhu" begin
    @test celsius_ke_fahrenheit(0) == 32
    @test celsius_ke_fahrenheit(100) == 212
    @test celsius_ke_fahrenheit(37) == 98.6
end

@testset "Rata-rata" begin
    @testset "kasus biasa" begin
        @test rata_rata([1, 2, 3]) == 2.0
        @test rata_rata([4]) == 4.0
    end
    @testset "kasus khusus" begin
        @test_throws ArgumentError rata_rata(Float64[])
    end
end

Eksekusi berkas tersebut dengan perintah berikut menampilkan ringkasan untuk setiap test set tingkat teratas, lengkap dengan kolom waktu per kelompok.

bash
# menjalankan seluruh pengujian proyek
julia runtests.jl
text Transkrip terminal
Test Summary: | Pass  Total  Time
Konversi suhu |    3      3  0.0s
Test Summary: | Pass  Total  Time
Rata-rata     |    3      3  0.1s

Bila salah satu ujian diubah menjadi tidak lulus, ringkasan keluaran menampilkan kolom Fail dengan baris ujian yang bersangkutan, dan perintah julia runtests.jl berakhir dengan kode keluar bukan nol. Sifat ini memungkinkan pengujian dihubungkan dengan alat otomatis lain, karena keberhasilan pengujian dapat dinilai dari kode keluar tanpa membaca keluaran teksnya.

Kesalahan umum#

Kesalahan pertama adalah menulis ujian yang bergantung pada keadaan antarujian. Test set sebaiknya dibangun agar setiap @test berdiri sendiri atas datanya. Urutan eksekusi dalam satu test set memang berurutan, tetapi pengujian yang saling menumpuk menyulitkan pencarian penyebab ketika satu perubahan menjatuhkan beberapa ujian sekaligus. Ujian yang membutuhkan penyiapan data menuliskan penyiapannya di dalam test set masing-masing.

Kesalahan kedua adalah memasukkan waktu tunggu, hasil unduhan, atau bilangan acak tanpa benih ke dalam ujian. Pengujian semacam itu kadang lulus dan kadang gagal tanpa ada perubahan kode, sehingga kepercayaan terhadap seluruh rangkaian pengujian merosot. Terakhir, ujian yang hanya mencakup kasus sukses memberi rasa aman palsu, karena error justru muncul dari masukan yang tidak sah. Setiap ArgumentError yang dinyatakan dalam kontrak fungsi sebaiknya dipasangkan dengan ujiannya sendiri melalui @test_throws.

Ringkasan#

  • @test menyatakan ekspresi yang harus bernilai true, dan @testset mengelompokkan serta menghitung ujian dengan ringkasan per kelompok.
  • @test_throws menguji bahwa error tipe tertentu muncul, sedangkan @test_broken dan @test_skip menyatakan keadaan ujian yang belum selesai.
  • Pengujian proyek berkumpul pada test/runtests.jl, dijalankan dengan Pkg.test() atau julia runtests.jl, dan keberhasilannya tercermin pada kode keluar.

Referensi#