{i>Tooltrade

Pengujian otomatis pada dasarnya hanyalah kode yang akan menampilkan atau menyebabkan error jika ada sesuatu yang salah. Sebagian besar library atau framework pengujian menyediakan berbagai primitif yang membuat pengujian lebih mudah ditulis.

Seperti disebutkan di bagian sebelumnya, primitif ini hampir selalu menyertakan cara untuk menentukan pengujian independen (disebut sebagai kasus pengujian) dan memberikan pernyataan. Pernyataan adalah cara untuk menggabungkan pemeriksaan hasil dan menampilkan error jika ada sesuatu yang salah, serta dapat dianggap sebagai primitif dasar dari semua primitif pengujian.

Halaman ini membahas pendekatan umum untuk primitif ini. Framework yang Anda pilih mungkin memiliki seperti ini, tetapi bukan referensi yang tepat.

Contoh:

import { fibonacci, catalan } from '../src/math.js';
import { assert, test, suite } from 'a-made-up-testing-library';

suite('math tests', () => {
  test('fibonacci function', () => {
    // check expected fibonacci numbers against our known actual values
    // with an explanation if the values don't match
    assert.equal(fibonacci(0), 0, 'Invalid 0th fibonacci result');
    assert.equal(fibonacci(13), 233, 'Invalid 13th fibonacci result');
  });
  test('relationship between sequences', () => {
    // catalan numbers are greater than fibonacci numbers (but not equal)
    assert.isAbove(catalan(4), fibonacci(4));
  });
  test('bugfix: check bug #4141', () => {
    assert.isFinite(fibonacci(0)); // fibonacci(0) was returning NaN
  })
});

Contoh ini membuat grup pengujian (terkadang disebut suite) yang disebut "uji matematika", dan menentukan tiga kasus pengujian independen yang masing-masing menjalankan beberapa pernyataan. Kasus pengujian ini biasanya dapat ditangani atau dijalankan satu per satu, misalnya, dengan tanda filter di runner pengujian Anda.

Helper pernyataan sebagai primitif

Sebagian besar framework pengujian, termasuk Vitest, menyertakan sekumpulan pendukung pernyataan pada objek assert yang memungkinkan Anda dengan cepat memeriksa nilai yang ditampilkan atau status lain terhadap beberapa expectation. Ekspektasi itu sering kali berupa nilai-nilai "baik yang diketahui". Pada contoh sebelumnya, kita mengetahui bahwa angka Fibonacci ke-13 seharusnya 233, sehingga kita dapat mengonfirmasinya secara langsung menggunakan assert.equal.

Anda mungkin juga memiliki ekspektasi bahwa nilai menggunakan bentuk tertentu, atau lebih besar dari nilai lain, atau memiliki beberapa properti lain. Kursus ini tidak akan membahas semua kemungkinan helper pernyataan, tetapi framework pengujian selalu menyediakan setidaknya pemeriksaan dasar berikut:

  • Pemeriksaan 'truthy', sering dijelaskan sebagai pemeriksaan 'ok', memeriksa apakah suatu kondisi benar, cocok dengan cara Anda menulis if yang memeriksa apakah sesuatu berhasil atau benar. Kolom ini cenderung diberikan sebagai assert(...) atau assert.ok(...), dan mengambil satu nilai serta komentar opsional.

  • Pemeriksaan kesetaraan, seperti dalam contoh uji matematika, yang mengharapkan nilai yang ditampilkan atau status objek sama dengan nilai baik yang diketahui. Elemen ini ditujukan untuk persamaan primitif (seperti untuk angka dan string) atau persamaan referensial (keduanya merupakan objek yang sama). Di balik layar, hal ini hanyalah pemeriksaan 'kebenaran' dengan perbandingan == atau ===.

    • JavaScript membedakan antara kesetaraan longgar (==) dan ketat (===). Sebagian besar library pengujian masing-masing menyediakan metode assert.equal dan assert.strictEqual.
  • Pemeriksaan kesetaraan mendalam, yang memperluas pemeriksaan kesetaraan untuk mencakup pemeriksaan konten objek, array, dan jenis data lainnya yang lebih kompleks, serta logika internal untuk menelusuri objek guna membandingkannya. Hal ini penting karena JavaScript tidak memiliki cara bawaan untuk membandingkan isi dua objek atau array. Misalnya, [1,2,3] == [1,2,3] selalu salah. Framework pengujian sering kali menyertakan helper deepEqual atau deepStrictEqual.

Helper pernyataan yang membandingkan dua nilai (bukan pemeriksaan 'truthy' saja) biasanya menggunakan dua atau tiga argumen:

  • Nilai sebenarnya, seperti yang dihasilkan dari kode yang sedang diuji atau menjelaskan status yang akan divalidasi.
  • Nilai yang diharapkan, biasanya hard code (misalnya, angka atau string literal).
  • Komentar opsional yang menjelaskan apa yang diharapkan atau apa yang mungkin gagal, yang akan disertakan jika baris ini gagal.

Mengombinasikan pernyataan untuk membuat berbagai pemeriksaan juga merupakan praktik yang cukup umum, karena sangat jarang ada pernyataan yang dapat mengonfirmasi status sistem Anda dengan benar. Contoh:

  test('JWT parse', () => {
    const json = decodeJwt('eyJieSI6InNhbXRob3Ii…');

    assert.ok(json.payload.admin, 'user should be admin');
    assert.deepEqual(json.payload.groups, ['role:Admin', 'role:Submitter']);
    assert.equal(json.header.alg, 'RS265')
    assert.isAbove(json.payload.exp, +new Date(), 'expiry must be in future')
  });

Vitest menggunakan library pernyataan Chai secara internal untuk menyediakan helper pernyataannya, dan ada baiknya memeriksa referensinya untuk mengetahui pernyataan dan helper yang mungkin sesuai dengan kode Anda.

Pernyataan fasih dan BDD

Beberapa developer lebih memilih gaya pernyataan yang dapat disebut pengembangan berbasis perilaku (BDD), atau pernyataan gaya Fluent. Objek ini juga disebut helper "expect", karena titik entri untuk memeriksa ekspektasi adalah metode bernama expect().

Helper akan berperilaku dengan cara yang sama seperti pernyataan yang ditulis seperti panggilan metode sederhana seperti assert.ok atau assert.strictDeepEquals, tetapi beberapa developer menganggapnya lebih mudah dibaca. Pernyataan BDD dapat dibaca seperti berikut:

// A failure here would generate "Expect result to be an array that does include 42"
const result = await possibleMeaningsOfLife();
expect(result).to.be.an('array').that.does.include(42);

// or a simpler form
expect(result).toBe('array').toContainEqual(42);

// the same in assert might be
assert.typeOf(result, 'array', 'Expected the result to be an array');
assert.include(result, 42, 'Expected the result to include 42');

Gaya pernyataan ini berfungsi karena teknik yang disebut perantaian metode, dengan objek yang ditampilkan oleh expect dapat terus dirantai bersama dengan panggilan metode lebih lanjut. Beberapa bagian panggilan, termasuk to.be dan that.does pada contoh sebelumnya, tidak memiliki fungsi dan hanya disertakan untuk membuat panggilan lebih mudah dibaca dan berpotensi menghasilkan komentar otomatis jika pengujian gagal. (Khususnya, expect biasanya tidak mendukung komentar opsional, karena rantai harus menjelaskan kegagalan dengan jelas.)

Banyak framework pengujian mendukung pernyataan Fluent/BDD dan reguler. Vitest, misalnya, mengekspor kedua pendekatan Chai dan memiliki pendekatannya sendiri yang sedikit lebih ringkas terhadap BDD. Di sisi lain, Jest hanya menyertakan metode harapan secara default.

Mengelompokkan pengujian di seluruh file

Saat menulis pengujian, kita sudah cenderung menyediakan pengelompokan implisit—bukan semua pengujian berada dalam satu file, biasanya menulis pengujian di beberapa file. Bahkan, runner pengujian biasanya hanya mengetahui bahwa file ditujukan untuk pengujian karena adanya filter atau ekspresi reguler yang telah ditetapkan sebelumnya—vitest, misalnya, menyertakan semua file dalam project yang diakhiri dengan ekstensi seperti ".test.jsx" atau ".spec.ts" (".test" dan ".spec" ditambah sejumlah ekstensi yang valid).

Pengujian komponen cenderung ditempatkan di file pembanding ke komponen yang sedang diuji, seperti pada struktur direktori berikut:

Daftar file dalam sebuah direktori, termasuk UserList.tsx dan UserList.test.tsx.
File komponen dan file pengujian terkait.

Demikian pula, pengujian unit cenderung ditempatkan berdekatan dengan kode yang sedang diuji. Setiap pengujian menyeluruh dapat berada dalam filenya sendiri, dan pengujian integrasi bahkan dapat ditempatkan di folder uniknya sendiri. Struktur ini dapat berguna ketika kasus pengujian yang kompleks berkembang sehingga memerlukan file dukungan non-pengujiannya sendiri, seperti library dukungan yang diperlukan hanya untuk pengujian.

Mengelompokkan pengujian dalam file

Seperti yang digunakan dalam contoh sebelumnya, adalah praktik yang umum untuk menempatkan pengujian dalam panggilan ke suite() yang mengelompokkan pengujian yang Anda siapkan dengan test(). Suite biasanya tidak menguji sendiri, tetapi membantu memberikan struktur dengan mengelompokkan pengujian atau sasaran terkait dengan memanggil metode yang diteruskan. Untuk test(), metode yang diteruskan menjelaskan tindakan pengujian itu sendiri.

Seperti pada pernyataan, ada padanan yang cukup standar di Fluent/BDD terhadap pengujian pengelompokan. Beberapa contoh umum dibandingkan dalam kode berikut:

// traditional/TDD
suite('math tests', () => {
  test('handle zero values', () => {
    assert.equal(fibonacci(0), 0);
  });
});

// Fluent/BDD
describe('math tests', () => {
  it('should handle zero values', () => {
    expect(fibonacci(0)).toBe(0);
  });
})

Di sebagian besar framework, suite dan describe berperilaku mirip, seperti test dan it, dibandingkan dengan perbedaan yang lebih besar antara menggunakan expect dan assert untuk menulis pernyataan.

Alat-alat lain memiliki pendekatan yang sedikit berbeda dalam mengatur rangkaian dan pengujian. Misalnya, runner pengujian bawaan Node.js mendukung panggilan bertingkat ke test() untuk membuat hierarki pengujian secara implisit. Namun, Vitest hanya mengizinkan penyusunan bertingkat semacam ini menggunakan suite() dan tidak akan menjalankan test() yang ditentukan di dalam test() lainnya.

Sama seperti pernyataan, ingat bahwa kombinasi persis metode pengelompokan yang disediakan oleh tech stack Anda tidak terlalu penting. Kursus ini akan membahasnya secara abstrak, tetapi Anda harus mengetahui bagaimana penerapannya pada alat pilihan Anda.

Metode siklus proses

Salah satu alasan untuk mengelompokkan pengujian, bahkan secara implisit di tingkat teratas dalam file, adalah untuk menyediakan metode penyiapan dan pembongkaran yang berjalan untuk setiap pengujian, atau sekali untuk sekelompok pengujian. Sebagian besar framework menyediakan empat metode:

Untuk setiap 'test()' atau 'it()' Sekali untuk suite
Sebelum pengujian dijalankan `beforeSetiap()` `beforeAll()`
Setelah pengujian dijalankan {i>afterEvery()` {i>afterAll()`<i}

Misalnya, Anda mungkin ingin mengisi otomatis database pengguna virtual sebelum setiap pengujian, lalu menghapusnya:

suite('user test', () => {
  beforeEach(() => {
    insertFakeUser('bob@example.com', 'hunter2');
  });
  afterEach(() => {
    clearAllUsers();
  });

  test('bob can login', async () => { … });
  test('alice can message bob', async () => { … });
});

Hal ini dapat berguna untuk menyederhanakan pengujian Anda. Anda dapat membagikan kode penyiapan dan penghapusan umum, bukan menduplikasinya di setiap pengujian. Selain itu, jika kode penyiapan dan pemutusan itu sendiri memunculkan error, hal tersebut dapat menunjukkan masalah struktural yang tidak melibatkan pengujian itu sendiri gagal.

Saran umum

Berikut adalah beberapa tip yang perlu diingat ketika memikirkan tentang primitif ini.

Primitif adalah panduan

Ingat bahwa alat dan primitif di sini, dan di beberapa halaman berikutnya, tidak akan sama persis dengan Vitest, atau Jest, atau Mocha, atau Web Test Runner, atau framework khusus lainnya. Meskipun kami telah menggunakan Vitest sebagai panduan umum, pastikan untuk memetakannya ke framework pilihan Anda.

Mengombinasikan pernyataan sesuai kebutuhan

Pengujian pada dasarnya adalah kode yang dapat menampilkan error. Setiap runner akan menyediakan primitif, kemungkinan test(), untuk menjelaskan kasus pengujian yang berbeda.

Namun, jika runner tersebut juga menyediakan assert(), expect(), dan helper pernyataan, ingat bahwa bagian ini lebih terkait kemudahan dan Anda dapat melewatinya jika perlu. Anda dapat menjalankan kode apa pun yang mungkin memunculkan error, termasuk library pernyataan lain, atau pernyataan if standar.

Penyiapan IDE dapat sangat membantu

Memastikan IDE, seperti VSCode, memiliki akses ke pelengkapan otomatis dan dokumentasi tentang alat pengujian yang Anda pilih, dapat membuat Anda lebih produktif. Misalnya, ada lebih dari 100 metode di assert dalam library pernyataan Chai, dan menyediakan dokumentasi untuk metode yang tepat muncul secara inline bisa menjadi lebih praktis.

Hal ini dapat menjadi sangat penting untuk beberapa framework pengujian yang mengisi namespace global dengan metode pengujiannya. Hal ini merupakan perbedaan kecil, tetapi sering kali dapat dilakukan untuk menggunakan library pengujian tanpa mengimpornya jika library tersebut otomatis ditambahkan ke namespace global:

// some.test.js
test('using test as a global', () => { … });

Sebaiknya impor helper meskipun didukung secara otomatis karena akan memberi IDE cara yang jelas untuk mencari metode ini. (Anda mungkin pernah mengalami masalah ini saat mem-build React, karena beberapa codebase memiliki React global yang ajaib, tetapi ada beberapa yang tidak, dan mengharuskannya diimpor di semua file menggunakan React.)

// some.test.js
import { test } from 'vitest';
test('using test as an import', () => { … });