Các bạn có bao giờ tái cấu trúc code không ? 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. Unit-test là gì ?
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 unit 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. Ai là người viết unit-test?
Thông thường thì các lập trình viên là người sẽ phải chịu trách nhiệm viết unit-test. Vì unit-test 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 unit-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 unit.
3. Khi nào thì viết unit-test?
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 trước cùng với các unit có thể xuất hiện trong hệ thống. Sau đó, trong lúc lập trình hoặc thậm chí là sau khi sản phẩm chạy ổn định rồi họ mới thực hiện viết unit-test [lúc này đã có các tập hợp dữ liệu input-output hoàn chỉ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 unit-test này là input của unit-test 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 [lỗi truy vấn đến database chẳng hạn].
- 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ị 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 unittest 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ề unittest.
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 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/FalseassertEqual[value1, value2]
--> Trả về True: Nếu giá trị value1 == value2
--> Trả về False: nếu value1 != value2
assertTrue[value]
--> Trả về True: Nếu giá trị value == True
--> Trả về False: nếu value1 == False
assertFalse[value]
--> Trả về True: Nếu giá trị value == False
--> Trả về False: nếu value1 == True
with self.assertRaises[TypeException]:
--expressions--
Trả về 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
Method | Checks 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 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 > ====================================================================== 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 > ---------------------------------------------------------------------- Ran 1 test in 0.000s OK > ====================================================================== 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] 4. Một số ví dụ về unittestVí dụ 1: Viết chương trình tìm nghiệm của phương trình bậc 1: aX + b = 0 Viết unit test cho chương trình trên Giải thuật: Nếu a == 0 và b == 0 -> phương trình vô số nghiệm, trả về ALL Nếu a == 0 và b != 0 -> Phương trình vô nghiệm, trả về NONE Kết quả: X = -b / a Sau khi giải bài toàn và lưu vào file first_equation.py
Thực hiện viết unittest của function trên.
Thực hiện chạy thử, ta thu được kết quả: OK Ví dụ 2: Cho 1 chuỗi cho trước, thực hiện tách chuỗi theo các khoảng trắng và trả về một list của các tuple dạng [[số thứ tự, giá trị],…] Input: Output: [[1, 'Python'], [2, 'is'], [3, 'a'], [4, 'best'], [5, 'language']]
Viết unit-test cho bài toán:
Kết quả thực hiện: OK Ví dụ 3: Xử lý chuỗi palindrome Dữ liệu mẫu: Input: test_str = ”Civic” Input: test_str = ”Noon”
Code của bài toán:
|