Empat jenis umum cakupan kode

Pelajari apa yang dimaksud dengan cakupan kode dan temukan empat cara umum untuk mengukurnya.

Pernahkah Anda mendengar ungkapan "cakupan kode"? Dalam postingan ini, kita akan mengeksplorasi cakupan kode dalam pengujian dan empat cara umum untuk mengukurnya.

Apa yang dimaksud dengan cakupan kode?

Cakupan kode adalah metrik yang mengukur persentase kode sumber yang dijalankan pengujian Anda. Pengujian ini membantu Anda mengidentifikasi area yang mungkin tidak memiliki pengujian yang tepat.

Sering kali, pencatatan metrik ini terlihat seperti ini:

File % Pernyataan % Cabang % Fungsi % Baris Baris yang ditemukan
file.js 90% 100% 90% 80% 89.256
coffee.js 55,55% 80% 50% 62,5% 10-11, 18

Saat Anda menambahkan fitur dan pengujian baru, meningkatkan persentase cakupan kode dapat membuat Anda lebih yakin bahwa aplikasi Anda telah diuji secara menyeluruh. Namun, masih ada lagi yang bisa ditemukan.

Empat jenis umum cakupan kode

Ada empat cara umum untuk mengumpulkan dan menghitung cakupan kode: fungsi, baris, cabang, dan cakupan pernyataan.

Empat jenis cakupan teks.

Untuk melihat bagaimana setiap jenis cakupan kode menghitung persentasenya, pertimbangkan contoh kode berikut untuk menghitung bahan kopi:

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

Pengujian yang memverifikasi fungsi calcCoffeeIngredient adalah:

/* coffee.test.js */

import { describe, expect, assert, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-incomplete';

describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });

  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown');
    expect(result).to.deep.equal({});
  });
});

Anda dapat menjalankan kode dan menguji pada demo langsung ini atau melihat repositori.

Cakupan fungsi

Cakupan kode: 50%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...
}

function isValidCoffee(name) {
  // ...
}

Cakupan fungsi adalah metrik sederhana. Metrik ini mencatat persentase fungsi dalam kode Anda yang dipanggil oleh pengujian Anda.

Dalam contoh kode, terdapat dua fungsi: calcCoffeeIngredient dan isValidCoffee. Pengujian hanya memanggil fungsi calcCoffeeIngredient, sehingga cakupan fungsi adalah 50%.

Cakupan garis

Cakupan kode: 62,5%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

Cakupan baris mengukur persentase baris kode yang dapat dieksekusi yang dijalankan oleh rangkaian pengujian Anda. Jika baris kode tetap tidak dieksekusi, itu berarti beberapa bagian kode belum diuji.

Contoh kode memiliki delapan baris kode yang dapat dieksekusi (ditandai dengan warna merah dan hijau), tetapi pengujian tidak mengeksekusi kondisi americano (dua baris) dan fungsi isValidCoffee (satu baris). Hal ini menghasilkan cakupan baris sebesar 62,5%.

Perlu diperhatikan bahwa cakupan baris tidak mempertimbangkan pernyataan deklarasi akun, seperti function isValidCoffee(name) dan let espresso, water;, karena tidak dapat dieksekusi.

Cakupan cabang

Cakupan kode: 80%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...

  if (coffeeName === 'espresso') {
    // ...
    return { espresso };
  }

  if (coffeeName === 'americano') {
    // ...
    return { espresso, water };
  }

  return {};
}
…

Cakupan cabang mengukur persentase cabang atau titik keputusan yang dieksekusi dalam kode, seperti pernyataan if atau loop. Menentukan apakah pengujian memeriksa cabang benar dan salah dari pernyataan bersyarat.

Ada lima cabang dalam contoh kode:

  1. Menelepon calcCoffeeIngredient hanya dengan coffeeName Tanda centang.
  2. Menelepon calcCoffeeIngredient dengan coffeeName dan cup Tanda centang.
  3. Kopi adalah Espresso Tanda centang.
  4. Kopi adalah Americano Tanda X.
  5. Kopi lainnya Tanda centang.

Pengujian mencakup semua cabang kecuali kondisi Coffee is Americano. Jadi cakupan cabang adalah 80%.

Cakupan laporan

Cakupan kode: 55,55%

/* coffee.js */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  return {};
}

export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

Cakupan pernyataan mengukur persentase pernyataan dalam kode yang dijalankan pengujian Anda. Pada awalnya, Anda mungkin bertanya-tanya, “bukankah ini sama dengan cakupan garis?” Cakupan pernyataan memang mirip dengan cakupan baris tetapi memperhitungkan satu baris kode yang berisi beberapa pernyataan.

Dalam contoh kode, ada delapan baris kode yang dapat dieksekusi, tetapi ada sembilan pernyataan. Dapatkah Anda melihat baris yang berisi dua pernyataan?

Periksa jawaban Anda

Ini adalah baris berikut: espresso = 30 * cup; water = 70 * cup;

Pengujian hanya mencakup lima dari sembilan pernyataan, sehingga cakupan pernyataan tersebut adalah 55,55%.

Jika Anda selalu menulis satu pernyataan per baris, cakupan baris Anda akan serupa dengan cakupan laporan Anda.

Jenis cakupan kode apa yang harus Anda pilih?

Sebagian besar alat cakupan kode menyertakan empat jenis cakupan kode umum ini. Memilih metrik cakupan kode yang akan diprioritaskan bergantung pada persyaratan project, praktik pengembangan, dan tujuan pengujian tertentu.

Secara umum, cakupan pernyataan adalah titik awal yang baik karena merupakan metrik yang sederhana dan mudah dipahami. Tidak seperti cakupan pernyataan, cakupan cabang dan cakupan fungsi mengukur apakah pengujian memanggil kondisi (cabang) atau fungsi. Oleh karena itu, jenis peristiwa tersebut adalah perkembangan alami setelah cakupan pernyataan.

Setelah Anda mencapai cakupan pernyataan yang tinggi, Anda kemudian dapat beralih ke cakupan cabang dan cakupan fungsi.

Apakah cakupan pengujian sama dengan cakupan kode?

Tidak. Cakupan pengujian dan cakupan kode sering kali membingungkan, tetapi keduanya berbeda:

  • Cakupan pengujian: Metrik akualitatif yang mengukur seberapa baik suite pengujian dalam mencakup fitur software. Manajemen risiko membantu menentukan tingkat risiko yang terlibat.
  • Cakupan kode: Metrik kuantitatif yang mengukur proporsi kode yang dieksekusi selama pengujian. Ini tentang berapa banyak kode yang dicakup dalam pengujian.

Berikut adalah analogi yang disederhanakan: bayangkan aplikasi web sebagai rumah.

  • Cakupan pengujian mengukur seberapa baik pengujian mencakup ruangan di rumah.
  • Cakupan kode mengukur seberapa banyak bagian rumah yang telah dilalui pengujian.

Cakupan kode 100% bukan berarti tidak ada bug

Meskipun mencapai cakupan kode yang tinggi dalam pengujian memang diinginkan, cakupan kode 100% tidak menjamin tidak adanya bug atau cacat dalam kode Anda.

Cara sederhana untuk mencapai cakupan kode 100%

Pertimbangkan pengujian berikut:

/* coffee.test.js */

// ...
describe('Warning: Do not do this', () => {
  it('is meaningless', () => { 
    calcCoffeeIngredient('espresso', 2);
    calcCoffeeIngredient('americano');
    calcCoffeeIngredient('unknown');
    isValidCoffee('mocha');
    expect(true).toBe(true); // not meaningful assertion
  });
});

Pengujian ini mencapai cakupan fungsi, baris, cabang, dan pernyataan 100%, tetapi ini tidak masuk akal karena sebenarnya tidak menguji kode. Pernyataan expect(true).toBe(true) akan selalu diteruskan terlepas dari apakah kode berfungsi dengan benar atau tidak.

Metrik yang buruk lebih buruk daripada tidak ada metrik

Metrik yang buruk dapat memberi Anda perasaan aman yang tidak benar, yang lebih buruk daripada tidak memiliki metrik sama sekali. Misalnya, jika Anda memiliki rangkaian pengujian yang mencapai cakupan kode 100%, tetapi semua pengujian tersebut tidak bermakna, Anda mungkin mendapatkan kesan keamanan palsu bahwa kode Anda sudah teruji dengan baik. Jika Anda secara tidak sengaja menghapus atau merusak sebagian kode aplikasi, pengujian akan tetap lulus, meskipun aplikasi tidak lagi berfungsi dengan benar.

Untuk menghindari skenario ini:

  • Uji ulasan. Tulis dan tinjau pengujian untuk memastikan pengujiannya bermanfaat dan uji kode dalam berbagai skenario.
  • Gunakan cakupan kode sebagai pedoman, bukan sebagai satu-satunya ukuran efektivitas pengujian atau kualitas kode.

Menggunakan cakupan kode dalam berbagai jenis pengujian

Mari kita pelajari lebih lanjut cara menggunakan cakupan kode dengan tiga jenis pengujian umum:

  • Pengujian unit. Pengujian ini adalah jenis pengujian terbaik untuk mengumpulkan cakupan kode karena dirancang untuk mencakup beberapa skenario kecil dan jalur pengujian.
  • Pengujian integrasi Kode ini dapat membantu mengumpulkan cakupan kode untuk pengujian integrasi, tetapi gunakan dengan hati-hati. Dalam hal ini, Anda menghitung cakupan untuk bagian kode sumber yang lebih besar, dan mungkin sulit untuk menentukan pengujian mana yang sebenarnya mencakup bagian kode yang mana. Meskipun demikian, menghitung cakupan kode pengujian integrasi mungkin berguna untuk sistem lama yang tidak memiliki unit yang terisolasi dengan baik.
  • Pengujian end-to-end (E2E). Mengukur cakupan kode untuk pengujian E2E sulit dan menantang karena sifat pengujian ini yang rumit. Alih-alih menggunakan cakupan kode, cakupan persyaratan mungkin bisa menjadi cara yang lebih baik. Hal ini karena fokus pengujian E2E adalah untuk mencakup persyaratan pengujian Anda, bukan berfokus pada kode sumber.

Kesimpulan

Cakupan kode dapat menjadi metrik yang berguna untuk mengukur efektivitas pengujian Anda. Cara ini dapat membantu Anda meningkatkan kualitas aplikasi dengan memastikan bahwa logika penting dalam kode Anda telah diuji dengan baik.

Namun, ingat bahwa cakupan kode hanyalah satu metrik. Pastikan juga untuk mempertimbangkan faktor lain, seperti kualitas pengujian dan persyaratan aplikasi.

Bertujuan untuk mencapai cakupan kode 100% bukanlah tujuannya. Sebagai gantinya, Anda harus menggunakan cakupan kode bersama dengan rencana pengujian menyeluruh yang menggabungkan berbagai metode pengujian, termasuk pengujian unit, pengujian integrasi, pengujian end-to-end, dan pengujian manual.

Lihat contoh kode lengkap dan pengujian dengan cakupan kode yang baik. Anda juga dapat menjalankan kode dan pengujian dengan demo langsung ini.

/* coffee.js - a complete example */

export function calcCoffeeIngredient(coffeeName, cup = 1) {
  if (!isValidCoffee(coffeeName)) return {};

  let espresso, water;

  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }

  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }

  throw new Error (`${coffeeName} not found`);
}

function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}
/* coffee.test.js - a complete test suite */

import { describe, expect, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-complete';

describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });

  it('should have americano', () => {
    const result = calcCoffeeIngredient('americano');
    expect(result.espresso).to.equal(30);
    expect(result.water).to.equal(70);
  });

  it('should throw error', () => {
    const func = () => calcCoffeeIngredient('mocha');
    expect(func).toThrowError(new Error('mocha not found'));
  });

  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown')
    expect(result).to.deep.equal({});
  });
});