Unit Tests
Unittest provides developers with a set of tools to construct and run tests on individual components or units of code to ensure their correctness. By running unittests, developers can identify and fix bugs, creating more reliable code.
Concepts
Unittest relies on the following concepts:
- Test Fixture: Prepares the environment to perform one or more tests, including any necessary cleanup actions. This could involve creating temporary databases, directories, or starting server processes.
- Test Case: An individual unit of testing that checks for a specific response to a set of inputs. The
TestCaseclass provided by unittest can be used to create new test cases. - Test Suite: A collection of test cases or test suites that should be executed together.
- Test Runner: Executes the tests and provides the outcome to the developer. It can use different interfaces, like graphical or textual, to present the test results.
Use Case
Let's look at a test case example where Python code simulates a cake factory performing different functions, such as choosing different sizes and flavors of a cake, adding toppings, returning a list of ingredients, and calculating the price.
from typing import List
class CakeFactory:
def __init__(self, cake_type: str, size: str):
self.cake_type = cake_type
self.size = size
self.toppings = []
# Price based on cake type and size
self.price = 10 if self.cake_type == "chocolate" else 8
self.price += 2 if self.size == "medium" else 4 if self.size == "large" else 0
def add_topping(self, topping: str):
self.toppings.append(topping)
# Adding 1 to the price for each topping
self.price += 1
def check_ingredients(self) -> List[str]:
ingredients = ['flour', 'sugar', 'eggs']
if self.cake_type == "chocolate":
ingredients.append('cocoa')
else:
ingredients.append('vanilla extract')
ingredients += self.toppings
return ingredients
def check_price(self) -> float:
return self.price
# Example of creating a cake and adding toppings
cake = CakeFactory("chocolate", "medium")
cake.add_topping("sprinkles")
cake.add_topping("cherries")
cake_ingredients = cake.check_ingredients()
cake_price = cake.check_price()
cake_ingredients, cake_price
In the code above, the CakeFactory class and its methods are defined. Now, let's define the unittest methods to test the different functions of the code. The test suite includes tests for the cake's flavor, size, toppings, ingredients, and price.
import unittest
class TestCakeFactory(unittest.TestCase):
def test_create_cake(self):
cake = CakeFactory("vanilla", "small")
self.assertEqual(cake.cake_type, "vanilla")
self.assertEqual(cake.size, "small")
self.assertEqual(cake.price, 8) # Vanilla cake, small size
def test_add_topping(self):
cake = CakeFactory("chocolate", "large")
cake.add_topping("sprinkles")
self.assertIn("sprinkles", cake.toppings)
def test_check_ingredients(self):
cake = CakeFactory("chocolate", "medium")
cake.add_topping("cherries")
ingredients = cake.check_ingredients()
self.assertIn("cocoa", ingredients)
self.assertIn("cherries", ingredients)
self.assertNotIn("vanilla extract", ingredients)
def test_check_price(self):
cake = CakeFactory("vanilla", "large")
cake.add_topping("sprinkles")
cake.add_topping("cherries")
price = cake.check_price()
self.assertEqual(price, 13) # Vanilla cake, large size + 2 toppings
# Running the unittests
unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromTestCase(TestCakeFactory))
This results in the output:
..F.
======================================================================
FAIL: test_check_price (__main__.TestCakeFactory)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<ipython-input-9-32dbf74b3655>", line 33, in test_check_price
self.assertEqual(price, 13) # Vanilla cake, large size + 2 toppings
AssertionError: 14 != 13
----------------------------------------------------------------------
Ran 4 tests in 0.007s
FAILED (failures=1)
<unittest.runner.TextTestResult run=4 errors=0 failures=1>
The test for test_check_price failed because the expected price was incorrect. The cake price should have been 14, not 13. We can correct that part of the test:
def test_check_price(self):
cake = CakeFactory("vanilla", "large")
cake.add_topping("sprinkles")
cake.add_topping("cherries")
price = cake.check_price()
self.assertEqual(price, 14) # Vanilla cake, large size + 2 toppings
Re-running the unittests now gives:
....
----------------------------------------------------------------------
Ran 4 tests in 0.002s
OK
<unittest.runner.TextTestResult run=4 errors=0 failures=0>
Key Takeaways
Unittest assists developers in building robust and effective code. It allows testing of small, isolated units of functionality to catch bugs early and ensure consistent behavior.
Writing Unit Tests in Python
To write unit tests in Python, we use the unittest module, which provides a framework for constructing and running tests.
Example: Testing a Name Rearrangement Function
Suppose we have a function rearrange_name that rearranges names from "Last, First" format to "First Last":
import re
def rearrange_name(name):
result = re.search(r"^([\w .]*), ([\w .]*)$", name)
if result is None:
return name
return "{} {}".format(result[2], result[1])
We can create a test file rearrange_test.py to test this function:
import unittest
from rearrange import rearrange_name
class TestRearrange(unittest.TestCase):
def test_basic(self):
testcase = "Lovelace, Ada"
expected = "Ada Lovelace"
self.assertEqual(rearrange_name(testcase), expected)
if __name__ == '__main__':
unittest.main()
Running the tests:
python rearrange_test.py
The output will indicate whether the tests have passed or failed.