Advertisement
  1. Code
  2. Coding Fundamentals
  3. Testing

Menulis Unit Test Profesional dengan Python

Scroll to top
Read Time: 20 min

() translation by (you can also view the original English article)

Pengujian adalah dasar dari pengembangan perangkat lunak yang solid. Ada banyak jenis testing, tetapi jenis yang paling penting adalah unit testing. Unit testing membermu banyak kepercayaan diri bahwa kamu dapat menggunakan karya yang telah teruji dengan baik sebagai primitif dan bergantung padanya ketika kamu menyusunnya untuk membuat program. Mereka meningkatkan inventaris kode tepercayamu di luar bawaan bahasa dan library standar. Selain itu, Python memberikan dukungan besar untuk penulisan unit test.

Contoh Menjalankan

Sebelum menyelami semua prinsip, heuristik, dan pedoman, mari kita lihat unit test yang representatif sedang beraksi. Class SelfDrivingCar adalah implementasi parsial dari logika mengemudi mobil yang bisa mengemudi sendiri. Ini sebagian besar berkaitan dengan mengendalikan kecepatan mobil. Ia mengetahui benda-benda di depannya, batas kecepatan, dan apakah benda itu sampai di tujuannya atau tidak.

1
class SelfDrivingCar(object):
2
3
    def __init__(self):
4
5
        self.speed = 0
6
7
        self.destination = None
8
9
        
10
11
    def _accelerate(self):
12
13
        self.speed += 1
14
15
        
16
17
    def _decelerate(self):
18
19
        if self.speed > 0:
20
21
            self.speed -= 1
22
23
                    
24
25
    def _advance_to_destination(self):
26
27
        distance = self._calculate_distance_to_object_in_front()
28
29
        if distance < 10:
30
31
            self.stop()
32
33
34
35
        elif distance < self.speed / 2:
36
37
            self._decelerate()
38
39
        elif self.speed < self._get_speed_limit():
40
41
            self._accelerate()
42
43
    
44
45
    def _has_arrived(self):
46
47
        pass
48
49
        
50
51
    def _calculate_distance_to_object_in_front(self):
52
53
        pass
54
55
        
56
57
    def _get_speed_limit(self):
58
59
        pass
60
61
        
62
63
    def stop(self):
64
65
        self.speed = 0
66
67
        
68
69
    def drive(self, destination):
70
71
        self.destination = destination
72
73
        while not self._has_arrived():            
74
75
            self._advance_to_destination()
76
77
78
        self.stop()
79
80
    def __init__(self):
81
82
        self.speed = 0
83
84
        self.destination = None
85
86
        
87
88
    def _accelerate(self):
89
90
        self.speed += 1
91
92
        
93
94
    def _decelerate(self):
95
96
        if self.speed > 0:
97
98
            self.speed -= 1
99
100
                    
101
102
    def _advance_to_destination(self):
103
104
        distance = self._calculate_distance_to_object_in_front()
105
106
        if distance < 10:
107
108
            self.stop()
109
110
111
112
        elif distance < self.speed / 2:
113
114
            self._decelerate()
115
116
        elif self.speed < self._get_speed_limit():
117
118
            self._accelerate()
119
120
    
121
122
    def _has_arrived(self):
123
124
        pass
125
126
        
127
128
    def _calculate_distance_to_object_in_front(self):
129
130
        pass
131
132
        
133
134
    def _get_speed_limit(self):
135
136
        pass
137
138
        
139
140
    def stop(self):
141
142
        self.speed = 0
143
144
        
145
146
    def drive(self, destination):
147
148
        self.destination = destination
149
150
        while not self._has_arrived():            
151
152
            self._advance_to_destination()
153
154
        self.stop()
155

Berikut ini adalah unit test untuk metode stop() untuk membangkitkan seleramu. Saya akan membahas detailnya nanti.

1
from unittest import TestCase
2
3
4
5
class SelfDrivingCarTest(TestCase):
6
7
    def setUp(self):
8
9
        self.car = SelfDrivingCar()
10
11
        
12
13
    def test_stop(self):
14
15
        self.car.speed = 5
16
17
        self.car.stop()
18
19
        # Verify the speed is 0 after stopping

20
21
        self.assertEqual(0, self.car.speed)
22
23
        
24
25
        # Verify it is Ok to stop again if the car is already stopped

26
27
        self.car.stop()
28
29
        self.assertEqual(0, self.car.speed)

Pedoman Unit Testing

Melakukan

Menulis unit test yang baik adalah kerja keras. Menulis unit test membutuhkan waktu. Saat kamu membuat perubahan pada kodmu, biasanya kamu perlu mengubah test juga. Terkadang kamu memiliki bug dalam kode test-mu. Itu berarti kamu harus benar-benar berkomitmen. Manfaatnya sangat besar, bahkan untuk proyek-proyek kecil, tetapi mereka tidak gratis.

Jadilah Disiplin

Kamu harus disiplin. Bersikaplah konsisten. Pastikan test selalu lulus. Jangan biarkan test rusak karena kamu "tahu" kodenya OK.

Mengotomatisasikan

Untuk membantmu disiplin, kamu harus mengotomatisasi unit test-mu. Test harus berjalan secara otomatis pada titik-titik penting seperti pra-commit atau pra-deployment. Idealnya, sistem manajemen kontrol sumbemu menolak kode yang semua test-nya tidak lulus.

Kode Yang Belum Di-test Adalah Rusak Menurut Definisi

Jika kamu tidak mengujinya, kamu tidak bisa mengatakan itu berfungsi. Ini berarti kamu harus menganggapnya rusak. Jika ini kode yang kritis, jangan gunakan untuk produksi.

Latar Belakang

Apa itu Unit?

Unit untuk tujuan unit testing adalah file/modul yang berisi sekumpulan fungsi terkait atau class. Jika kamu memiliki file dengan beberapa kelas, kamu harus menulis unit test untuk setiap class.

Ke TDD atau Tidak ke TDD

Test-driven development adalah praktik di mana kamu menulis test sebelum kamu menulis kode. Ada beberapa manfaat dari pendekatan ini, tetapi saya sarankan untuk menghindarinya jika kamu memiliki disiplin untuk menulis test yang tepat nanti.

Alasannya adalah saya mendesain dengan kode. Saya menulis kode, melihatnya, menulis ulang, melihatnya lagi dan menulis ulang lagi dengan sangat cepat. Tes menulis pertama membatasi saya dan memperlambat saya.

Setelah saya selesai dengan desain awal, saya akan menulis tes segera, sebelum diintegrasikan dengan sisa sistem. Yang mengatakan, ini adalah cara yang bagus untuk memperkenalkan dirmu ke unit test, dan itu memastikan semua kodemu akan memiliki test.

Modul Unittest

Modul unittest dilengkapi dengan library standar Python. Ini memberikan class yang disebut TestCase, yang dapat berasal dari class-mu. Kemudian kamu dapat mengganti metode setUp() untuk menyiapkan perlengkapan test sebelum setiap test dan/atau metode class classSetUp() untuk menyiapkan perlengkapan test untuk semua test (tidak diatur ulang di antara masing-masing test). Ada metode tearDown() dan classTearDown() yang sesuai dengan yang bisa kamu timpa juga.

Berikut adalah bagian yang relevan dari class SelfDrivingCarTest kita. Saya hanya menggunakan metode setUp(). Saya membuat contoh SelfDrivingCar baru dan menyimpannya di self.car sehingga tersedia untuk setiap test.

1
from unittest import TestCase
2
3
4
5
class SelfDrivingCarTest(TestCase):
6
7
    def setUp(self):
8
9
        self.car = SelfDrivingCar()

Langkah selanjutnya adalah menulis metode test khusus untuk menguji kode yang sedang di test — class SelfDrivingCar dalam kasus ini — melakukan apa yang seharusnya dilakukan. Struktur metode test cukup standar:

  • Menyiapkan environment (opsional).
  • Menyiapkan hasil yang diharapkan.
  • Memanggil kode yang sedang di test.
  • Menyatakan bahwa hasil aktual sesuai dengan hasil yang diharapkan.

Perhatikan bahwa hasilnya tidak harus berupa output dari suatu metode. Ini bisa berupa perubahan status class, efek samping seperti menambahkan baris baru dalam database, menulis file atau mengirim email.

Sebagai contoh, metode stop() dari class SelfDrivingCar tidak mengembalikan apa pun, tetapi metode ini mengubah keadaan internal dengan menetapkan kecepatannya ke 0. Metode assertEqual() yang disediakan oleh class dasar TestCase digunakan di sini untuk memverifikasi bahwa pemanggilan stop() berfungsi seperti yang diharapkan.

1
def test_stop(self):
2
3
        self.car.speed = 5
4
5
        self.car.stop()
6
7
        # Verify the speed is 0 after stopping

8
9
        self.assertEqual(0, self.car.speed)
10
11
        
12
13
        # Verify it is Ok to stop again if the car is already stopped

14
15
        self.car.stop()
16
17
        self.assertEqual(0, self.car.speed)

Sebenarnya ada dua test di sini. Tes pertama adalah memastikan bahwa jika kecepatan mobil 5 dan stop() dipanggil, maka kecepatannya menjadi 0. Kemudian, test lain adalah untuk memastikan tidak ada yang salah jika memanggil stop() lagi ketika mobil sudah berhenti.

Nanti, saya akan memperkenalkan beberapa test lagi untuk fungsionalitas tambahan.

Modul Doctest

Modul doctest cukup menarik. Ini memungkinkamu menggunakan contoh kode interaktif dalam docstring-mu dan memverifikasi hasilnya, termasuk Ekspesi yang diajukan.

Saya tidak menggunakan atau merekomendasikan doctest untuk sistem skala besar. Unit testing yang tepat membutuhkan banyak pekerjaan. Kode test biasanya jauh lebih besar dari kode yang diuji. Docstring bukan media yang tepat untuk menulis tes yang komprehensif. Tapi itu keren. Berikut adalah fungsi faktorial dengan dokumen test:

1
import math
2
3
4
5
def factorial(n):
6
7
    """Return the factorial of n, an exact integer >= 0.

8


9


10


11
    If the result is small enough to fit in an int, return an int.

12


13
    Else return a long.

14


15


16


17
    >>> [factorial(n) for n in range(6)]

18


19
    [1, 1, 2, 6, 24, 120]

20


21
    >>> [factorial(long(n)) for n in range(6)]

22


23
    [1, 1, 2, 6, 24, 120]

24


25
    >>> factorial(30)

26


27
    265252859812191058636308480000000L

28


29
    >>> factorial(30L)

30


31
    265252859812191058636308480000000L

32


33
    >>> factorial(-1)

34


35
    Traceback (most recent call last):

36


37
        ...

38


39
    ValueError: n must be >= 0

40


41


42


43
    Factorials of floats are OK, but the float must be an exact integer:

44


45
    >>> factorial(30.1)

46


47
    Traceback (most recent call last):

48


49
        ...

50


51
    ValueError: n must be exact integer

52


53
    >>> factorial(30.0)

54


55
    265252859812191058636308480000000L

56


57


58


59
    It must also not be ridiculously large:

60


61
    >>> factorial(1e100)

62


63
    Traceback (most recent call last):

64


65
        ...

66


67
    OverflowError: n too large

68


69
    """
70
71
    if not n >= 0:
72
73
        raise ValueError("n must be >= 0")
74
75
    if math.floor(n) != n:
76
77
        raise ValueError("n must be exact integer")
78
79
    if n+1 == n:  # catch a value like 1e300

80
81
        raise OverflowError("n too large")
82
83
    result = 1
84
85
    factor = 2
86
87
    while factor <= n:
88
89
        result *= factor
90
91
        factor += 1
92
93
    return result
94
95
96
97
98
99
if __name__ == "__main__":
100
101
    import doctest
102
103
    doctest.testmod()

Seperti yang kamu lihat, docstring jauh lebih besar daripada kode fungsinya. Itu tidak mendukung keterbacaan.

Menjalankan Test

Oke. Kamu menulis unit test-mu. Untuk sistem yang besar, kamu akan memiliki puluhan/ratusan/ribuan modul dan class di berbagai direktori. Bagaimana kamu menjalankan semua tes ini?

Modul unittest menyediakan berbagai fasilitas untuk mengelompokkan tes dan menjalankannya secara terprogram. Lihat Loading and Running Tests. Tetapi cara yang termudah adalah pendeteksi test. Opsi ini hanya ditambahkan dalam Python 2.7. Pra-2.7 Anda bisa menggunakan nose untuk menemukan dan menjalankan test. Nose memiliki beberapa keunggulan lain seperti menjalankan fungsi pengujian tanpa harus membuat class untuk kasus test-mu. Tetapi untuk tujuan artikel ini, mari tetap bersatu.

Untuk menemukan dan menjalankan tesy berbasis unittes-mu, cukup ketik pada command-line:

python -m unittest discover

unittest akan memindai semua file dan sub-direktori, menjalankan test apa pun yang ditemukannya, dan memberikan laporan yang bagus serta runtime. Jika kamu ingin melihat test apa yang sedang berjalan, kamu dapat menambahkan flag -v:

python -m unittest discover -v

Ada beberapa bendera yang mengendalikan operasi:

1
python -m unittest -h
2
3
Usage: python -m unittest [options] [tests]
4
5
6
7
Options:
8
9
  -h, --help       Show this message
10
11
  -v, --verbose    Verbose output
12
13
  -q, --quiet      Minimal output
14
15
  -f, --failfast   Stop on first failure
16
17
  -c, --catch      Catch control-C and display results
18
19
  -b, --buffer     Buffer stdout and stderr during test runs
20
21
22
23
Examples:
24
25
  python -m unittest test_module               - run tests from test_module
26
27
  python -m unittest module.TestClass          - run tests from module.TestClass
28
29
  python -m unittest module.Class.test_method  - run specified test method
30
31
32
33
[tests] can be a list of any number of test modules, classes and test
34
35
methods.
36
37
38
39
Alternative Usage: python -m unittest discover [options]
40
41
42
43
Options:
44
45
  -v, --verbose    Verbose output
46
47
  -f, --failfast   Stop on first failure
48
49
  -c, --catch      Catch control-C and display results
50
51
  -b, --buffer     Buffer stdout and stderr during test runs
52
53
  -s directory     Directory to start discovery ('.' default)
54
55
  -p pattern       Pattern to match test files ('test*.py' default)
56
57
  -t directory     Top level directory of project (default to
58
59
                   start directory)
60
61
62
63
For test discovery all test modules must be importable from the top
64
65
level directory of the project.

Cakupan Test

Cakupan test adalah bidang yang sering diabaikan. Cakupan berarti seberapa banyak kodemu yang benar-benar diuji oleh test-mu. Misalnya, jika kamu memiliki fungsi dengan pernyataan if-else dan kamu hanya menguji cabang if, maka kamu tidak tahu apakah cabang else berfungsi atau tidak. Dalam contoh kode berikut, fungsi add() memeriksa jenis argumennya. Jika keduanya bilangan bulat, ia hanya akan menambahkannya.

Jika keduanya adalah string, ia mencoba untuk mengubahnya menjadi bilangan bulat dan menambahkannya. Kalau tidak, itu menimbulkan eksepsi. Fungsi test_add() menguji fungsi add() dengan argumen yang merupakan bilangan bulat dan dengan argumen yang float dan memverifikasi perilaku yang benar dalam setiap kasusnya. Tetapi cakupan test-nya tidak lengkap. Kasus argumen string tidak diuji. Hasilnya, test berhasil, tetapi kesalahan ketik di cabang tempat argumen keduanya tidak ditemukan (lihat 'intg' di sana?).

1
import unittest
2
3
4
5
def add(a, b):
6
7
    """This function adds two numbers a, b and returns their sum

8


9


10


11
    a and b may integers

12


13
    """
14
15
    if isinstance(a, int) and isinstance(b, int):
16
17
        return a + b
18
19
    elseif isinstance(a, str) and isinstance(b, str):
20
21
        return int(a) + intg(b)
22
23
    else:
24
25
        raise Exception('Invalid arguments')
26
27
28
29
class Test(unittest.TestCase):
30
31
    def test_add(self):
32
33
        self.assertEqual(5, add(2, 3))
34
35
        self.assertEqual(15, add(-6, 21))
36
37
        self.assertRaises(Exception, add, 4.0, 5.0)
38
39
40
41
unittest.main()       

Berikut hasilnya:

1
----------------------------------------------------------------------
2
3
Ran 1 test in 0.000s
4
5
6
7
OK
8
9
10
11
Process finished with exit code 0

Unit Test Praktis

Menulis unit test pada kebutuhan industri tidak mudah atau sederhana. Ada beberapa hal yang perlu dipertimbangkan dan pertukaran yang harus dilakukan.

Desain untuk Testabilitas

Jika kodemu adalah apa yang disebut kode spaghetti formal atau bola lumpur besar di mana berbagai tingkat abstraksi dicampur bersama dan setiap bagian kode bergantung pada setiap bagian kode lainnya, kamu akan kesulitan mengujinya. Juga, setiap kali kamu mengubah sesuatu, kamu harus memperbarui banyak test juga.

Kabar baiknya adalah bahwa desain perangkat lunak yang tepat untuk keperluan umum adalah apa yang kamu butuhkan untuk testabilitas. Secara khusus, kode modular berfaktor dengan baik, di mana setiap komponen memiliki tanggung jawab yang jelas dan berinteraksi dengan komponen lain melalui antarmuka yang terdefinisi dengan baik, akan membuat penulisan unit yang baik menjadi menyenangkan.

Misalnya, class SelfDrivingCar kita bertanggung jawab untuk pengoperasian mobil tingkat tinggi: pergi, berhenti, navigasi. Ini memiliki metode calcul_distance_to_object_in_front() yang belum diimplementasikan. Fungsi ini mungkin harus diimplementasikan oleh sub-sistem yang benar-benar terpisah. Ini mungkin termasuk membaca data dari berbagai sensor, berinteraksi dengan mobil self-driving lainnya, setumpuk visi alat berat untuk menganalisis gambar dari beberapa kamera.

Mari kita lihat bagaimana ini bekerja dalam praktiknya. SelfDrivingCar akan menerima argumen yang disebut object_detector yang memiliki metode yang disebut calcul_distance_to_object_in_front(), dan itu akan mendelegasikan fungsi ini ke objek ini. Sekarang, tidak perlu untuk melakukan unit test ini karena object_detector bertanggung jawab (dan harus diuji) untuk itu. Kamu masih ingin melakukan unit test fakta bahwa kamu menggunakan object_detector dengan benar.

1
class SelfDrivingCar(object):
2
3
    def __init__(self, object_detector):
4
5
        self.object_detector
6
7
        self.speed = 0
8
9
        self.destination = None
10
11
                
12
13
    def _calculate_distance_to_object_in_front(self):
14
15
        return self.object_detector.calculate_distance_to_object_in_front()

Biaya/Manfaat

Jumlah upaya yang kamu lakukan dalam pengujian harus dikorelasikan dengan biaya kegagalan, seberapa stabil kodenya, dan betapa mudahnya memperbaikinya jika masalah terdeteksi di telepon.

Sebagai contoh, class mobil self-driving kita sangat kritis. Jika metode stop() tidak berfungsi dengan baik, mobil self-driving kita mungkin membunuh orang, menghancurkan properti, dan menggagalkan seluruh pasar mobil self-driving. Jika kamu mengembangkan mobil self-driving, saya curiga unit test-mu untuk metode stop() akan sedikit lebih teliti daripada saya.

Di sisi lain, jika satu tombol di aplikasi we-mu pada halaman yang terkubur tiga tingkat di bawah halaman utammu berkedip sedikit ketika seseorang mengkliknya, kamu dapat memperbaikinya, tetapi mungkin tidak akan menambahkan unit test khusus untuk kasus ini. Ekonomi tidak membenarkannya.

Menguji Pola Pikir

Menguji pola pikir itu penting. Satu prinsip yang saya gunakan adalah bahwa setiap bagian dari kode memiliki setidaknya dua pengguna: kode lain yang menggunakannya dan test yang mengujinya. Aturan sederhana ini banyak membantu dengan desain dan dependensi. Jika kamu ingat bahwa kamu harus menulis test untuk kodemu, kamu tidak akan menambahkan banyak dependensi yang sulit untuk direkonstruksi selama testing.

Sebagai contoh, anggaplah kodemu perlu menghitung sesuatu. Untuk melakukan itu, perlu memuat beberapa data dari database, membaca file konfigurasi, dan berkonsultasi secara dinamis dengan beberapa REST API untuk informasi terbaru. Ini semua mungkin diperlukan karena berbagai alasan, tetapi memasukkan semua itu ke dalam satu fungsi akan membuatnya cukup sulit untuk unit test. Masih mungkin dengan mocking, tetapi jauh lebih baik untuk menyusun kodemu dengan benar.

Fungsi Murni

Kode termudah untuk di-test adalah fungsi murni. Fungsi murni adalah fungsi yang hanya mengakses nilai parameternya, tidak memiliki efek samping, dan mengembalikan hasil yang sama setiap kali dipanggil dengan argumen yang sama. Mereka tidak mengubah status programu, tidak mengakses sistem file atau jaringan. Manfaatnya terlalu banyak untuk dihitung di sini.

Mengapa mereka mudah diuji? Karena tidak perlu mengatur environment khusus untuk mengujinya. Kamu hanya melewati argumen dan menguji hasilnya. Kamu juga tahu bahwa selama kode yang diuji tidak berubah, test-mu tidak harus berubah.

Bandingkan dengan fungsi yang membaca file konfigurasi XML. Test-mu harus membuat file XML dan meneruskan nama file ke kode yang diuji. Bukan masalah besar. Tapi anggaplah seseorang memutuskan bahwa XML itu menjijikkan dan semua file konfigurasi harus dalam JSON. Mereka menjalankan bisnis mereka dan mengonversi semua file konfigurasi ke JSON. Mereka menjalankan semua test termasuk test-mu dan semuanya lulus!

Mengapa? Karena kodenya tidak berubah. Itu masih mengharapkan file konfigurasi XML, dan test-mu masih membangun file XML untuk itu. Tetapi dalam produksi, kodemu akan mendapatkan file JSON, yang gagal diurai.

Menguji Penanganan Kesalahan

Penanganan kesalahan adalah hal lain yang sangat penting untuk diuji. Itu juga bagian dari desain. Siapa yang bertanggung jawab atas kebenaran inputan? Setiap fungsi dan metode harus jelas tentangnya. Jika itu adalah tanggung jawab fungsi, ia harus memverifikasi inputnya, tetapi jika itu adalah tanggung jawab penelepon maka fungsi tersebut dapat menjalankan bisnisnya dan menganggap inputnya benar. Keseluruhan kebenaran sistem akan dipastikan dengan melakukan test untuk pemanggilnya agar memverifikasi bahwa itu hanya melewati inputan yang benar ke fungsimu.

Biasanya, kamu ingin memverifikasi input pada antarmuka publik ke kodmu karena kamu belum tentu tahu siapa yang akan memanggil kodemu. Mari kita lihat metode drive() dari mobil self-driving. Metode ini mengharapkan parameter "destination". Parameter "destination" akan digunakan nanti dalam navigasi, tetapi metode drive tidak melakukan apa pun untuk memverifikasi itu benar.

Mari kita asumsikan bahwa tujuan seharusnya adalah tuple dari garis lintang dan bujur. Ada semua jenis test yang dapat dilakukan untuk memverifikasinya valid (semisal. apakah tujuan di tengah laut). Untuk tujuan kita, mari kita pastikan itu adalah tuple float di kisaran 0,0 hingga 90,0 untuk garis lintang dan -180,0 hingga 180,0 untuk garis bujur.

Ini adalah class SelfDrivingCar yang diperbarui. Saya dengan sepele menerapkan beberapa metode yang tidak diimplementasikan karena metode drive() memanggil beberapa metode ini secara langsung atau tidak langsung.

1
class SelfDrivingCar(object):
2
3
    def __init__(self, object_detector):
4
5
        self.object_detector = object_detector
6
7
        self.speed = 0
8
9
        self.destination = None
10
11
12
13
    def _accelerate(self):
14
15
        self.speed += 1
16
17
18
19
    def _decelerate(self):
20
21
        if self.speed > 0:
22
23
            self.speed -= 1
24
25
26
27
    def _advance_to_destination(self):
28
29
        distance = self._calculate_distance_to_object_in_front()
30
31
        if distance < 10:
32
33
            self.stop()
34
35
36
37
        elif distance < self.speed / 2:
38
39
            self._decelerate()
40
41
        elif self.speed < self._get_speed_limit():
42
43
            self._accelerate()
44
45
46
47
    def _has_arrived(self):
48
49
        return True
50
51
52
53
    def _calculate_distance_to_object_in_front(self):
54
55
        return self.object_detector.calculate_distance_to_object_in_front()
56
57
58
59
    def _get_speed_limit(self):
60
61
        return 65
62
63
64
65
    def stop(self):
66
67
        self.speed = 0
68
69
70
71
    def drive(self, destination):
72
73
        self.destination = destination
74
75
        while not self._has_arrived():
76
77
            self._advance_to_destination()
78
79
        self.stop()

Untuk menguji penanganan kesalahan dalam pengujian, saya akan memberikan argumen yang tidak valid dan memverifikasi bahwa mereka ditolak dengan benar. Kamu dapat melakukan ini dengan menggunakan self.assertRaises() metode unittest.TestCase. Metode ini berhasil jika kode yang diuji memang memunculkan eksepsi.

Mari kita lihat dalam aksi. Metode test_drive() melewati lintang dan bujur di luar rentang yang valid dan mengharapkan metode drive() untuk meningkatkan eksepsi.

1
from unittest import TestCase
2
3
from self_driving_car import SelfDrivingCar
4
5
6
7
8
9
class MockObjectDetector(object):
10
11
    def calculate_distance_to_object_in_front(self):
12
13
        return 20
14
15
16
17
18
19
class SelfDrivingCarTest(TestCase):
20
21
    def setUp(self):
22
23
        self.car = SelfDrivingCar(MockObjectDetector())
24
25
26
27
    def test_stop(self):
28
29
        self.car.speed = 5
30
31
        self.car.stop()
32
33
        # Verify the speed is 0 after stopping

34
35
        self.assertEqual(0, self.car.speed)
36
37
38
39
        # Verify it is Ok to stop again if the car is already stopped

40
41
        self.car.stop()
42
43
        self.assertEqual(0, self.car.speed)
44
45
46
47
    def test_drive(self):
48
49
        # Valid destination

50
51
        self.car.drive((55.0, 66.0))
52
53
54
55
        # Invalid destination wrong range

56
57
        self.assertRaises(Exception, self.car.drive, (-55.0, 200.0))

Test gagal, karena metode drive() tidak memeriksa argumennya untuk validitas dan tidak memunculkan eksepsi. Kamu mendapatkan laporan yang bagus dengan informasi lengkap tentang apa yang gagal, di mana dan mengapa.

1
python -m unittest discover -v
2
3
test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ... FAIL
4
5
test_stop (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok
6
7
8
9
======================================================================
10
11
FAIL: test_drive (untitled.test_self_driving_car.SelfDrivingCarTest)
12
13
----------------------------------------------------------------------
14
15
Traceback (most recent call last):
16
17
  File "/Users/gigi/PycharmProjects/untitled/test_self_driving_car.py", line 29, in test_drive
18
19
    self.assertRaises(Exception, self.car.drive, (-55.0, 200.0))
20
21
AssertionError: Exception not raised
22
23
24
25
----------------------------------------------------------------------
26
27
Ran 2 tests in 0.000s
28
29
30
31
FAILED (failures=1)

Untuk memperbaikinya mari kita perbarui metode drive() untuk benar-benar memeriksa rentang argumennya:

1
def drive(self, destination):
2
3
        lat, lon = destination
4
5
        if not (0.0 <= lat <= 90.0):
6
7
            raise Exception('Latitude out of range')
8
9
        if not (-180.0 <= lon <= 180.0):
10
11
            raise Exception('Latitude out of range')
12
13
        
14
15
        self.destination = destination
16
17
        while not self._has_arrived():
18
19
            self._advance_to_destination()
20
21
        self.stop()

Sekarang, semua test lulus.

1
python -m unittest discover -v
2
3
test_drive (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok
4
5
test_stop (untitled.test_self_driving_car.SelfDrivingCarTest) ... ok
6
7
8
9
----------------------------------------------------------------------
10
11
Ran 2 tests in 0.000s
12
13
14
15
OK
16

Menguji Metode Private

Haruskah kamu menguji setiap fungsi dan metode? Secara khusus, haruskah kamu menguji metode private yang hanya dipanggil oleh kodemu? Jawaban yang biasanya tidak memuaskan adalah: "Itu tergantung".

Saya akan mencoba untuk menjadi berguna di sini dan memberi tahumu apa itu tergantung. Kamu tahu persis siapa yang memanggil metode pribadimu — itu kodemu sendiri. Jika test-mu untuk metode public yang memanggil metode private-mu komprehensif maka kamu sudah menguji metode private-mu secara mendalam. Tetapi jika metode pribadi sangat rumit, kamu mungkin ingin mengujinya secara mandiri. Gunakan penilaiamu.

Cara Mengatur Unit Test-mu

Dalam sistem yang besar, tidak selalu jelas bagaimana mengatur test-mu. Haruskah kamu memiliki satu file besar dengan semua test untuk satu paket, atau satu file test untuk setiap class? Apakah test harus dalam file yang sama dengan kode yang diuji, atau di direktori yang sama?

Inilah sistem yang saya gunakan. Test harus sepenuhnya terpisah dari kode yang sedang diuji (karenanya saya tidak menggunakan doctest) Idealnya, kodmu harus dalam satu paket. Test untuk setiap paket harus di direktori saudara paketmu. Di direktori test, harus ada satu file untuk setiap modul paketmu bernama test_<module_name>.

Misalnya, jika kamu memiliki tiga modul dalam pakemu: module_1.py, module_2.py dan module_3.py, kamu harus memiliki tiga file test: test_module_1.py, test_module_2.py dan test_module_3.py di bawah direktori test.

Konvensi ini memiliki beberapa keunggulan. Itu membuatnya jelas hanya dengan menelusuri direktori bahwa kamu tidak lupa untuk menguji beberapa modul sepenuhnya. Ini juga membantu untuk mengatur test dalam potongan ukuran yang wajar. Dengan asumsi bahwa modulmu berukuran cukup maka kode test untuk setiap modul akan berada di file sendiri, yang mungkin sedikit lebih besar dari modul yang diuji, tetapi masih sesuatu yang cocok dengan nyaman dalam satu file.

Kesimpulan

Unit Test adalah dasar dari kode yang solid. Dalam tutorial ini, saya mengeksplorasi beberapa prinsip dan pedoman untuk unit testing dan menjelaskan alasan di balik beberapa praktik terbaik. Semakin besar sistem yang kamu bangun, menjadi semakin penting unit test. Tetapo unit test tidak cukup. Jenis tes lain juga diperlukan untuk sistem dengan skala besar:  integration tests, performance tests, load tests, penetration tests, acceptance tests,, dll.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.