Kursus Python / Menguji fungsi dan integrasi CSV dengan unittest

Pengujian otomatis memeriksa hasil atau exception untuk masukan yang telah ditentukan, lalu melaporkan ketidaksesuaian sebagai kegagalan. Dengan unittest, Anda dapat mengulang pemeriksaan setelah mengubah program tanpa menilai keluaran satu per satu secara manual. Pilih aturan perilaku, batas nilai, dan kondisi gagal yang memang dibutuhkan pengguna.

Menentukan kontrak yang akan dilindungi

Latihan ini menguji fungsi total untuk daftar bilangan bulat nonnegatif. Daftar kosong harus menghasilkan nol; nilai negatif, boolean, dan string harus ditolak dengan ValueError. Total contoh transaksi adalah 14000. Nilai harapan ini ditetapkan dari kebutuhan, bukan dihitung kembali menggunakan rumus yang sama di dalam pengujian.

Mulailah dengan fungsi kecil yang tidak mengakses waktu, jaringan, atau berkas pengguna. Dengan demikian, kegagalan lebih mudah dilacak ke aturan perhitungan. Pengujian untuk fungsi ini tidak membuktikan seluruh aplikasi bebas kesalahan, tetapi melindungi kontrak yang dipilih ketika implementasi diubah.

unittest tersedia dalam pustaka standar Python sehingga latihan dasar tidak memerlukan paket tambahan. Gunakan Python 3.12 atau lebih baru dalam lingkungan virtual proyek. Buat folder latihan kosong agar pencarian pengujian tidak ikut menjalankan file percobaan lain. Nama file pengujian memakai awalan test_ dan metode pengujiannya juga memakai awalan test_.

Menyimpan fungsi dan file pengujian lengkap

Simpan kode fungsi berikut sebagai expense_math.py dengan UTF-8:

def total(amounts):
    if any(type(x) is not int or x < 0 for x in amounts):
        raise ValueError("invalid amount")
    return sum(amounts)

Simpan file berikut sebagai test_expense_math.py di folder yang sama:

import unittest
from expense_math import total

class TotalTests(unittest.TestCase):
    def test_regular(self):
        self.assertEqual(total([3500, 8000, 2500]), 14000)

    def test_empty(self):
        self.assertEqual(total([]), 0)

    def test_invalid(self):
        for values in [[-1], [True], ["8000"]]:
            with self.subTest(values=values):
                with self.assertRaises(ValueError):
                    total(values)

if __name__ == "__main__":
    unittest.main()

Buka terminal pada folder tersebut, pilih interpreter lingkungan virtual, lalu jalankan:

python -m unittest -v

Hasil akhirnya harus menyatakan tiga metode pengujian dijalankan dan menampilkan OK. Pengujian masukan tidak valid memiliki tiga subkasus melalui subTest; jumlah metode tetap tiga. Waktu eksekusi, urutan rincian diagnostik, dan format tampilannya dapat berbeda menurut lingkungan.

assertEqual membandingkan hasil nyata dengan nilai harapan. assertRaises menyatakan bahwa masukan tertentu harus menimbulkan exception yang dipilih; jika fungsi diam-diam menerima nilai tersebut, pengujian gagal. Blok subTest menandai masukan yang sedang diperiksa sehingga hasil kegagalan lebih mudah dibedakan. Mekanisme exception mengikuti try, except, dan raise.

Jika terminal menyatakan Ran 0 tests, pemeriksaan belum membuktikan perilaku fungsi. Periksa folder kerja, ejaan nama file, awalan metode, dan penyimpanan perubahan. Jika muncul kesalahan impor, pastikan kedua file berada berdampingan dan terminal menjalankan perintah dari folder itu. FAIL umumnya menunjukkan harapan tidak terpenuhi, sedangkan ERROR menunjukkan pengujian terganggu exception yang tidak diharapkan.

Membuat kegagalan yang disengaja lalu memulihkan kode

  1. Pada fungsi total, ubah sementara return sum(amounts) menjadi return sum(amounts) + 1. Simpan dan jalankan ulang perintah yang sama. Pengujian hasil normal serta daftar kosong harus gagal karena hasil bergeser satu.
  2. Kembalikan baris tersebut ke bentuk awal. Jalankan ulang sampai tiga pengujian kembali OK.
  3. Hapus sementara kondisi validasi dan baris raise, sehingga fungsi hanya mengembalikan sum(amounts). Jalankan ulang: masukan negatif dan boolean tidak lagi memicu ValueError, sedangkan string dapat memicu TypeError. Hasilnya tidak boleh dianggap berhasil.
  4. Pulihkan seluruh fungsi asli dan jalankan lagi. Tinggalkan file dalam kondisi OK, bukan implementasi rusak yang digunakan untuk percobaan.

Percobaan ini memeriksa apakah pengujian mendeteksi pelanggaran aturan. Jangan mengubah nilai harapan menjadi 14001 hanya untuk menyesuaikan implementasi yang salah. Ketika kebutuhan memang berubah, tentukan dahulu aturan baru, lalu sesuaikan pengujian dan implementasi secara sadar.

Kasus tambahan yang masuk akal adalah satu nilai nol, beberapa nilai nol, dan bilangan besar. Jangan menganggap fungsi menerima semua jenis iterable: contoh melakukan dua lintasan, yaitu validasi dan penjumlahan. Generator sekali pakai dapat habis pada lintasan pertama. Kontrak latihan ini adalah daftar, sehingga dukungan generator memerlukan perubahan dan pengujian tersendiri.

Membedakan unit test dan pemeriksaan integrasi CSV

Unit test di atas memusatkan perhatian pada aturan satu fungsi. Pengujian integrasi menghubungkan beberapa bagian, misalnya eksekusi program melalui command line, pembacaan CSV, penulisan laporan, dan status keluar proses. Fungsi penjumlahan yang benar tidak menjamin nama kolom atau jalur berkas aplikasi juga benar.

Untuk percobaan integrasi berikut, siapkan terlebih dahulu csv_report.py lengkap dari proyek laporan CSV dalam folder latihan yang sama. Program tersebut menerima kolom id,category,amount, menolak ID ganda, menulis report.csv dan rejected.csv, serta keluar dengan status 1 jika ada record ditolak. Jangan menggantinya dengan fungsi total; kontrak kedua program berbeda.

Simpan kode lengkap berikut sebagai check_csv.py, lalu jalankan python check_csv.py tanpa opsi optimasi -O. Pemeriksaan di dalam contoh memakai pernyataan assert yang dapat dihilangkan oleh optimasi tersebut.

import subprocess
import sys
import tempfile
from pathlib import Path
program = Path("csv_report.py").resolve()
with tempfile.TemporaryDirectory() as folder:
    work = Path(folder)
    source = work / "expenses-input.csv"
    source.write_text("id,category,amount\ne1,food,8000\ne1,food,8000\n", encoding="utf-8")
    result = subprocess.run([sys.executable, str(program)], cwd=work, capture_output=True, text=True)
    assert result.returncode == 1
    assert "food,8000" in (work / "report.csv").read_text(encoding="utf-8")
    assert "duplicate id" in (work / "rejected.csv").read_text(encoding="utf-8")
print("CSV integration checks passed")

Hasil yang diharapkan:

CSV integration checks passed

Dua record dengan ID e1 sengaja diberikan. Record pertama diterima, yang kedua ditolak, sehingga total kategori food tetap 8000 dan status keluar program laporan adalah 1. Status 1 itu merupakan kondisi yang diharapkan oleh skrip pemeriksa; bukan berarti skrip pemeriksanya gagal. Skrip pemeriksa sendiri selesai dengan status 0 setelah seluruh assertion terpenuhi.

program diubah menjadi jalur absolut sebelum proses anak dijalankan dengan folder sementara sebagai cwd. Dengan demikian, skrip tetap ditemukan sedangkan masukan dan keluarannya terisolasi dari laporan proyek. TemporaryDirectory membersihkan folder pada akhir blok konteks. Jangan memakai data operasional untuk menguji kegagalan atau menonaktifkan perlindungan penimpaan laporan.

Jika dependensi csv_report.py belum disiapkan, tes integrasi tidak dapat dijalankan dengan benar. Baca diagnostik proses dan periksa file yang diperlukan, lalu ulangi dengan salinan program yang sesuai. Panduan unittest dan tempfile menjelaskan fasilitas pemeriksaan serta pembersihan yang digunakan di sini.