Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Phương thức Stubs trong Python
Trong Python, mô-đun unittest.mock
cung cấp các công cụ mạnh mẽ để tạo và quản lý các đối tượng giả (mock objects) cũng như các giá trị giả (stubs). Việc sử dụng các đối tượng mock giúp tách biệt các phần của chương trình, cho phép kiểm thử từng phần một cách độc lập và hiệu quả. Trong bài viết này, mình sẽ tìm hiểu cách sử dụng patch() từ mô-đun unittest.mock để thay thế tạm thời một mục tiêu bằng một đối tượng mock. Qua đó, bạn sẽ hiểu rõ hơn về cách thức hoạt động của các đối tượng mock và cách áp dụng chúng trong kiểm thử đơn vị (unit testing) để tạo ra các bài kiểm thử đáng tin cậy và dễ duy trì.
Giới thiệu về các stub trong Python
Stub là các đối tượng giả lập trả về các giá trị được mã hóa cứng (hard-coded). Mục đích chính của stub là chuẩn bị một trạng thái cụ thể của hệ thống cần kiểm thử.
Stub hữu ích vì chúng luôn trả về kết quả nhất quán, giúp việc viết kiểm thử dễ dàng hơn. Ngoài ra, bạn có thể chạy kiểm thử ngay cả khi các thành phần mà stub đại diện chưa hoạt động.
Giả sử bạn cần phát triển một hệ thống báo động giám sát nhiệt độ của một phòng, chẳng hạn như phòng máy chủ.
Bài viết này được đăng tại [free tuts .net]
Để làm điều này, bạn cần cài đặt một thiết bị cảm biến nhiệt độ và sử dụng dữ liệu từ cảm biến đó để cảnh báo nếu nhiệt độ vượt quá một mức nhất định.
Trước tiên, định nghĩa một lớp Sensor trong mô-đun sensor.py:
import random class Sensor: @property def temperature(self): return random.randint(10, 45)
Lớp Sensor có thuộc tính temperature trả về nhiệt độ ngẫu nhiên từ 10 đến 45. Trong thực tế, lớp Sensor cần kết nối với thiết bị cảm biến để lấy nhiệt độ thực tế.
Tiếp theo, định nghĩa lớp Alarm sử dụng đối tượng Sensor:
from sensor import Sensor class Alarm: def __init__(self, sensor=None) -> None: self._low = 18 self._high = 24 self._sensor = sensor or Sensor() self._is_on = False def check(self): temperature = self._sensor.temperature if temperature < self._low or temperature > self._high: self._is_on = True @property def is_on(self): return self._is_on
Mặc định, thuộc tính is_on
của Alarm là tắt (False). Phương thức check()
sẽ bật báo động nếu nhiệt độ thấp hơn 18 hoặc cao hơn 24.
Vì phương thức temperature()
của Sensor trả về nhiệt độ ngẫu nhiên, sẽ khó khăn để kiểm thử các kịch bản khác nhau để đảm bảo lớp Alarm hoạt động đúng.
Để giải quyết vấn đề này, bạn có thể định nghĩa một stub cho lớp Sensor gọi là TestSensor. Lớp TestSensor có thuộc tính temperature trả về giá trị được cung cấp khi khởi tạo đối tượng của nó.
Tiếp theo, định nghĩa lớp TestSensor trong mô-đun test_sensor.py:
class TestSensor: def __init__(self, temperature) -> None: self._temperature = temperature @property def temperature(self): return self._temperature
Lớp TestSensor giống với lớp Sensor ngoại trừ thuộc tính temperature trả về giá trị được chỉ định trong constructor.
Tiếp theo, định nghĩa một lớp TestAlarm trong mô-đun kiểm thử test_alarm.py và nhập Alarm cùng TestSensor từ các mô-đun alarm.py và sensor.py:
import unittest from alarm import Alarm from test_sensor import TestSensor class TestAlarm(unittest.TestCase): pass
Kiểm thử đầu tiên: kiểm tra xem báo động có tắt theo mặc định không:
import unittest from alarm import Alarm from test_sensor import TestSensor class TestAlarm(unittest.TestCase): def test_is_alarm_off_by_default(self): alarm = Alarm() self.assertFalse(alarm.is_on)
Trong test_is_alarm_off_by_default
, chúng ta tạo một đối tượng Alarm mới và sử dụng phương thức assertFalse()
để kiểm tra xem thuộc tính is_on
của đối tượng alarm có phải là False không.
Chạy kiểm thử:
python -m unittest -v
Output:
test_is_alarm_off_by_default (test_alarm.TestAlarm) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Kiểm thử thứ hai:
kiểm tra phương thức check()
của lớp Alarm khi nhiệt độ quá cao:
import unittest from alarm import Alarm from test_sensor import TestSensor class TestAlarm(unittest.TestCase): def test_is_alarm_off_by_default(self): alarm = Alarm() self.assertFalse(alarm.is_on) def test_check_temperature_too_high(self): alarm = Alarm(TestSensor(25)) alarm.check() self.assertTrue(alarm.is_on)
Trong phương thức test_check_temperature_too_high()
:
- Tạo một instance của TestSensor với nhiệt độ là 25 và truyền nó vào constructor của Alarm.
- Gọi phương thức
check()
của đối tượng alarm. - Sử dụng
assertTrue()
để kiểm tra xem thuộc tínhis_on
của alarm có phải là True không.
Chạy kiểm thử:
python -m unittest -v
Output:
test_check_temperature_too_high (test_alarm.TestAlarm) ... ok test_is_alarm_off_by_default (test_alarm.TestAlarm) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK
Báo động bật vì nhiệt độ cao hơn 24.
Kiểm thử thứ ba:
kiểm tra phương thức check()
của lớp Alarm khi nhiệt độ quá thấp:
import unittest from alarm import Alarm from test_sensor import TestSensor class TestAlarm(unittest.TestCase): def test_is_alarm_off_by_default(self): alarm = Alarm() self.assertFalse(alarm.is_on) def test_check_temperature_too_high(self): alarm = Alarm(TestSensor(25)) alarm.check() self.assertTrue(alarm.is_on) def test_check_temperature_too_low(self): alarm = Alarm(TestSensor(17)) alarm.check() self.assertTrue(alarm.is_on)
rong phương thức test_check_temperature_too_low()
:
- Tạo một instance của TestSensor với nhiệt độ là 17 và truyền nó vào constructor của Alarm.
- Gọi phương thức
check()
của đối tượng alarm. - Sử dụng
assertTrue()
để kiểm tra xem thuộc tínhis_on
của alarm có phải là True không.
Chạy kiểm thử:
python -m unittest -v
Output:
test_check_temperature_too_high (test_alarm.TestAlarm) ... ok test_check_temperature_too_low (test_alarm.TestAlarm) ... ok test_is_alarm_off_by_default (test_alarm.TestAlarm) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK
Kiểm thử thứ tư
kiểm tra phương thức check()
của lớp Alarm khi nhiệt độ trong khoảng an toàn (18, 24):
import unittest from alarm import Alarm from test_sensor import TestSensor class TestAlarm(unittest.TestCase): def test_is_alarm_off_by_default(self): alarm = Alarm() self.assertFalse(alarm.is_on) def test_check_temperature_too_high(self): alarm = Alarm(TestSensor(25)) alarm.check() self.assertTrue(alarm.is_on) def test_check_temperature_too_low(self): alarm = Alarm(TestSensor(15)) alarm.check() self.assertTrue(alarm.is_on) def test_check_normal_temperature(self): alarm = Alarm(TestSensor(20)) alarm.check() self.assertFalse(alarm.is_on)
Trong phương thức test_check_normal_temperature()
:
- Tạo một TestSensor với nhiệt độ 20 và truyền nó vào constructor của Alarm.
- Gọi phương thức
check()
của đối tượng alarm. - Sử dụng
assertFalse()
để kiểm tra xem thuộc tínhis_on
của alarm có phải là False không.
Chạy kiểm thử:
python -m unittest -v
Output:
test_check_normal_temperature (test_alarm.TestAlarm) ... ok test_check_temperature_too_high (test_alarm.TestAlarm) ... ok test_check_temperature_too_low (test_alarm.TestAlarm) ... ok test_is_alarm_off_by_default (test_alarm.TestAlarm) ... ok ---------------------------------------------------------------------- Ran 4 tests in 0.001s OK
Sử dụng lớp MagicMock để tạo stub trong Python
Python cung cấp đối tượng MagicMock trong mô-đun unittest.mock
cho phép bạn tạo các stub dễ dàng hơn.
Để tạo một stub cho lớp Sensor sử dụng lớp MagicMock, bạn chỉ cần truyền lớp Sensor vào constructor của MagicMock:
from unittest.mock import MagicMock mock_sensor = MagicMock(Sensor)
Đối tượng mock_sensor
là một instance mới của lớp MagicMock giả lập lớp Sensor.
Sử dụng đối tượng mock_sensor
, bạn có thể thiết lập thuộc tính hoặc gọi một phương thức. Ví dụ, bạn có thể gán nhiệt độ cụ thể, chẳng hạn 25, cho thuộc tính temperature của mock_sensor như sau:
mock_sensor.temperature = 25
Ví dụ sau đây cho thấy phiên bản mới của TestAlarm sử dụng lớp MagicMock:
import unittest from unittest.mock import MagicMock from alarm import Alarm from sensor import Sensor class TestAlarm(unittest.TestCase): def setUp(self): self.mock_sensor = MagicMock(Sensor) self.alarm = Alarm(self.mock_sensor) def test_is_alarm_off_by_default(self): alarm = Alarm() self.assertFalse(alarm.is_on) def test_check_temperature_too_high(self): self.mock_sensor.temperature = 25 self.alarm.check() self.assertTrue(self.alarm.is_on) def test_check_temperature_too_low(self): self.mock_sensor.temperature = 15 self.alarm.check() self.assertTrue(self.alarm.is_on) def test_check_normal_temperature(self): self.mock_sensor.temperature = 20 self.alarm.check() self.assertFalse(self.alarm.is_on)
Sử dụng phương thức patch() trong Python
Để dễ dàng hơn khi làm việc với MagicMock, bạn có thể sử dụng patch()
như một decorator. Ví dụ:
import unittest from unittest.mock import patch from alarm import Alarm class TestAlarm(unittest.TestCase): @patch('sensor.Sensor') def test_check_temperature_too_low(self, sensor): sensor.temperature = 10 alarm = Alarm(sensor) alarm.check() self.assertTrue(alarm.is_on)
Trong ví dụ này, chúng ta sử dụng decorator @patch
trên phương thức test_check_temperature_too_low()
. Trong decorator, chúng ta truyền sensor.Sensor
làm mục tiêu để patch.
Khi sử dụng decorator @patch
, phương thức kiểm thử sẽ có tham số thứ hai là một instance của MagicMock giả lập lớp sensor.Sensor
.
Bên trong phương thức kiểm thử, chúng ta thiết lập thuộc tính temperature của sensor là 10, tạo một instance mới của lớp Alarm, gọi phương thức check()
, và sử dụng assertTrue()
để kiểm tra xem báo động có bật không.
Chạy kiểm thử:
python -m unittest -v
Output:
test_check_temperature_too_low (test_alarm_with_patch.TestAlarm) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
Kết bài
Việc sử dụng stub để trả về các giá trị được mã hóa cứng là một kỹ thuật hữu ích trong kiểm thử, giúp tạo ra các kịch bản kiểm thử nhất quán và dễ quản lý. Python cung cấp lớp MagicMock trong mô-đun unittest.mock
để tạo ra các stub một cách linh hoạt và mạnh mẽ, giúp giả lập các hành vi phức tạp của đối tượng trong quá trình kiểm thử. Hơn nữa, việc sử dụng patch()
làm cho quá trình tạo MagicMock trở nên dễ dàng hơn, giúp bạn nhanh chóng thay thế các thành phần của chương trình bằng các đối tượng giả mà không làm thay đổi mã nguồn chính.
Thông qua các kỹ thuật này, bạn có thể thực hiện kiểm thử đơn vị một cách hiệu quả, đảm bảo rằng từng phần của ứng dụng hoạt động đúng như mong đợi. Bằng cách áp dụng đúng đắn các công cụ như MagicMock và patch(), việc kiểm thử sẽ trở nên đơn giản và đáng tin cậy hơn, giúp nâng cao chất lượng và độ tin cậy của phần mềm.