Empat jenis umum cakupan kode

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

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

Cakupan kode adalah metrik yang mengukur persentase kode sumber yang dieksekusi pengujian Anda. Hal 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 telah diuji secara menyeluruh. Namun, masih ada banyak lagi yang bisa ditemukan.

Empat jenis cakupan kode yang umum

Ada empat cara umum untuk mengumpulkan dan menghitung cakupan kode: cakupan fungsi, baris, cabang, dan 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 pengujian di 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 yang mudah. Metrik ini mencatat persentase fungsi dalam kode Anda yang dipanggil oleh pengujian Anda.

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

Cakupan jalur

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 dieksekusi oleh rangkaian pengujian Anda. Jika baris kode tetap tidak dieksekusi, 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%.

Perhatikan bahwa cakupan baris tidak memperhitungkan pernyataan deklarasi, 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. Ini menentukan apakah pengujian memeriksa cabang benar dan salah dari pernyataan kondisional.

Ada lima cabang dalam contoh kode:

  1. Memanggil 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 dieksekusi oleh pengujian Anda. Sekilas, Anda mungkin bertanya-tanya, “bukankah ini sama dengan cakupan baris?” Memang, cakupan pernyataan 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 menemukan baris yang berisi dua pernyataan?

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, keduanya merupakan progres 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. Hal ini membantu menentukan tingkat risiko yang terlibat.
  • Cakupan kode: Metrik kuantitatif yang mengukur proporsi kode yang dieksekusi selama pengujian. Ini tentang jumlah kode yang dicakup oleh pengujian.

Berikut adalah analogi sederhana: 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 tentu saja Anda ingin mencapai cakupan kode yang tinggi dalam pengujian, cakupan kode 100% tidak menjamin tidak adanya bug atau kekurangan 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 tidak masuk akal karena tidak benar-benar menguji kode. Pernyataan expect(true).toBe(true) akan selalu lulus terlepas dari apakah kode berfungsi dengan benar.

Metrik yang buruk lebih buruk daripada tidak ada metrik

Metrik yang buruk dapat memberikan kesan keamanan yang palsu, yang lebih buruk daripada tidak memiliki metrik sama sekali. Misalnya, jika Anda memiliki rangkaian pengujian yang mencapai cakupan kode 100%, tetapi semua pengujian tidak berarti, Anda mungkin mendapatkan rasa aman palsu 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:

  • Peninjauan pengujian. Tulis dan tinjau pengujian untuk memastikan pengujiannya bermanfaat dan uji kode dalam berbagai skenario.
  • Gunakan cakupan kode sebagai panduan, 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 Pengujian 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 akan sulit untuk menentukan pengujian mana yang sebenarnya mencakup bagian kode tersebut. 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 ini yang rumit. Daripada menggunakan cakupan kode, cakupan persyaratan mungkin merupakan 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. Hal 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 untuk mempertimbangkan faktor lain juga, seperti kualitas pengujian dan persyaratan aplikasi Anda.

Sasaran kami bukanlah cakupan kode 100%. 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({});
  });
});