Unit test và tầm quan trọng

Tái cấu trúc code thường xuyên sẽ giúp bạn nâng cao tay nghề rất nhiều, vậy điều gì sẽ khiến bạn “ngại” tái cấu trúc code? Sau mỗi lần refactor, bạn có bao giờ nín thở khi deploy lại sản phẩm lên PRODUCTION hay không? Làm thế nào để không phải trải qua cảm giác kinh hoàng ấy? Trong quy trình phát triển phần mềm, có một giai đoạn mà các lập trình có thể tự cứu được “sự nghiệp” của mình mà không phải chờ đợi kết quả test của các tester hay báo cáo vận hành sản phẩm trên PRODUCTION, giai đoạn đó là viết unit test.

Vậy unit test là gì? Và áp dụng nó trong các bài toán giải quyết trong Python như thế nào? Mời các bạn theo dõi trong bài viết tiếp theo của tôi.

1. Khái niệm

Unit là một khái niệm trong hệ thống phần mềm và mỗi unit được định nghĩa là các thành phần độc lập với nhau theo “định cỡ”: Function -> Class -> Module -> Package (với Python).
Unit-test là một thuật ngữ trong kiểm thử phần mềm, được định nghĩa là kiểm thử mức đơn vị. Có nghĩa là đặt các bài test vào các thành phần “đơn vị” (unit) của hệ thống phần mềm.

Chúng ta có thể hiểu unit-test như một black-box-testing với tập dữ liệu đầu vào sau khi chạy qua một đơn vị và tập dữ liệu đầu ra phải khớp với một tập dữ liệu đã được tính toán trước.

2. Người thực hiện nhiệm vụ quan trọng

Thông thường, mỗi lập trình viên sẽ là người sẽ phải chịu trách nhiệm viết unit-test. Vì thế, chúng là công cụ bảo đảm tính “đúng đắn” của mỗi thành phần sản phẩm mà họ làm ra.

Để có thể viết được các test, các lập trình viên sẽ phải hiểu rõ về yêu cầu cần thực hiện trong các task.

3. Khi nào?

Tùy vào đặc thù của từng dự án hoặc yêu cầu tiến độ của dự án mà lập trình viên sẽ thực hiện viết prototype cho unit test hoặc sau khi thực hiện project, nhưng nên thực hiện song song cùng với task để đảm bảo được chất lượng sản phẩm hoàn thành.

4. Một số yêu cầu với unit test.

  • Unit test phải ngắn gọn, dễ hiểu, dễ đọc, có thể sẽ phải có đầy đủ mô tả cho từng nhóm dữ liệu input/output.
  • Mỗi unit test cần phát triển riêng biệt, không nên thiết kế output của hàm này là input của hàm tiếp theo.
  • Khi đặt tên unit test cần đặt tên gợi nhớ hoặc theo quy chuẩn của từng nhóm phát triển để tường minh việc unit test này đang test cho unit nào.
  • Mỗi unit test chỉ nên thực hiện test cho một unit, nếu các unit có về input/output hoặc code thì chấp nhận việc duplicate các unit test.

5. Lợi ích của unit test.

  • Khi có unit test, các lập trình viên có thể tự tin mà refactor code.
  • Khi thực hiện chạy unit test, đôi khi lập trình viên sẽ phát hiện ra lỗi tiềm ẩn mà bình thường không nghĩ tới.
  • Nếu làm việc cộng tác trong team, việc đặt unit test như các rule trong tiến trình CI sẽ ngăn được trường hợp sản phẩm bị lỗi nhưng vẫn được triển khai trên PRODUCTION.

Cách sử dụng unit test trong Python.

Với mỗi ngôn ngữ lập trình lại có các công cụ, thư viện khác nhau để thực hiện viết unit-test. Trong Python, có thể sử dụng pytest và unittest để viết các unit-test. Do unit-test có độ thông dụng nhiều hơn nên bài viết sau đây của tôi sẽ tập trung vào module unittest trong Python.

1. Giới thiệu về unit test.

Trong lập trình thì cách giới thiệu nhanh nhất cho một module/thư viện chính là … lập trình dựa trên các đặc tính của module/thư viên đó. Sau đây là một ví dụ nhỏ về unitest. Các bạn hãy tạo một file có tên test_simple_unittest.py và gõ vào đoạn code như dưới đây.

# test_simple_unittest.py
import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('python'.upper(), 'PYTHON')

    def test_isupper(self):
        self.assertTrue('PYTHON'.isupper())
        self.assertFalse('Python'.isupper())

    def test_islower(self):
        self.assertTrue('PYTHON'.islower())
        self.assertFalse('Python'.islower())

    def test_split(self):
        test_string = 'python is a best language'
        self.assertEqual(test_string.split(),
                        ['python', 'is', 'a', 'best', 'language'])
        # check that test_string.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            test_string.split(2)

if __name__ == '__main__':
    unittest.main(verbosity=2)

Từ màn hình terminal, thực hiện chạy file trên, chúng ta thu được kết quả:

> python .\test_simple_unittest.py
test_islower (__main__.TestStringMethods) ... FAIL
test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok

======================================================================
FAIL: test_islower (__main__.TestStringMethods)
----------------------------------------------------------------------
Traceback (most recent call last):
File ".\test_simple_unittest.py", line 14, in test_islower
self.assertTrue('PYTHON'.islower())
AssertionError: False is not true

----------------------------------------------------------------------
Ran 4 tests in 0.002s

FAILED (failures=1)

Bây giờ chúng ta cùng nhau phân tích một chút về ví dụ trên.

import unittest –> Có nghĩa là module unittest là module đã được cài đặt cùng với gói cài đặt của Python.

class TestStringMethods(unittest.TestCase): –> module unittest cung cấp một class unittest.TestCase để các class khác thực hiện kế thừa.

def test_upper(self):, def test_isupper(self):, def test_split(self): –> các function đều bắt đầu bằng test_

unittest.main(verbosity=2) –> Để khởi chạy các test case trong một module, cần đặt gọi đến unittest.main() của module đó. unittest.main() thường đặt ở cuối cùng của module (file code).

assertEqual, assertTrue, assertFalse, assertRaises –> Các function dùng để so sánh và raise lên các message thông báo cho kết quả test chính chính xác hay không.

Trong trường kết quả test không chính xác, sẽ hiển thị ra bài test không pass được bằng cách trỏ đến dòng code và nguyên nhân gây ra lỗi.
Như ví dụ trên, lỗi nằm ở dòng só 14 khi cố tình đặt self.assertTrue('PYTHON'.islower()), và messagelỗi cũng chỉ ra đúng dòng lỗi là dòng 14, và nôi dung lỗi cũng rõ ràng AssertionError: False is not true

2. Một số function trong unit test thường dùng.

2.1. Các function trong unit-test trả về True/False

assertEqual(value1, value2)
–> Trả về True: Nếu giá trị value1 == value2
–> Trả về False: nếu value1 != value2

assertTrue(value)
–> Với True: Nếu giá trị value == True
–> Trả về False: nếu value1 == False

assertFalse(value)
–> Với True: Nếu giá trị value == False
–> Trả về False: nếu value1 == True


with self.assertRaises(TypeException):
--expressions--

Với True: Nếu trong các expressions phát sinh ra lỗi TypeException
Trả về False: Nếu trong expressions không phát sinh ra lỗi

2.2. Các function khác
MethodChecks that
assertNotEqual(a, b)a != b
assertIs(a, b)a is b
assertIsNot(a, b)a is not b
assertIsNone(x)x is None
assertIsNotNone(x)x is not None
assertIn(a, b)a in b
assertNotIn(a, b)a not in b
assertIsInstance(a, b)isinstance(a, b)
assertNotIsInstance(a, b)
assertAlmostEqual(a, b)round(a-b, 7) == 0
assertNotAlmostEqual(a, b)round(a-b, 7) != 0
assertGreater(a, b)a > b
assertGreaterEqual(a, b)a >= b
assertLess(a, b)a < b
assertLessEqual(a, b)a <= b
assertRegex(s, r)r.search(s)
assertNotRegex(s, r)not r.search(s)
assertCountEqual(a, b)a and b have the same elements in the same number, regardless of their order
2.3. Các function so sánh các kiểu dữ liệu khác nhau.
MethodUsed to compare
assertMultiLineEqual(a, b)strings
assertSequenceEqual(a, b)sequences
assertListEqual(a, b)lists
assertTupleEqual(a, b)tuples
assertSetEqual(a, b)sets or frozensets
assertDictEqual(a, b)dicts

3. Cách chạy unittest.

Ở ví dụ phía trên, ngoài cách gọi trực tiếp vào module/file để thực thi, chúng ta có thể gọi unittest từng đơn vị như sau: Test cả module:
>python -m unittest test_simple_unittest
======================================================================FAIL: test_islower (test_simple_unittest.TestStringMethods)———————————————————————-Traceback (most recent call last):  File “E:\code_learn\Projects\20201206_python-unitest\python-unittest\source_code\test_simple_unittest.py”, line 14, in test_islower    self.assertTrue(‘PYTHON’.islower())AssertionError: False is not true
———————————————————————-Ran 4 tests in 0.001s
FAILED (failures=1)Test từng class/function trong module
python -m unittest test_simple_unittest.TestStringMethods======================================================================FAIL: test_islower (test_simple_unittest.TestStringMethods)———————————————————————-Traceback (most recent call last):  File “E:\code_learn\Projects\20201206_python-unitest\python-unittest\source_code\test_simple_unittest.py”, line 14, in test_islower    self.assertTrue(‘PYTHON’.islower())AssertionError: False is not true
———————————————————————-Ran 4 tests in 0.001s
FAILED (failures=1)Test từng function trong module
python -m unittest test_simple_unittest.TestStringMethods.test_isupper———————————————————————-Ran 1 test in 0.000s
OK
python -m unittest test_simple_unittest.TestStringMethods.test_islower======================================================================FAIL: test_islower (test_simple_unittest.TestStringMethods)———————————————————————-Traceback (most recent call last):  File “E:\code_learn\Projects\20201206_python-unitest\python-unittest\source_code\test_simple_unittest.py”, line 14, in test_islower    self.assertTrue(‘PYTHON’.islower())AssertionError: False is not true
———————————————————————-Ran 1 test in 0.000s
FAILED (failures=1)

Kết

Trên đây là một số vấn đề cơ bản khi thực hiện viết unittest cho lập trình Python. Hẹn gặp lại các bạn vào một bài viết chuyên sâu hơn, phân tích kỹ hơn các chiến thuật viết unittest và unittest-mock.
Source code chương trình được lưu trên github tại link: https://github.com/quangvinh1986/python-unittest.
Cảm ơn các bạn đã theo dõi bài viết.

Ngoài ra, bạn có thể tham khảo thêm tạo dự án React từ đầu tại bài viết này

Trích nguồn bài viết: https://codelearn.io/sharing/lam-quen-voi-unittest-trong-python

Tài liệu tham khảo: https://docs.python.org/3/library/unittest.html.

Leave a Comment