Streamline Python Testing with Tox: Automate and Standardize Your Workflow

Maximize your Python testing efficiency by leveraging Tox, automating and standardizing your processes for superior outcomes.

Introduction

There are various aspects to software development but one of the tedious and painful activities is ensuring the functionalities work across different versions of the operating systems, libraries, and environments. In this article, we will learn the frameworks that will help developers to address these challenges and build better-quality software with seamless automated testing. With the intent to keep things simple, we build a simple application with minimum functionality as our objective is not to master the app development or unit testing. We will test our app for Python 3.8 and Python 3.11 but this can be extended to as many versions as required.

In this article, we will learn the below topics.

  • Building Book Renting application
  • Learn and write test cases using pytest
  • Learn to configure Tox and set up multiple python environments
  • Executing test cases using Tox and analyzing the results

Building the application

We will build a Book Renting Application that will have the following functionalities

  • Show the number of books in stock
  • Let’s user rent the books
  • Let’s user return the books

We will define two classes BookRental and Customer. The complete code can be found in the file — BookRental.py

class BookRental:

def __init__(self, stock = 0):
self.__stock = stock

@property
def stock(self):
"""The stock value is readonly

Returns:
int: Returns the book stock
"""
return self.__stock
.....
.....

@logged
def display_books_stock(self):
"""A method to display the books in stock

Returns:
int: Returns the update book stock
"""
print(f'There are {self.stock} books available for rent')
return self.stock
class Customer:
def __init__(self):
self.__books = 0


@property
def books(self):
"""_summary_

Returns:
_type_: _description_
"""
return self.__books

....
....

@logged
def return_books(self):
"""_summary_

Returns:
int: Number of books to return
"""
return_books = input("How many books do you wish to return?: ")

return_books = Customer.validate_choice(return_books)

if return_books < 1:
print("You need at least one book to return")
return None
else:
return return_books

We will create a main function with user interaction where the user can mention the number of books to be rented or returned. Let us define the main function — main.py

from BookRental import BookRental, Customer

def main():
"""
The main function initiating the objects and the process
"""

book = BookRental(100)
customer = Customer()

while True:
print("""
====== Book Rental Service =======
1. Display available books
2. Request books
3. Return books
4. Exit
""")
choice = input("Enter your choice: ")

try:
choice = int(choice)
except ValueError:
print("Error with data type")
continue

if choice == 1:
book.display_books_stock()
elif choice == 2:
book.rent_books(customer.request_books())
elif choice == 3:
book.return_books(customer.return_books())
else:
break

print("Thank you for using the book rental service.")

Navigate to the project directory and run the application

python main.py


OUTPUT:
====== Book Rental Service =======
1. Display available books
2. Request books
3. Return books
4. Exit

Enter your choice: 1
There are 100 books available for rent

====== Book Rental Service =======
1. Display available books
2. Request books
3. Return books
4. Exit

Enter your choice: 2
How many books do you wish to rent?: 10
You have successfully rented 10 books
Current available stock is 90

====== Book Rental Service =======
1. Display available books
2. Request books
3. Return books
4. Exit

Enter your choice: 3
How many books do you wish to return?: 5
You have successfully returned 5 books
Current available stock is 95

====== Book Rental Service =======
1. Display available books
2. Request books
3. Return books
4. Exit

Enter your choice: 4
Thank you for using the book rental service.

Overview of pytest and test coverage

Now that we have the application built and executed successfully, let us write unit test cases using pytest. The complete test cases can be found in the file test_BookRental.py

# pip install pytest to install 
import pytest
from BookRental import Customer, BookRental

STOCK = 100

def test_display_books_stock():

book1 = BookRental(STOCK)
assert STOCK == book1.display_books_stock()

book2 = BookRental(0)
assert 0 == book2.display_books_stock()

book3 = BookRental(-1)
assert -1 == book3.display_books_stock()

...
...

def test_return_books_zero():
return_book = BookRental(STOCK)
assert 100 == return_book.return_books(0)

Please note that the file name should start with “test_” followed by the filename. This will let pytest identify the file with the test cases. We can execute these test cases with the below command

pytest test_BookRental.py

OUTPUT:
===================test session starts ====================
platform win32 -- Python 3.8.8, pytest-7.2.0, pluggy-1.0.0
rootdir: C:\Book Renting With python
plugins: cov-4.0.0
collected 7 items

test_BookRental.py .......[100%]

=================== 7 passed in 0.06s ====================

We can also check for test coverage below. Please notice there are seven test cases and all got executed successfully (100%) but the test coverage is 70% which means we need to write more unit test cases to make 100% coverage.

pytest --cov


OUTPUT:

platform win32 -- Python 3.8.8, pytest-7.2.0, pluggy-1.0.0
rootdir: C:\Book Renting With python
plugins: cov-4.0.0
collected 7 items

test_BookRental.py ....... [100%]

----------- coverage: platform win32, python 3.8.8-final-0 -----------
Name Stmts Miss Cover
----------------------------------------
BookRental.py 76 23 70%
test_BookRental.py 29 0 100%
----------------------------------------
TOTAL 105 23 78%


============= 7 passed in 0.10s ============

Overview of Tox

The Tox is a test command line tool that is used for managing virtual environments. It is mainly used to automatically test functionality under different python environments before release. Tox can be configured to execute test cases written in test tools like pytest and mypy.

Tox aims to automate and standardize testing in Python. It is part of a larger vision of easing the packaging, testing and release process of Python software — Tox wiki

We will create a tox.ini and configure python versions 3.8 and 3.11 and use pytest to write and test cases.

# pip install tox to install

[tox]
envlist = {py38, py311}-{pytest}

[testenv]
deps =
-r requirements.txt

[testenv:{py38, py311}-pytest]
description = Run pytest.
deps =
pytest
pytest-cov
{[testenv]deps}
commands =
pytest --cov

Once initiated, Tox will execute tests for Python 3.8 and Python 3.11. The result is as below. It is a fast and efficient way to test our functionalities for various Python versions. If any test fails for a specific python version, Tox would highlight the failed version and the test case.

C:\Book Renting With python>tox -r

OUTPUT:

============================================================================================== test session starts ===============================================================================================
platform win32 -- Python 3.8.8, pytest-7.2.0, pluggy-1.0.0
cachedir: .tox\py38-pytest\.pytest_cache
rootdir: C:\Book Renting With python
plugins: cov-4.0.0
collected 7 items

test_BookRental.py ....... [100%]

----------- coverage: platform win32, python 3.8.8-final-0 -----------
Name Stmts Miss Cover
----------------------------------------
BookRental.py 76 23 70%
test_BookRental.py 29 0 100%
----------------------------------------
TOTAL 105 23 78%


=============================================================================================== 7 passed in 0.14s ================================================================================================
py38-pytest: OK ✔ in 19.52 seconds
py311-pytest: remove tox env folder C:\Book Renting With python\.tox\py311-pytest
py311-pytest: PEP-514 violation in Windows Registry at HKEY_LOCAL_MACHINE/PythonCore/2.7/InstallPath error: missing
py311-pytest: install_deps> python -I -m pip install pytest -r requirements.txt
py311-pytest: install_package> python -I -m pip install --force-reinstall --no-deps "C:\Users\Book-renting-service-0.0.0.tar.gz"
py311-pytest: commands[0]> pytest --cov
============================================================================================== test session starts ===============================================================================================
platform win32 -- Python 3.11.1, pytest-7.2.0, pluggy-1.0.0
cachedir: .tox\py311-pytest\.pytest_cache
rootdir: C:\Users\Book Renting With python
plugins: cov-4.0.0
collected 7 items

test_BookRental.py ....... [100%]

---------- coverage: platform win32, python 3.11.1-final-0 -----------
Name Stmts Miss Cover
----------------------------------------
BookRental.py 76 23 70%
test_BookRental.py 29 0 100%
----------------------------------------
TOTAL 105 23 78%


=============================================================================================== 7 passed in 0.11s ================================================================================================
.pkg: _exit> python "C:\.\_backend.py" True setuptools.build_meta __legacy__
py38-pytest: OK (19.52=setup[18.81]+cmd[0.70] seconds)
py311-pytest: OK (15.05=setup[14.34]+cmd[0.70] seconds)
congratulations :) (34.95 seconds)

Conclusion

As we know there is nothing called error-free code or bug-free application. A typical solution/app will have many dependent libraries and is expected to be compatible with different operating systems, and environments with various versions of each of them. It is practically impossible to manually test and validate functionalities across all scenarios. In this article, we looked into Pytest and Tox libraries to automate the entire testing process. This will let developers focus more on the core functionalities and not worry about their compatibility with a wide range of past and present versions of libraries and environments. The setup can be used as template and extended to any project.

I hope you liked the article and found it helpful.

You can connect with me — on Linkedin

You can find the code for reference — on Github

References

https://docs.pytest.org/en/7.2.x/

https://tox.wiki/en/latest/

https://pypi.org/project/tox/

Leave a Reply

Your email address will not be published. Required fields are marked *