Generative AI for Unit Testing

Codecademy Team
Use ChatGPT to create unit tests for our Python code.

Getting Started

Are you a student in school, a junior software engineer, or a hobbyist programmer creating your own unit tests? Maybe you’re still “copy & pasting” previous unit tests and then modifying them for your new classes? If so, this article is definitely for you! We will be going over how to use Generative AI, specifically ChatGPT, to develop unit tests in Python. Let’s begin by telling ChatGPT that we require assistance with developing unit tests in Python.

Prompt:

I'd like your help with creating unit tests for a Python class I have.

Response:

Of course, I'd be happy to help you create unit tests for your Python class. Unit tests are a crucial part of ensuring that your code works as expected and that future changes don't introduce new bugs. To get started, I'll need some information about the class you want to test. Specifically, please provide:

1. A brief description of the class and its purpose.
2. The methods you want to test (if there are any specific methods).
3. Any dependencies the class might have on external resources or other classes.

Once you provide this information, I can guide you through the process of writing effective unit tests for your Python class.

Create Unit Tests with ChatGPT

The code we will be using in this article will be similar to the one used in our Debugging Python Code with ChatGPT tutorial. We will provide ChatGPT with a simple Calculator class that has the following methods: add(), subtract(), multiply(), and divide().

Note: When giving ChatGPT code (in any language), it is recommended to present the code in the form of a code block to distinguish it from regular text. When you include code in a ChatGPT prompt, make sure to format it as we have done in the following prompt.

Prompt:

```py
class Calculator:
    def add(self, left, right):
        return left + right

    def subtract(self, left, right):
        return left - right

    def multiply(self, left, right):
        return left * right

    def divide(self, left, right):
        return left / right
```py

Response:

Thank you for providing the Calculator class. To create unit tests for this class, we will use the built-in Python unittest framework. Let's start by writing some test cases to cover the functionality of the Calculator class. We'll focus on testing each of the four methods: add, subtract, multiply, and divide.

Create a new Python file (e.g., test_calculator.py) and write the following code:
```py
import unittest
from your_calculator_module import Calculator  # Replace 'your_calculator_module' with the actual module name

class TestCalculator(unittest.TestCase):
    def setUp(self):
        # Create an instance of the Calculator class before each test
        self.calculator = Calculator()

    def test_add(self):
        result = self.calculator.add(2, 3)
        self.assertEqual(result, 5)

    def test_subtract(self):
        result = self.calculator.subtract(5, 3)
        self.assertEqual(result, 2)

    def test_multiply(self):
        result = self.calculator.multiply(4, 3)
        self.assertEqual(result, 12)

    def test_divide(self):
        result = self.calculator.divide(10, 3)
        self.assertEqual(result, 10 % 3)  # The correct result should be 1

if __name__ == '__main__':
    unittest.main()
```py

ChatGPT returned some test functions for our Calculator class. It created a TestCalculator class with 5 functions: setUp(), test_add(), test_subtract(), test_multiply(), and test_divide(). For this section, let’s focus on the test functions.

We see the test functions all have a single test case inside of them. This probably doesn’t cover all edge cases that might need to be covered for each function. Instead of trying to come up with every single edge case, let’s ask ChatGPT if there are any edge cases that we have not considered in the current unit tests.

Prompt:

Are there edge cases that are not being tested for?

Response:


Yes, there are several other edge cases that you can consider testing for in your Calculator class. Testing these scenarios will help ensure that your calculator functions correctly and handles various inputs appropriately. Here are some additional edge cases you may want to test for:

Negative Numbers: Test operations involving negative numbers, such as addition, subtraction, multiplication, and division.

Zero Operand: Test the case where one or both operands are zero for all four basic operations.

Large Numbers: Test with large numbers to ensure that your calculator can handle them without overflowing or causing other issues.

Floating-Point Precision: For the multiply and divide methods, test with decimal numbers to verify that the precision is retained up to a reasonable number of decimal places.

Invalid Inputs: Test for scenarios where invalid input is passed to the calculator methods (e.g., passing non-numeric values).

Boundary Values: Test with values that are close to the minimum and maximum values for integer or floating-point data types.

Rounding and Precision: Test for scenarios where precision may be lost due to the limitations of floating-point arithmetic.

Chained Operations: Test with multiple consecutive operations (e.g., 2 + 3 * 4 - 1) to ensure that the order of operations is correctly handled.

Here's an example of how you could add some of these additional tests to the existing TestCalculator class:

```py
class TestCalculator(unittest.TestCase):
    # ... (existing test methods)

    def test_divide_by_negative(self):
        result = self.calculator.divide(10, -2)
        self.assertEqual(result, -5)

    def test_divide_zero_by_number(self):
        result = self.calculator.divide(0, 5)
        self.assertEqual(result, 0)

    def test_divide_number_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            self.calculator.divide(10, 0)

    def test_multiply_large_numbers(self):
        result = self.calculator.multiply(999999999, 999999999)
        self.assertEqual(result, 999999998000000001)

    def test_add_decimal_numbers(self):
        result = self.calculator.add(0.1, 0.2)
        self.assertAlmostEqual(result, 0.3, places=4)

    # ... (other additional test methods)

if __name__ == '__main__':
    unittest.main()
```py

By including a comprehensive set of test cases, you can gain confidence in the correctness of your Calculator class and ensure that it behaves as expected in various scenarios. Remember, the goal of testing is to uncover potential issues and ensure that your code handles all possible scenarios gracefully.

Great work! We now have a start of test cases to use for our Calculator class. Additionally, we have some ideas from ChatGPT that could enhance further development of other unit tests.

Customizing ChatGPT’s Unit Tests

Let’s review all of the test functions we currently have.

import unittest
from calculator import Calculator
class TestCalculator(unittest.TestCase):
def setUp(self):
self.calculator = Calculator()
def test_add(self):
result = self.calculator.add(2, 3)
self.assertEqual(result, 5)
def test_subtract(self):
result = self.calculator.subtract(5, 3)
self.assertEqual(result, 2)
def test_multiply(self):
result = self.calculator.multiply(4, 3)
self.assertEqual(result, 12)
def test_divide(self):
result = self.calculator.divide(10, 2)
self.assertEqual(result, 5)
with self.assertRaises(ZeroDivisionError):
self.calculator.divide(10, 0)
def test_divide_by_negative(self):
result = self.calculator.divide(10, -2)
self.assertEqual(result, -5)
def test_divide_zero_by_number(self):
result = self.calculator.divide(0, 5)
self.assertEqual(result, 0)
def test_divide_number_by_zero(self):
with self.assertRaises(ZeroDivisionError):
self.calculator.divide(10, 0)
def test_multiply_large_numbers(self):
result = self.calculator.multiply(999999999, 999999999)
self.assertEqual(result, 999999998000000001)
def test_add_decimal_numbers(self):
result = self.calculator.add(0.1, 0.2)
self.assertAlmostEqual(result, 0.3, places=4)
if __name__ == '__main__':
unittest.main()

Before executing the unit tests ChatGPT provides, we’ll ensure that the unit tests abide by 3 rules:

  1. The tests apply to our application
  2. There is no repetition across the tests
  3. Each test function only examines one unit, or function, from the original codebase