PYTHON UNIT TESTING
CÁC CHỦ ĐỀ
BÀI MỚI NHẤT
MỚI CẬP NHẬT

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ì.

test php

banquyen png
Bài viết này được đăng tại freetuts.net, không được copy dưới mọi hình thức.

Giới thiệu về các stub trong Python

Ph C6 B0 C6 A1ng 20th E1 BB A9c 20assert   20trong 20Python 20 6  png

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ính is_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ính is_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ính is_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.

Cùng chuyên mục:

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn toàn diện về module datetime trong Python

Hướng dẫn toàn diện về module datetime trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Từ khóa yield trong Python

Từ khóa yield trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Định dạng chuỗi Strings trong Python

Định dạng chuỗi Strings trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Làm việc với các biến môi trường trong Python

Làm việc với các biến môi trường trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa iterator và iterable trong Python

Sự khác biệt giữa iterator và iterable trong Python

Cách làm việc với file tarball/tar trong Python

Cách làm việc với file tarball/tar trong Python

Chuyển đổi kiểu dữ liệu trong Python

Chuyển đổi kiểu dữ liệu trong Python

Sự khác biệt giữa toán tử == và is trong Python

Sự khác biệt giữa toán tử == và is trong Python

Làm việc với file ZIP trong Python

Làm việc với file ZIP trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Sự khác biệt giữa byte objects và string trong Python

Sự khác biệt giữa byte objects và string trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format  trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Top