Empat jenis umum cakupan kode

Pelajari apa itu 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 itu cakupan kode?

Cakupan kode adalah metrik yang mengukur persentase kode sumber yang dijalankan pengujian Anda. Langkah ini membantu Anda mengidentifikasi area yang mungkin tidak diuji dengan tepat.

Sering kali, pencatatan metrik ini terlihat seperti ini:

File % Pernyataan % Cabang % Fungsi % Garis Garis 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 memberikan keyakinan lebih bahwa aplikasi Anda telah diuji secara menyeluruh. Namun, masih ada banyak 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-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 pengujian pada demo langsung ini atau lihat repositori.

Cakupan fungsi

Cakupan kode: 50%

/* coffee.js */

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

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

Cakupan fungsi adalah metrik yang mudah dipahami. Jenis ini merekam persentase fungsi dalam kode yang dipanggil oleh pengujian Anda.

Dalam contoh kode, ada 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 rangkaian pengujian Anda. Jika ada baris kode yang tetap tidak dijalankan, 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 menjalankan kondisi americano (dua baris) dan fungsi isValidCoffee (satu baris). Hal ini menghasilkan cakupan garis sebesar 62,5%.

Perhatikan bahwa cakupan garis tidak memperhitungkan 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 Chek.
  2. Menelepon calcCoffeeIngredient dengan coffeeName dan cup Tanda Chek.
  3. Kopi adalah Espresso Tanda Chek.
  4. Kopi adalah Americano Tanda X.
  5. Kopi lainnya Tanda Chek.

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

Liputan 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 oleh pengujian Anda. Sekilas, Anda mungkin bertanya-tanya, "bukankah ini sama dengan cakupan garis?" Memang, cakupan pernyataan mirip dengan cakupan garis 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;

Tes tersebut hanya mencakup lima dari sembilan pernyataan, dengan demikian cakupan pernyataan adalah 55,55%.

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

Jenis cakupan kode apa yang harus Anda pilih?

Sebagian besar alat cakupan kode mencakup 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, keduanya adalah perkembangan alami setelah cakupan pernyataan.

Setelah mencapai cakupan pernyataan yang tinggi, Anda dapat melanjutkan 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 akulitatif yang mengukur seberapa baik rangkaian pengujian mencakup fitur software. Langkah ini membantu menentukan tingkat risiko yang ada.
  • Cakupan kode: Metrik kuantitatif yang mengukur proporsi kode yang dijalankan selama pengujian. Ini tentang berapa banyak kode yang dicakup oleh pengujian.

Berikut analogi yang disederhanakan: bayangkan aplikasi web sebagai rumah.

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

Cakupan kode 100% bukan berarti tidak ada bug

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

Cara mudah 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 tidak masuk akal karena tidak benar-benar menguji kode. Pernyataan expect(true).toBe(true) akan selalu lulus terlepas dari apakah kode berfungsi dengan benar atau tidak.

Metrik yang buruk lebih buruk daripada tidak ada metrik

Metrik yang buruk dapat memberi Anda rasa aman yang keliru, dan hal ini lebih buruk dibandingkan jika Anda tidak memiliki metrik sama sekali. Misalnya, jika Anda memiliki rangkaian pengujian yang mencapai 100% cakupan kode tetapi semua pengujian tidak bermakna, Anda mungkin salah merasakan keamanan bahwa kode Anda telah diuji dengan baik. Jika Anda tidak sengaja menghapus atau merusak bagian kode aplikasi, pengujian akan tetap lulus, meskipun aplikasi tidak lagi berfungsi dengan benar.

Untuk menghindari skenario ini:

  • Tinjauan pengujian. Tulis dan tinjau pengujian untuk memastikan pengujian tersebut bermakna dan uji kode dalam berbagai skenario yang berbeda.
  • Gunakan cakupan kode sebagai panduan, bukan sebagai satu-satunya ukuran efektivitas atau kualitas kode pengujian.

Menggunakan cakupan kode dalam berbagai jenis pengujian

Mari kita lihat lebih dekat 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 Pengujian ini dapat membantu mengumpulkan cakupan kode untuk pengujian integrasi, tetapi gunakan dengan hati-hati. Dalam hal ini, Anda menghitung cakupan bagian yang lebih besar dari kode sumber, 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 menyeluruh (E2E). Mengukur cakupan kode untuk pengujian E2E sulit dan menantang karena sifat pengujian yang rumit. Daripada menggunakan cakupan kode, cakupan persyaratan mungkin merupakan cara yang lebih baik. 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 Anda juga mempertimbangkan faktor lainnya, seperti kualitas pengujian dan persyaratan aplikasi.

Menargetkan 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({});
  });
});