{"id":435,"date":"2024-01-29T11:19:39","date_gmt":"2024-01-29T11:19:39","guid":{"rendered":"https:\/\/learnpython.elegantwallp.com\/?p=435"},"modified":"2024-01-29T11:19:41","modified_gmt":"2024-01-29T11:19:41","slug":"python-stubs","status":"publish","type":"post","link":"https:\/\/learnpython.elegantwallp.com\/2024\/01\/29\/python-stubs\/","title":{"rendered":"Python Stubs"},"content":{"rendered":"\n<p><strong>Summary<\/strong>: in this tutorial, you\u2019ll learn how to use Python stubs to isolate parts of your program from each other for unit testing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Introduction to the Python stubs<\/h2>\n\n\n\n<p>Stubs are test doubles that return hard-coded values. The primary purpose of stubs is to prepare a specific state of the system under test.<\/p>\n\n\n\n<p>Stubs are beneficial because they return consistent results, making the test easier to write. Also, you can run tests even if the components that stubs are present are not working yet.<\/p>\n\n\n\n<p>Suppose you need to develop an alarm system that monitors the temperature of a room like a server room.<\/p>\n\n\n\n<p>To do that you need to set up a temperature sensor device and use the data from that sensor to alert if the temperature is below or above a specific temperature.<\/p>\n\n\n\n<p>First, define a\u00a0<code>Sensor<\/code>\u00a0class in the\u00a0<code>sensor.py<\/code>\u00a0module:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>import random class Sensor: @property def temperature(self): return random.randint(10, 45)<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>The&nbsp;<code>Sensor<\/code>&nbsp;class has a temperature property that returns a random temperature between 10 and 45. In the real world, the&nbsp;<code>Sensor<\/code>&nbsp;class needs to connect to the sensor device to get the actual temperature.<\/p>\n\n\n\n<p>Second, define the\u00a0<code>Alarm<\/code>\u00a0class that uses a\u00a0<code>Sensor<\/code>\u00a0object:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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 &lt; self._low or temperature > self._high: self._is_on = True @property def is_on(self): return self._is_on<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>By default, the&nbsp;<code>is_on<\/code>&nbsp;property of the&nbsp;<code>Alarm<\/code>&nbsp;is off (<code>False<\/code>). The&nbsp;<code>check()<\/code>&nbsp;method turns the alarm on if the temperature is lower than 18 or higher than 42.<\/p>\n\n\n\n<p>Once the&nbsp;<code>is_on<\/code>&nbsp;property of an&nbsp;<code>Alarm<\/code>&nbsp;object is on, you can send it to the alarm device to alert accordingly.<\/p>\n\n\n\n<p>Because the&nbsp;<code>temperature()<\/code>&nbsp;method of the&nbsp;<code>Sensor<\/code>&nbsp;returns a random temperature, it\u2019ll be difficult to test various scenarios to ensure the&nbsp;<code>Alarm<\/code>&nbsp;class works properly.<\/p>\n\n\n\n<p>To resolve it, you can define a stub for the&nbsp;<code>Sensor<\/code>&nbsp;class called&nbsp;<code>TestSensor<\/code>. The&nbsp;<code>TestSensor<\/code>&nbsp;has the&nbsp;<code>temperature<\/code>&nbsp;property that returns a value provided when its object is initialized.<\/p>\n\n\n\n<p>Third, define the\u00a0<code>TestSensor<\/code>\u00a0in\u00a0<code>test_sensor.py<\/code>\u00a0module:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>class TestSensor: def __init__(self, temperature) -> None: self._temperature = temperature @property def temperature(self): return self._temperature<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>The&nbsp;<code>TestSensor<\/code>&nbsp;class is like the&nbsp;<code>Sensor<\/code>&nbsp;class except that the&nbsp;<code>temperature<\/code>&nbsp;property returns a value specified in the constructor.<\/p>\n\n\n\n<p>Fourth, define a\u00a0<code>TestAlarm<\/code>\u00a0class in the\u00a0<code>test_alarm.py<\/code>\u00a0test module and import the\u00a0<code>Alarm<\/code>\u00a0and\u00a0<code>TestSensor<\/code>\u00a0from the\u00a0<code>alarm.py<\/code>\u00a0and\u00a0<code>sensor.py<\/code>\u00a0modules:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>import unittest from alarm import Alarm from test_sensor import TestSensor class TestAlarm(unittest.TestCase): pass<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>Fifth, test if the alarm is off by default:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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) <\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>In the&nbsp;<code>test_is_alarm_off_by_default<\/code>&nbsp;we create a new alarm instance and use the&nbsp;<code>assertFalse()<\/code>&nbsp;method to check if the&nbsp;<code>is_on<\/code>&nbsp;property of the alarm object is&nbsp;<code>False<\/code>.<\/p>\n\n\n\n<p>Run the test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>python -m unittest -v<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>Output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>test_is_alarm_off_by_default (test_alarm.TestAlarm) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>Sixth, test the\u00a0<code>check()<\/code>\u00a0method of the\u00a0<code>Alarm<\/code>\u00a0class in case the temperature is too high:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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)<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>In the&nbsp;<code>test_check_temperature_too_high()<\/code>&nbsp;test method:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Create an instance of the&nbsp;<code>TestSensor<\/code>&nbsp;with temperature 25 and passes it to the&nbsp;<code>Alarm<\/code>&nbsp;constructor.<\/li>\n\n\n\n<li>Call the&nbsp;<code>check()<\/code>&nbsp;method of the alarm object.<\/li>\n\n\n\n<li>Use the&nbsp;<code>assertTrue()<\/code>&nbsp;to test if the&nbsp;<code>is_on<\/code>&nbsp;property of the alarm is&nbsp;<code>True<\/code>.<\/li>\n<\/ul>\n\n\n\n<p>Run the test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>python -m unittest -v<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>Output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>The alarm is on because the temperature is higher than 24.<\/p>\n\n\n\n<p>Seventh, test the\u00a0<code>check()<\/code>\u00a0method of the Alarm class when the temperature is too low:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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)<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>In the&nbsp;<code>test_check_temperature_too_low()<\/code>&nbsp;test method:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Create an instance of the&nbsp;<code>TestSensor<\/code>&nbsp;with temperature 17 and passes it to the Alarm constructor.<\/li>\n\n\n\n<li>Call the&nbsp;<code>check()<\/code>&nbsp;method of the alarm object.<\/li>\n\n\n\n<li>Use the&nbsp;<code>assertTrue()<\/code>&nbsp;to test if the is_on property of the alarm is True.<\/li>\n<\/ul>\n\n\n\n<p>Run the test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>python -m unittest -v<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>Output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>Seventh, test the\u00a0<code>check()<\/code>\u00a0method of the\u00a0<code>Alarm<\/code>\u00a0class if the temperature is in the safe range (18, 24):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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)<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>In the&nbsp;<code>test_check_normal_temperature()<\/code>&nbsp;method we create a TestSensor with the temperature 20 and pass it to the Alarm constructor. Since the temperature is in the range (18, 24), the alarm should be off.<\/p>\n\n\n\n<p>Run the test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>python -m unittest -v<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>Output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Using MagicMock class to create stubs<\/h2>\n\n\n\n<p>Python provides you with the\u00a0<code>MagicMock<\/code>\u00a0object in the\u00a0<code>unittest.mock<\/code>\u00a0module that allows you to create stubs more easily.<\/p>\n\n\n\n<p>To create a stub for the\u00a0<code>Sensor<\/code>\u00a0class using the\u00a0<code>MagicMock<\/code>\u00a0class, you pass the\u00a0<code>Sensor<\/code>\u00a0class to the\u00a0<code>MagicMock()<\/code>\u00a0constructor:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>mock_sensor = MagicMock(Sensor)<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>The&nbsp;<code>mock_sensor<\/code>&nbsp;is the new instance of the&nbsp;<code>MagicMock<\/code>&nbsp;class that mocks the&nbsp;<code>Sensor<\/code>&nbsp;class.<\/p>\n\n\n\n<p>By using the\u00a0<code>mock_sensor<\/code>\u00a0object, you can set its property or call a method. For example, you can assign a specific temperature e.g., 25 to the\u00a0<code>temperature<\/code>\u00a0property of the mock sensor like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>mock_sensor.temperature = 25<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>The following shows the new version of the\u00a0<code>TestAlarm<\/code>\u00a0that uses the\u00a0<code>MagicMock<\/code>\u00a0class:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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)<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Using patch() method<\/h2>\n\n\n\n<p>To make it easier to work with\u00a0<code>MagicMock<\/code>, you can use the\u00a0<code><a href=\"https:\/\/www.pythontutorial.net\/python-unit-testing\/python-patch\/\">patch()<\/a><\/code>\u00a0as a decorator. For example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>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)<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>In this example, we use a&nbsp;<code>@patch<\/code>&nbsp;decorator on the&nbsp;<code>test_check_temperature_too_low()<\/code>&nbsp;method. In the decorator, we pass the&nbsp;<code>sensor.Sensor<\/code>&nbsp;as a target to patch.<\/p>\n\n\n\n<p>Once we use the&nbsp;<code>@patch<\/code>&nbsp;decorator, the test method will have the second parameter which is an instance of the&nbsp;<code>MagicMock<\/code>&nbsp;that mocks the&nbsp;<code>sensor.Sensor<\/code>&nbsp;class.<\/p>\n\n\n\n<p>Inside the test method, we set the temperature property of the sensor to 10, create a new instance of the Alarm class, and call&nbsp;<code>check()<\/code>&nbsp;method, and use the&nbsp;<code>assertTrue()<\/code>&nbsp;method to test if the alarm is on.<\/p>\n\n\n\n<p>Run the test:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>python -m unittest -v<\/code><small>Code language: Python (python)<\/small><\/code><\/pre>\n\n\n\n<p>Output:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code><code>test_check_temperature_too_low (test_alarm_with_patch.TestAlarm) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s<\/code><\/code><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Summary: in this tutorial, you\u2019ll learn how to use Python stubs to isolate parts of your program from each other for unit testing. Introduction to the Python stubs Stubs are test doubles that return hard-coded values. The primary purpose of stubs is to prepare a specific state of the system under test. Stubs are beneficial [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[43],"tags":[],"class_list":["post-435","post","type-post","status-publish","format-standard","hentry","category-3-python-unit-testing"],"_links":{"self":[{"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/posts\/435","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/comments?post=435"}],"version-history":[{"count":1,"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/posts\/435\/revisions"}],"predecessor-version":[{"id":436,"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/posts\/435\/revisions\/436"}],"wp:attachment":[{"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/media?parent=435"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/categories?post=435"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/learnpython.elegantwallp.com\/wp-json\/wp\/v2\/tags?post=435"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}