Mengenal Unit Testing dengan Python

Bagus Aji Santoso 18 Juli 2017

Mengenal Unit Testing dengan Python

Testing adalah proses untuk memastikan bahwa kode yang ditulis sudah beralan dengan seharusnya. Tujuan ini dapat dicapai dengan berbagai cara mulai dari secara manual memasukkan beberapa nilai dan memastikan hasil yang didapat sudah benar, hingga membuat serangkaian tes terstruktur yang berjalan secara otomatis dan memastikan bahwa kesuluruhan program sudah berjalan dengan seharusnya.

Satu bentuk testing yang paling umum adalah unit testing. Teknik ini dilakukan dengan cara melakukan pengecekan satu blok kode (biasanya sebuah fungsi) dan memastikan bahwa blok tersebut sudah berjalan dengan benar. Sebagai contoh, berikut ini kode Python yang akan mencetak kode tahun dalam bentuk angka Romawi:

symbols = [('M', 1000), ('C M', 900), ('D', 500), ('C D', 400), ('C', 100), ('X C', 90), ('L', 50), ('X L', 40), ('X', 10), ('I X', 9), ('V', 5), ('I V', 4), ('I', 1)]

def romannumeral(number):
	while number > 0:
		for symbol, value in symbols:
			if number - value >= 0:
				print symbol,
				number = number - value
				continue

number_in = raw_input("Enter a number: ")
romannumeral(int(number_in))

Kode di atas cukup sulit untuk dilakukan testing karena fungsi yang sama melakukan dua pekerjaan yaitu menghitung nilai dan mencetaknya. Artinya tidak ada tempat untuk menangkap dan menguji nilai Romawi yang dihasilkan sebelum dikirim ke terminal.

Langkah pertama yang harus dilakukan adalah me-refactor kode di atas sehingga fungsi romannumeral() hanya mengembalikan nilai Romawinya tanpa mencetak. Berikut fungsi yang telah diperbarui:

def romannumeral(number):
	outstring = ""
	while number > 0:
		for symbol, value in symbols:
			if number - value >= 0:
				outstring += symbol
				number = number - value
				continue
	return outstring

Kode di atas menghapus spasi diantara dua simbol, jadi kita juga menghapus spasi yang ada di tupple yang ada dalam list symbols.

Karena sekarang kita sudah bisa menangkap kode yang dihasilkan, maka kita sudah dapat mengotomasi pengujian program ini.

Test Driven Development

Salah satu materi yang diajarkan saat belajar software development adalah kita harus menulis sebuah tes saat memulai proyek baru. Paradigma ini (yang dikenal dengan istilah *test driven development *atau TDD) melakukan pengujian bukan hanya untuk mencari bug, tapi juga membangun spesifikasi program itu sendiri.

Proses TDD mengikuti langkah-langkah berikut:

  1. Tulis tes baru
  2. Jalankan semua tes dan lihat apakah ada yang gagal
  3. Jika satu atau lebih tes gagal, tulis kode untuk mengatasi masalah yang ada
  4. Jalankan lagi tesnya
  5. Jika tes yang dijalankan semua lolos, lanjutkan pekerjaan, jika tidak kembali ke langkah pertama.

Perangkat lunak yang dibangun dengan TDD akan berevolusi seiring dengan penambahan tes baru untuk fitur baru. Perangkat lunak ini akan selalu lolos uji karena fitur baru hanya akan ditambahkan jika ada sebuah tes yang dapat menentukan tingkah lakunya dan karena semua tes dijalankan secara otomatis bersama-sama, maka dapat dipastikan perangkat lunak ini teruji.

PyUnittest

Ada beberapa pustaka untuk melakukan testing yang mempermudah pekerjaan kita dalam mengatur test case. Salah satu modul Python yang paling populer untuk keperluan ini adalah PyUnittest. Pustaka ini biasanya sudah ada saat Python dipasang sehingga kita tak perlu repot memasangnya sendiri.

Dengan penambahan beberapa test case, program kita menjadi:

import unittest

symbols = [('M', 1000), ('CM', 900), ('D', 500), ('CD', 400), ('C', 100), ('XC', 90), ('L', 50), ('XL', 40), ('X', 10), ('IX', 9), ('V', 5), ('IV', 4), ('I', 1)]

def romannumeral(number):
	outstring = ""
	while number > 0:
		for symbol, value in symbols:
			if number - value >= 0:
				outstring += symbol
				number = number - value
				continue
	return outstring
	

class Test(unittest.TestCase):
	def test_9(self):
		self.assertEqual(rommannumeral(9), "IX")
	def test_29(self):
		self.assertEqual(romannumeral(29),"XXIX")
	def test_707(self):
		self.assertEqual(romannumeral(707), "DCCVII")
	def test_1800(self):
		self.assertEqual(romannumeral(1800),"MDCCC")

if __name__ == '__main__':
	number_in = raw_input("Enter a number: ")
	romannumeral(int(number_in))

Program di atas penulis simpan dengan nama roman.py. Pembaca bisa menyimpannya dengan nama apapun.

Kondisi name == 'main' akan bernilai true jika program ini dijalankan lewat terminal, jadi program akan berjalan dengan normal saat kita menggunakan perintah python roman.py.

Tes adalah fungsi-fungsi yang ada didalam sebuah kelas yang diturunkan dari unittest.TestCase dan tes-tes tersebut merupakan salah satu fungsi assert. Pada contoh kode di atas kita menggunakan assertEqual() yang membandingkan nilai yang dihasilkan oleh fungsi romannumeral() dengan nilai yang seharusnya.

Proses pengujian dapat dimulai dengan perintah:

python -m unittest roman

Perhatikan bahwa kita tidak menyertakan akhiran .py.

mas@cp:~/Sandbox$ python -m unittest roman
FFF.
======================================================================
FAIL: test_1800 (roman.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "roman.py", line 24, in test_1800
    self.assertEqual(romannumeral(1800),"MDCCC")
AssertionError: 'MDCXCLXLXIXI' != 'MDCCC'

======================================================================
FAIL: test_29 (roman.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "roman.py", line 20, in test_29
    self.assertEqual(romannumeral(29),"XXIX")
AssertionError: 'XIXVIVI' != 'XXIX'

======================================================================
FAIL: test_707 (roman.Test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "roman.py", line 22, in test_707
    self.assertEqual(romannumeral(707), "DCCVII")
AssertionError: 'DCXCXVII' != 'DCCVII'

----------------------------------------------------------------------
Ran 4 tests in 0.000s

FAILED (failures=3)

Hasil pengujian yang kita dapatkan dari perintah unittest di atas menunukkan bahwa tiga dari empat tes gagal. Ini artinya ada bagian yang salah dalam menentukan angka Romawi. Perintah continue seharusnya adalah break. Jika pembaca memperbaiki kode yang sudah ada maka hasil tes seharusnya berhasil 100%.

mas@cp:~/Sandbox$ python -m unittest roman
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

Fungsi Assert Lain

Pada contoh yang telah dibahas, kita menggunakan assertEqual untuk menguji apakah sebuah tes berhasil atau gagal. Selain opsi ini ada pula fungsi assert lain yang dapat dipilih. Beberapa yang paling populer adalah assertTrue(statement), assertRaises(exception), assertIsInstance(object, class) dan assertAlmostEqual(value1, value2).

Kunjungi dokumentasi unittest untuk daftar fungsi assert yang lebih lengkap https://docs.python.org/2/library/unittest.html.

sumber