============================= test session starts ==============================
platform linux -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0
rootdir: /home/runner/work/py-techguides/py-techguides/codebase
configfile: pyproject.toml
plugins: anyio-4.8.0
collecting ... collected 6 items
tests/test_calc.py::test_add[10-5-15] PASSED [ 16%]
tests/test_calc.py::test_add[-2-5-3] PASSED [ 33%]
tests/test_calc.py::test_add[3.5-2.5-6] PASSED [ 50%]
tests/test_calc.py::test_subtract[10-5-5] PASSED [ 66%]
tests/test_calc.py::test_subtract[-2--2-0] PASSED [ 83%]
tests/test_calc.py::test_subtract[3.5-2.5-1] PASSED [100%]
============================== 6 passed in 0.02s ===============================
Testing Examples
This section provides concrete, hands-on examples of writing modular Python code and associated test run using pytest. We will develop simple code in a few modules, then show how to structure and run the corresponding tests.
In particular, you will learn how to:
- Write simple modules containing business logic or utility functions.
- Set up corresponding test modules for single components (unit tests) and their interaction (integration tests).
Test Examples
Unit tests
As its name suggests, a unit test focuses on one main expectation, which can be a single function, class, or module. Moreover, a unit test should normally:
- Cover corner cases
- Be lightweight
- Test one piece of code at a time
- Be repeatable
- Be self-validating: In other words, it should result in a binary pass-or-fail outcome, not needing further human investigation to interpret the result.
As a best practice, strive to adopt a Test-First Approach, namely writing the test before the relevant piece of code. This approach allows for a more modular design, as the code is developed to pass the tests.
Following are examples of unit test implementations for different scenarios and their test runs.
Maths
Below is the implementation of a simple maths module, defining functions to add and subtract two numbers:
# src/python_training_material/calc.py
def add(a, b):
return a + b
def subtract(a, b):
return a - b
# other operations...
The following is the implementation of the unit tests for the maths module, wherein we test the add
and subtract
functions:
# tests/test_calc.py
import pytest
from python_training_material.calc import add, subtract
# Test cases for addition
@pytest.mark.parametrize("a, b, expected", [(10, 5, 15), (-2, 5, 3), (3.5, 2.5, 6)])
def test_add(a, b, expected):
assert add(a, b) == expected
# Test cases for subtraction
@pytest.mark.parametrize("a, b, expected", [(10, 5, 5), (-2, -2, 0), (3.5, 2.5, 1)])
def test_subtract(a, b, expected):
assert subtract(a, b) == expected
@pytest.mark.parametrize
is ideal for providing distinct input data to individual tests, whereas @pytest.fixture
is better suited for reusable data across multiple tests.
I/O
Below is the implementation of an I/O module, which contains two functions that write and read data from a file:
# src/python_training_material/io.py
def write_to_file(file_path, data):
with open(file_path, "w") as file:
file.write(data)
def read_from_file(file_path):
with open(file_path, "r") as file:
return file.read()
# other I/O operations...
The following is the implementation of the unit tests for the I/O module, wherein we test both write_to_file()
and read_from_file()
within the same test function, to make sure that a file is written and read correctly when using our I/O functions in conjunction:
# tests/test_io.py
from python_training_material.io import read_from_file, write_to_file
def test_file_write_read(tmpdir):
# Write to a temporary file
= tmpdir.join("test.txt")
test_file "Hello, World!")
write_to_file(test_file,
# Read from the same file
= read_from_file(test_file)
content assert content == "Hello, World!"
# Additional tests for edge cases and larger files...
============================= test session starts ==============================
platform linux -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0
rootdir: /home/runner/work/py-techguides/py-techguides/codebase
configfile: pyproject.toml
plugins: anyio-4.8.0
collecting ... collected 1 item
tests/test_io.py::test_file_write_read PASSED [100%]
============================== 1 passed in 0.01s ===============================
Book Library
In software development, especially in complex applications, it’s crucial to structure code in a way that promotes modularity and testability. One effective strategy is to define abstract interfaces for different components or services in your application. Since abstract interfaces define a set of method signatures without implementation, by using them in this context we decouple the implementation details from the interface, making it easier to test components in isolation and together.
Defining abstract interfaces is particularly beneficial for unit testing, where we want to test components in isolation. In fact, by relying on abstract interfaces, we can implement multiple versions of a service (e.g., mocks or stubs) to facilitate testing various scenarios without relying on concrete implementations that might have dependencies or side effects.
Let’s consider a library management system that involves multiple services:
- Book Service: Manages the collection of books in the library.
- User Service: Handles user registration and tracking of borrowed books.
In this scenario, we want to test the interaction between these services, such as a user borrowing a book. By defining abstract interfaces for our services, we establish a contract that all implementations must follow. This approach allows us to:
- Easily swap implementations for testing purposes.
- Mock or stub services during tests to isolate components.
- Ensure consistency in how services interact with each other.
In accordance with the aforementioned, we start by defining the abstract interface for users and books, which will allow variable implementations with concrete classes via inheritance. In the following sections, we’ll use those classes to model the services of a book library management system.
Book Service Implementation
For our scenario, we define an abstract library book service class and a corresponding implementation:
# src/python_training_material/library/book_service.py
from abc import ABC, abstractmethod
class AbstractBookService(ABC):
@abstractmethod
def add_book(self, title: str, author: str) -> int | None:
pass
@abstractmethod
def borrow_book(self, book_id: int) -> bool:
pass
@abstractmethod
def return_book(self, book_id: int) -> bool:
pass
@abstractmethod
def remove_book(self, book_id: int) -> bool:
pass
class BookService(AbstractBookService):
def __init__(self):
self._books = {}
self._id_counter = 1
def add_book(self, title: str, author: str) -> int | None:
= self._id_counter
book_id if title == "" or author == "":
return None
= {"title": title, "author": author, "available": True}
book self._books[book_id] = book
self._id_counter += 1
return book_id
def get_book(self, book_id: int) -> dict | None:
return self._books.get(book_id)
def book_is_available(self, book_id: int) -> bool:
= self.get_book(book_id)
book if book:
return book["available"]
return False
def search_book_by_title(self, title: str) -> dict:
if title == "":
return {}
return {
book_id: bookfor book_id, book in self._books.items()
if title.lower() in book["title"].lower()
}
def borrow_book(self, book_id: int) -> bool:
if self.book_is_available(book_id):
self._books[book_id]["available"] = False
return True # Indicate successful borrow
return False
def return_book(self, book_id: int) -> bool:
= self.get_book(book_id)
book if book and not book["available"]:
self._books[book_id]["available"] = True
return True # Indicate successful return
return False
def remove_book(self, book_id: int) -> bool:
if self.book_is_available(book_id):
del self._books[book_id]
return True # Indicate successful deletion
return False
We then define a series of unit tests encompassing all its methods:
# tests/library/test_book_service.py
import pytest
from python_training_material.library.book_service import BookService
@pytest.fixture
def book_service():
return BookService()
@pytest.fixture
def book_data():
"""
Fixture to provide book data for testing.
"""
return {"title": "1984", "author": "George Orwell"}
@pytest.fixture
def created_book_id(book_service, book_data):
"""
Fixture to create a book and return its ID for testing.
"""
= book_service.add_book(**book_data)
book_id return book_id
@pytest.fixture
def borrowed_book_id(book_service, created_book_id):
"""
Fixture that borrows the created_book_id so that
we start the test with an already-borrowed book.
"""
book_service.borrow_book(created_book_id)return created_book_id
def test_add_book_with_existing_data(book_service, book_data):
"""
Test that a book can be added successfully.
"""
= book_service.add_book(**book_data)
added_book_id assert book_service.get_book(added_book_id) is not None
@pytest.mark.parametrize(
"missing_fields",
["title": ""},
{"author": ""},
{"title": "", "author": ""},
{
],
)def test_add_book_with_missing_data(book_service, book_data, missing_fields):
"""
Test that adding a book with missing data returns None.
"""
= book_data | missing_fields
book_data_modified assert book_service.add_book(**book_data_modified) is None
def test_get_book_with_existing_id(book_service, book_data, created_book_id):
"""
Test that an existing book can be retrieved successfully.
"""
assert book_service.get_book(created_book_id) == book_data | {"available": True}
def test_get_book_with_missing_id(book_service, missing_id=999):
"""
Test that retrieving a book with a non-existent id fails.
"""
# Assuming the value of `missing_id` does not represent an existing book
assert book_service.get_book(missing_id) is None
def test_book_is_available_with_existing_id(book_service, created_book_id):
"""
Test that a book with an existing id is available.
"""
# Upon creation via `book_service.add_book()`, a book's `available` field is set to True
assert book_service.book_is_available(created_book_id) is True
def test_book_is_available_with_borrowed_book(book_service, borrowed_book_id):
"""
Test that a borrowed book with an existing id is not available.
"""
assert book_service.book_is_available(borrowed_book_id) is False
def test_book_is_available_with_missing_id(book_service, missing_id=999):
"""
Test that a book with a non-existent id is not available.
"""
# Assuming the value of `missing_id` does not represent an existing book
assert book_service.book_is_available(missing_id) is False
def test_borrow_book_with_existing_id(book_service, created_book_id):
"""
Test that a book with an existing id can be borrowed successfully.
"""
assert book_service.borrow_book(created_book_id) is True
assert book_service.book_is_available(created_book_id) is False
def test_borrow_book_with_missing_id(book_service, missing_id=999):
"""
Test that borrowing a book with a non-existent id fails.
"""
# Assuming the value of `missing_id` does not represent an existing book
assert book_service.borrow_book(missing_id) is False
def test_borrow_book_unavailable(book_service, borrowed_book_id):
"""
Test that borrowing an already borrowed book returns False.
"""
assert book_service.borrow_book(borrowed_book_id) is False
def test_search_book_by_full_title(book_service, book_data, created_book_id):
"""
Test that searching for a book by its full title returns the correct results.
"""
= book_service.search_book_by_title(book_data["title"])
book_found assert book_found == {created_book_id: book_data | {"available": True}}
def test_search_book_by_partial_title(book_service, book_data, created_book_id):
"""
Test that searching for a book by its partial title returns the correct results.
"""
= book_service.search_book_by_title(book_data["title"][:2]) # 19
book_found assert book_found == {created_book_id: book_data | {"available": True}}
def test_search_book_by_title_missing(book_service, missing_title="The Grinch"):
"""
Test that searching for a book that does not exist returns an empty dictionary.
"""
# Assuming the value of missing_title does not represent an existing book
assert book_service.search_book_by_title(missing_title) == {}
def test_search_book_by_title_empty(book_service):
"""
Test that searching for a book with an empty title doesn't return any results.
"""
assert book_service.search_book_by_title("") == {}
def test_return_book_borrowed(book_service, borrowed_book_id):
"""
Test that a book that has been borrowed can be returned successfully.
"""
assert book_service.return_book(borrowed_book_id) is True
assert book_service.book_is_available(borrowed_book_id) is True
def test_return_book_not_borrowed(book_service, created_book_id):
"""
Test that returning an existing book that was not borrowed returns False.
"""
assert book_service.return_book(created_book_id) is False
def test_return_book_with_missing_id(book_service, missing_id=999):
"""
Test that returning a book with a non-existent id returns False.
"""
# Assuming the value of `missing_id` does not represent an existing book
assert book_service.return_book(missing_id) is False
def test_return_book_already_returned(book_service, borrowed_book_id):
"""
Test that returning a book that has already been returned returns False.
"""
book_service.return_book(borrowed_book_id)assert book_service.return_book(borrowed_book_id) is False
def test_remove_book_with_existing_id(book_service, created_book_id):
"""
Test that a book with an existing id can be deleted successfully.
"""
assert book_service.remove_book(created_book_id) is True
assert book_service.get_book(created_book_id) is None
def test_remove_book_with_missing_id(book_service, missing_id=999):
"""
Test that removing a book with a non-existent id returns False.
"""
# Assuming the value of `missing_id` does not represent an existing book
assert book_service.remove_book(missing_id) is False
def test_remove_book_with_borrowed_id(book_service, borrowed_book_id):
"""
Test that removing a borrowed book returns False.
"""
assert book_service.remove_book(borrowed_book_id) is False
============================= test session starts ==============================
platform linux -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0
rootdir: /home/runner/work/py-techguides/py-techguides/codebase
configfile: pyproject.toml
plugins: anyio-4.8.0
collecting ... collected 23 items
tests/library/test_book_service.py::test_add_book_with_existing_data PASSED [ 4%]
tests/library/test_book_service.py::test_add_book_with_missing_data[missing_fields0] PASSED [ 8%]
tests/library/test_book_service.py::test_add_book_with_missing_data[missing_fields1] PASSED [ 13%]
tests/library/test_book_service.py::test_add_book_with_missing_data[missing_fields2] PASSED [ 17%]
tests/library/test_book_service.py::test_get_book_with_existing_id PASSED [ 21%]
tests/library/test_book_service.py::test_get_book_with_missing_id PASSED [ 26%]
tests/library/test_book_service.py::test_book_is_available_with_existing_id PASSED [ 30%]
tests/library/test_book_service.py::test_book_is_available_with_borrowed_book PASSED [ 34%]
tests/library/test_book_service.py::test_book_is_available_with_missing_id PASSED [ 39%]
tests/library/test_book_service.py::test_borrow_book_with_existing_id PASSED [ 43%]
tests/library/test_book_service.py::test_borrow_book_with_missing_id PASSED [ 47%]
tests/library/test_book_service.py::test_borrow_book_unavailable PASSED [ 52%]
tests/library/test_book_service.py::test_search_book_by_full_title PASSED [ 56%]
tests/library/test_book_service.py::test_search_book_by_partial_title PASSED [ 60%]
tests/library/test_book_service.py::test_search_book_by_title_missing PASSED [ 65%]
tests/library/test_book_service.py::test_search_book_by_title_empty PASSED [ 69%]
tests/library/test_book_service.py::test_return_book_borrowed PASSED [ 73%]
tests/library/test_book_service.py::test_return_book_not_borrowed PASSED [ 78%]
tests/library/test_book_service.py::test_return_book_with_missing_id PASSED [ 82%]
tests/library/test_book_service.py::test_return_book_already_returned PASSED [ 86%]
tests/library/test_book_service.py::test_remove_book_with_existing_id PASSED [ 91%]
tests/library/test_book_service.py::test_remove_book_with_missing_id PASSED [ 95%]
tests/library/test_book_service.py::test_remove_book_with_borrowed_id PASSED [100%]
============================== 23 passed in 0.04s ==============================
User Service Implementation
We implement a library’s user service similarly, by moving from the abstract class to a concrete implementation:
# src/python_training_material/library/user_service.py
from abc import ABC, abstractmethod
from python_training_material.library.book_service import AbstractBookService
class AbstractUserService(ABC):
@abstractmethod
def add_user(self, name: str) -> int | None:
pass
@abstractmethod
def remove_user(self, user_id: int) -> bool:
pass
@abstractmethod
def borrow_book(self, user_id: int, book_id: int) -> bool:
pass
@abstractmethod
def return_book(self, user_id: int, book_id: int) -> bool:
pass
class UserService(AbstractUserService):
def __init__(self, book_service: AbstractBookService):
self._users = {}
self._id_counter = 1
self._book_service = book_service
def add_user(self, name: str) -> int | None:
= self._id_counter
user_id if name == "":
return None
dict[str, list[int]] = {"name": name, "borrowed_books_ids": []}
user: self._users[user_id] = user
self._id_counter += 1
return user_id
def _user_exists(self, user_id: int) -> bool:
return user_id in self._users
def get_user(self, user_id: int) -> dict | None:
if not self._user_exists(user_id):
return None
return self._users[user_id]
def borrow_book(self, user_id: int, book_id: int) -> bool:
if not self._user_exists(user_id):
return False
= self._book_service.borrow_book(book_id)
book_borrowed if book_borrowed:
self._users[user_id]["borrowed_books_ids"].append(book_id)
return True # Indicate successful borrow
return False
def user_borrowed_book(self, user_id: int, book_id: int) -> bool:
if not self._user_exists(user_id):
return False
return book_id in self.get_user(user_id)["borrowed_books_ids"]
def return_book(self, user_id: int, book_id: int) -> bool:
if not self._user_exists(user_id):
return False
if book_id not in self._users[user_id]["borrowed_books_ids"]:
return False
= self._book_service.return_book(book_id)
book_returned if book_returned:
self._users[user_id]["borrowed_books_ids"].remove(book_id)
return True # Indicate successful return
return False
def remove_user(self, user_id: int) -> bool:
if self._user_exists(user_id):
# Check if the user has borrowed books, if so, cannot be removed
if self._users[user_id]["borrowed_books_ids"]:
return False
del self._users[user_id]
return True # Indicate successful deletion
return False
We then define a series of unit tests encompassing all its methods. Note how the UserService
class relies on BookService
to determine whether a book is available and can be borrowed/returned.
The following unit tests verify the behavior of the implemented UserService
in isolation from the concrete BookService
implementation, relying on mocking the behavior of the (abstract) BookService
class.
# tests/library/test_user_service.py
from unittest.mock import create_autospec
import pytest
from python_training_material.library.book_service import AbstractBookService
from python_training_material.library.user_service import UserService
@pytest.fixture
def mocked_book_service():
"""
Fixture to provide a mocked instance of AbstractBookService using create_autospec.
"""
= create_autospec(AbstractBookService, instance=True)
mock return mock
@pytest.fixture
def user_service(mocked_book_service):
"""
Fixture to provide a fresh instance of UserService with a mocked BookService.
"""
return UserService(book_service=mocked_book_service)
@pytest.fixture
def user_data():
"""
Fixture to provide user data for testing.
"""
return {"name": "Davide Vitiello"}
@pytest.fixture
def created_user_id(user_service, user_data):
"""
Fixture to create a user and returning its id.
"""
= user_service.add_user(**user_data)
user_id return user_id
def test_add_user_with_existing_name(user_service, user_data):
"""
Test that a user can be registered successfully.
"""
= user_service.add_user(**user_data)
user_id assert user_service.get_user(user_id) == user_data | {"borrowed_books_ids": []}
def test_add_user_with_missing_name(user_service):
"""
Test that adding a user with a missing name returns None.
"""
assert user_service.add_user("") is None
def test_get_user_with_existing_id(user_service, user_data, created_user_id):
"""
Test that a user can be retrieved successfully.
"""
assert user_service.get_user(created_user_id) == user_data | {
"borrowed_books_ids": []
}
def test_get_user_with_missing_id(user_service, missing_id=999):
"""
Test that a non-existent user returns None.
"""
# Assuming the value of `missing_id` does not represent an existing user
assert user_service.get_user(missing_id) is None
def test_borrow_book_with_existing_user(
user_service, mocked_book_service, created_user_id
):"""
Test that a user can borrow a book successfully.
"""
= 1
book_id = True
mocked_book_service.borrow_book.return_value
assert user_service.borrow_book(created_user_id, book_id) is True
# Assert that borrow_book was called with the correct book_id
mocked_book_service.borrow_book.assert_called_once_with(book_id)
# Verify that the book is in the user's `borrowed_books_ids`
assert user_service.user_borrowed_book(created_user_id, book_id) is True
def test_borrow_book_with_missing_user(
=999
user_service, mocked_book_service, missing_id
):"""
Test that borrowing a book with a non-existent user returns False.
"""
= 1
book_id
# Assuming the value of `missing_id` does not represent an existing user
assert user_service.borrow_book(missing_id, book_id) is False
# Assert that borrow_book was not called since the user does not exist
mocked_book_service.borrow_book.assert_not_called()
# Verify that the book is not in the user's `borrowed_books_ids`
assert user_service.user_borrowed_book(missing_id, book_id) is False
def test_return_book_with_existing_user(
user_service, mocked_book_service, created_user_id
):"""
Test that a user can return a book successfully.
"""
= 1
book_id = True
mocked_book_service.borrow_book.return_value = True
mocked_book_service.return_book.return_value
# Borrow the book
user_service.borrow_book(created_user_id, book_id)
# Return the book
assert user_service.return_book(created_user_id, book_id) is True
# Assert that return_book was called with the correct book_id
mocked_book_service.return_book.assert_called_once_with(book_id)
# Verify that the book is no longer in the user's borrowed_books
assert user_service.user_borrowed_book(created_user_id, book_id) is False
def test_return_book_with_missing_user(
=999
user_service, mocked_book_service, missing_id
):"""
Test that returning a book with a non-existent user returns False.
"""
= 1
book_id
# Assuming the value of `missing_id` does not represent an existing user
assert user_service.return_book(missing_id, book_id) is False
# Assert that return_book was not called since the user does not exist
mocked_book_service.return_book.assert_not_called()
def test_return_book_not_borrowed(user_service, mocked_book_service, created_user_id):
"""
Test that returning a book that was not borrowed by the specified user returns False.
"""
= 1
book_id = True
mocked_book_service.borrow_book.return_value
# First, add a user and borrow a book
= user_service.add_user("Average Joe")
borrowing_user_id
user_service.borrow_book(borrowing_user_id, book_id)
# Attempt to return the same book for a different user
assert user_service.return_book(created_user_id, book_id) is False
# Assert that return_book was NOT called since the user did not borrow the book
mocked_book_service.return_book.assert_not_called()
def test_remove_user_with_existing_id(user_service, created_user_id):
"""
Test that a user can be removed successfully.
"""
assert user_service.remove_user(created_user_id) is True
assert user_service.get_user(created_user_id) is None
def test_remove_user_with_missing_id(user_service, mocked_book_service, missing_id=999):
"""
Test that removing a user with a non-existent id returns False.
"""
# Assuming the value of `missing_id` does not represent an existing user
assert user_service.remove_user(missing_id) is False
assert user_service.get_user(missing_id) is None
# Assert that no interaction with BookService occurred
mocked_book_service.assert_not_called()
============================= test session starts ==============================
platform linux -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0
rootdir: /home/runner/work/py-techguides/py-techguides/codebase
configfile: pyproject.toml
plugins: anyio-4.8.0
collecting ... collected 11 items
tests/library/test_user_service.py::test_add_user_with_existing_name PASSED [ 9%]
tests/library/test_user_service.py::test_add_user_with_missing_name PASSED [ 18%]
tests/library/test_user_service.py::test_get_user_with_existing_id PASSED [ 27%]
tests/library/test_user_service.py::test_get_user_with_missing_id PASSED [ 36%]
tests/library/test_user_service.py::test_borrow_book_with_existing_user PASSED [ 45%]
tests/library/test_user_service.py::test_borrow_book_with_missing_user PASSED [ 54%]
tests/library/test_user_service.py::test_return_book_with_existing_user PASSED [ 63%]
tests/library/test_user_service.py::test_return_book_with_missing_user PASSED [ 72%]
tests/library/test_user_service.py::test_return_book_not_borrowed PASSED [ 81%]
tests/library/test_user_service.py::test_remove_user_with_existing_id PASSED [ 90%]
tests/library/test_user_service.py::test_remove_user_with_missing_id PASSED [100%]
============================== 11 passed in 0.08s ==============================
Integration Tests
Integration testing is a crucial aspect of developing a Python-based codebase, ensuring that different parts of your application work together seamlessly. With pytest
, writing and running integration tests becomes a more manageable and streamlined process, contributing significantly to the overall quality of your codebase. Here, we’ll explore how to implement effective integration tests with pytest
.
In this integration test, the UserService
and BookService
classes must interact to model a user borrowing and returning a book from a library. The library has to update its list of available books every time a user borrows some. Given the interaction of the book and user services as part of the borrowing and returning of books, we can model this scenario as one or more integration tests.
In the following, we check the sanity of the interaction between the UserService
and BookService
classes in different scenarios, as for example, a user borrowing a book and returning it:
# tests/library/test_integration_book_user_services.py
import pytest
from python_training_material.library.book_service import BookService
from python_training_material.library.user_service import UserService
@pytest.fixture
def book_service():
return BookService()
@pytest.fixture
def user_service(book_service):
return UserService(book_service)
@pytest.fixture
def created_user_id(user_service):
return user_service.add_user("Davide Vitiello")
@pytest.fixture
def created_book_id(book_service):
return book_service.add_book("1984", "George Orwell")
def test_user_with_existing_id_borrows_book(
user_service, book_service, created_user_id, created_book_id
):"""
Test to verify that a user can borrow a book,
and BookService and UserService states are updated accordingly.
"""
# Borrow the book
assert user_service.borrow_book(created_user_id, created_book_id) is True
# Verify that the user has borrowed the book
assert user_service.user_borrowed_book(created_user_id, created_book_id) is True
# Verify that the book is not available
assert book_service.book_is_available(created_book_id) is False
def test_user_with_existing_id_borrows_missing_book(
=999
user_service, created_user_id, missing_book_id
):"""
Test to verify that a user cannot borrow a book if the book does not exist.
"""
# Assuming the value of `missing_book_id` does not represent an existing book
assert user_service.borrow_book(created_user_id, missing_book_id) is False
def test_user_with_missing_id_borrows_book(
=999
user_service, book_service, created_book_id, missing_user_id
):"""
Test to verify that a user cannot borrow a book if the user does not exist.
"""
# Assuming the value of `missing_user_id` does not represent an existing user
assert user_service.borrow_book(missing_user_id, created_book_id) is False
assert book_service.book_is_available(created_book_id) is True
def test_user_with_existing_id_returns_book(
user_service, book_service, created_user_id, created_book_id
):"""
Test to verify that a user can return a borrowed book
and that both services' states are updated accordingly.
"""
# Borrow the book
user_service.borrow_book(created_user_id, created_book_id)
# Return the book
assert user_service.return_book(created_user_id, created_book_id) is True
# Verify the user no longer has the book
assert user_service.user_borrowed_book(created_user_id, created_book_id) is False
# Verify that BookService marks the returned book available again
assert book_service.book_is_available(created_book_id) is True
def test_user_with_existing_id_returns_book_not_borrowed(
user_service, book_service, created_user_id
):"""
Test to verify that a user cannot return a book if the user did not borrow it.
"""
# Add a new book
= book_service.add_book("Pride and Prejudice", "Jane Austen")
book_id
# Try to return the book
assert user_service.return_book(created_user_id, book_id) is False
def test_user_with_missing_id_returns_book(
=999
user_service, created_book_id, missing_user_id
):"""
Test to verify that a user cannot return a book if the user does not exist.
"""
# Assuming the value of `missing_user_id` does not represent an existing user
assert user_service.return_book(missing_user_id, created_book_id) is False
def test_remove_borrowed_book(
user_service, book_service, created_user_id, created_book_id
):"""
Test to verify that borrowed book cannot be removed.
"""
user_service.borrow_book(created_user_id, created_book_id)assert book_service.remove_book(created_book_id) is False
def test_remove_user_with_borrowed_book(user_service, created_user_id, created_book_id):
"""
Test to verify that user with borrowed book cannot be removed.
"""
user_service.borrow_book(created_user_id, created_book_id)assert user_service.remove_user(created_user_id) is False
============================= test session starts ==============================
platform linux -- Python 3.12.8, pytest-8.3.4, pluggy-1.5.0
rootdir: /home/runner/work/py-techguides/py-techguides/codebase
configfile: pyproject.toml
plugins: anyio-4.8.0
collecting ... collected 8 items
tests/library/test_integration_user_book_service.py::test_user_with_existing_id_borrows_book PASSED [ 12%]
tests/library/test_integration_user_book_service.py::test_user_with_existing_id_borrows_missing_book PASSED [ 25%]
tests/library/test_integration_user_book_service.py::test_user_with_missing_id_borrows_book PASSED [ 37%]
tests/library/test_integration_user_book_service.py::test_user_with_existing_id_returns_book PASSED [ 50%]
tests/library/test_integration_user_book_service.py::test_user_with_existing_id_returns_book_not_borrowed PASSED [ 62%]
tests/library/test_integration_user_book_service.py::test_user_with_missing_id_returns_book PASSED [ 75%]
tests/library/test_integration_user_book_service.py::test_remove_borrowed_book PASSED [ 87%]
tests/library/test_integration_user_book_service.py::test_remove_user_with_borrowed_book PASSED [100%]
============================== 8 passed in 0.03s ===============================