Python Advanced Concepts: Unit Testing with unittest


Unit testing is a fundamental concept in software development. It involves writing tests for individual components of your application to ensure they are functioning as expected. Python's built-in library unittest provides a framework for this.

a. A basic Bookstore class:

This class will serve as our test subject. It represents a simple bookstore where you can add, remove, and search for books.

Code

# Defining a class named 'bookstore'.
class bookstore():
    # Initialising the bookstore with an empty list of books.
    def __init__(self):
        self.books = []

    # Method to add a book to the bookstore.
    def add_book(self, bookname: str, author: str, cost: float):
        # Check if the provided cost is of float type.
        if not isinstance(cost, float):
            print("Value error: Cost must be a price i.e 9.99")
            return

        # Loop through the list of books.
        for book in self.books:
            # Check if the bookname already exists in the list.
            if bookname == book[0]:
                print("Cannot add book. Book already exists")
                return
        # If the book doesn't exist, add it to the list.
        else:
            self.books.append((bookname, author, cost))

    # Method to remove a book from the bookstore using the bookname.
    def remove_book(self, bookname):
        # Loop through the list of books.
        for book in self.books:
            # Check if the bookname matches any in the list.
            if bookname == book[0]:
                # Remove the matching book from the list.
                self.books.remove(book)
                return
        # If no matching book is found, raise a ValueError.
        raise ValueError("{} does not exist in the bookstore.".format(bookname))

    # Method to print all the current books in the bookstore.
    def print_current_books(self):
        print("Books in Bookstore\n")
        # Loop through the list of books and print their details.
        for book in self.books:
           print("Bookname: {}\nAuthor: {}\n"
                 "Cost: {:.2f}\n".format(book[0], book[1], book[2]))

    # Method to search for a book by its name.
    def search_for_book(self, bookname):
        # Initialising an empty list to store found books.
        books_found = []
        # Loop through the list of books.
        for book in self.books:
            # Check if the search term is part of any bookname (case-insensitive).
            if str(bookname).lower() in book[0].lower():
                # Add the matching book to the books_found list.
                books_found.append(book)
        # Return the list of found books.
        return books_found

b. The Unit Tests:

Using Python's unittest framework, we write tests to ensure our bookstore behaves as expected.

Code

# Importing the unittest module to use its functionalities.
import unittest

# Importing the bookstore class from our bookstore module. Make sure the bookstore.py is in the same folder (or you have your IDE setup correctly to locate remote files)
from bookstore import bookstore

# Creating a class to test the bookstore, inheriting from unittest.TestCase.
class TestBookstore(unittest.TestCase):

    # Method to set up data before each test.
    def setUp(self):
        # Initialising a bookstore instance to test its methods.
        self.bookstore = bookstore()

        # Creating two sample book records to use in the tests.
        self.book1 = ("Python 3.8", "Slashroot", 16.99)
        self.book2 = ("Python 2023", "Slashroot", 76.99)

    # Test method to test the add_book method of bookstore.
    def test_add_book(self):

        # Adding the first sample book to the bookstore.
        self.bookstore.add_book(*self.book1)
        # Checking if the first book is now in the bookstore's list of books.
        self.assertIn(self.book1, self.bookstore.books)

        # Trying to add the same book again to the bookstore.
        self.bookstore.add_book(*self.book1)
        # Ensuring the bookstore still has only one instance of the book.
        self.assertEqual(len(self.bookstore.books), 1)

        # Adding the second sample book to the bookstore.
        self.bookstore.add_book(*self.book2)
        # Checking if the second book is now in the bookstore's list of books.
        self.assertIn(self.book2, self.bookstore.books)
        # Checking if the bookstore now has two books.
        self.assertEqual(len(self.bookstore.books), 2)

    # Test method to test the remove_book method of bookstore.
    def test_remove_book(self):
        # Adding the first sample book to the bookstore.
        self.bookstore.add_book(*self.book1)
        # Removing the first book.
        self.bookstore.remove_book(self.book1[0])
        # Ensuring the first book is no longer in the bookstore's list of books.
        self.assertNotIn(self.book1, self.bookstore.books)

        # Testing if an error is raised when trying to remove a book that isn't in the bookstore.
        with self.assertRaises(ValueError):
            self.bookstore.remove_book(self.book2[0])

    # Test method to test the search_for_book method of bookstore.
    def test_search_for_book(self):
        # Adding both sample books to the bookstore.
        self.bookstore.add_book(*self.book1)
        self.bookstore.add_book(*self.book2)

        # Searching for a term that isn't a book title.
        found_books = self.bookstore.search_for_book("Slashroot")
        # Ensuring no books are found.
        self.assertEqual(len(found_books), 0)

        # Searching for the exact title of the second book.
        found_books = self.bookstore.search_for_book("Python 2023")
        # Ensuring the second book is found.
        self.assertIn(self.book2, found_books)

        # Searching for a common term in both book titles.
        found_books = self.bookstore.search_for_book("Python")
        # Ensuring two books are found.
        self.assertEqual(len(found_books), 2)
        # Checking that both books are in the results.
        self.assertIn(self.book1, found_books)
        self.assertIn(self.book2, found_books)

    # Test method to test the print_current_books method of bookstore.
    def test_print_current_books(self):
        # Adding both sample books to the bookstore.
        self.bookstore.add_book(*self.book1)
        self.bookstore.add_book(*self.book2)
        # Printing the current list of books. (This method mainly checks for runtime errors.)
        self.bookstore.print_current_books()

# Ensuring the unittests run only when the script is executed directly.
if __name__ == '__main__':
    unittest.main()

c. Key Points:

1. setUp(): This method is used to set up resources needed for the tests. It's run before every test method.
2. assertIn, assertEqual: These are assertion methods to verify test outcomes. They raise exceptions if the assertion fails.
3. if __name__ == '__main__': unittest.main(): This is the typical way to run tests in a script.
4. This test script can now be used every time a developer makes a change to the bookstore class to verify its integrity.

Enquiries

[email protected]

Copyright © 2023 - slash-root.com