# Locally testing Python Morsels exercises

Share

Let's talk about testing Python Morsels exercise solutions locally.

## The Exercise Statement

Let's say we have an exercise to create a Python class that represents a rectangle. The `Rectangle` class should accept an optional `length` and `width` and when initialized and it has `length`, `width`, `perimeter`, and `area` attributes. It should also have a nice string representation.

The bonus requires us to make `Rectangle` objects comparable (with equality and inequality).

## Our solution

We've written a solution that represents the required `Rectangle` class in a `rectangle` module (a `rectangle.py` file):

``````class Rectangle:

"""The rectangle class"""

def __init__(self, length=1, width=1) -> None:
self.length = length
self.width = width
self.perimeter = 2 * (self.length + self.width)
self.area = self.length * self.width

def __repr__(self) -> str:
return f"Rectangle({self.length}, {self.width})"
``````

We can manually test our code by passing our `rectangle.py` file to a Python interpreter along with a `-i` argument:

``````\$ python3 -i rectangle.py
>>>
``````

This will run `rectangle.py` from the command-line and drop into an interactive Python interpreter (a.k.a. the Python REPL):

``````>>> r1 = Rectangle()
>>> r1
Rectangle(1, 1)
>>> r1.area
1
>>> r2 = Rectangle(4,3)
>>> r2
Rectangle(4, 3)
>>> r1.perimeter
4
>>> r2.perimeter
14
>>> r2.width
3
``````

We can create objects (like `r1` and `r2` above) and can play around with them to verify the functionality of our class.

## Test Script

Every Python Morsels exercise comes with a test script for testing your code locally. The test script for this exercise (`test_rectangle.py`) contains several individual test cases to verify that our code works as expected.

This is the test script provided for this exercise, `test_rectangle.py`, which tests our `Rectangle` class:

``````import unittest

from rectangle import Rectangle

class RectangleTests(unittest.TestCase):

"""Tests for Rectangle."""

def test_length_width(self):
rect = Rectangle(3, 6)
self.assertEqual(rect.length, 3)
self.assertEqual(rect.width, 6)

def test_default_values(self):
rect = Rectangle()
self.assertEqual(rect.length, 1)
self.assertEqual(rect.width, 1)

def test_perimeter(self):
rect = Rectangle(3, 6)
self.assertEqual(rect.perimeter, 18)

def test_area(self):
rect = Rectangle(3, 6)
self.assertEqual(rect.area, 18)

def test_string_representation(self):
rect = Rectangle(3, 6)
self.assertEqual(str(rect), "Rectangle(3, 6)")
self.assertEqual(repr(rect), "Rectangle(3, 6)")

# To test the Bonus part of this exercise, comment out the following line
@unittest.expectedFailure
def test_equality(self):
a = Rectangle()
b = Rectangle(1, 1)
self.assertEqual(a, b)
self.assertFalse(a != b)

if __name__ == "__main__":
unittest.main(verbosity=2)
``````

## Running the automated tests

To test our `Rectangle` class, we'll save the test_rectangle.py test script (downloaded from the Python Morsels website) in the same folder/directory as your rectangle.py file.

``````rectangle/
│
├── rectangle.py
└── test_rectangle.py
``````

We also need to make sure our current working directory is the directory that contains our code (`rectangle.py`) and our test script (`test_rectangle.py`). That may require changing directories (to wherever we've saved these files):

``````\$ cd /home/trey/python_morsels/rectangle/
``````

We can now run the automated tests against our code by running our test script:

``````\$ python3 test_rectangle.py
``````

The output shows whether a given test passed or failed:

``````test_area (__main__.RectangleTests) ... ok
test_default_values (__main__.RectangleTests) ... ok
test_equality (__main__.RectangleTests) ... expected failure
test_length_width (__main__.RectangleTests) ... ok
test_perimeter (__main__.RectangleTests) ... ok
test_string_representation (__main__.RectangleTests) ... ok

----------------------------------------------------------------------
Ran 6 tests in 0.001s

OK (expected failures=1)
``````

We have three possible outcomes that summarise the result of running the tests:

1. `OK` means all tests passed successfully
2. `FAIL` means some or all tests did not pass
3. `ERROR` means a test raised an exception other than `AssertionError`

We can also use `-m unittest` along with `-k` to run tests for test methods matching a given name:

``````\$ python3 -m unittest -v test_rectangle.py -k test_area
test_area (test_rectangle.RectangleTests) ... ok

----------------------------------------------------------------------
Ran 1 test in 0.000s

OK
``````

## Testing the bonus(es) for an exercise

Python Morsels exercises usually have one or more bonuses, which you can optionally work on after the base problem. Within Python Morsels test scripts, bonuses are decorated with `@expectedFailure`, which makes sure a failing test doesn't result in a failing exercise.

Once a complete solution for the exercise is ready we can test the bonus by commenting-out the `@unittest.ExpectedFailure` line.

If we comment out that line in our the tests for our `Rectangle` class:

``````    # @unittest.expectedFailure
def test_equality(self):
a = Rectangle()
b = Rectangle(1, 1)
self.assertEqual(a, b)
self.assertFalse(a != b)
``````

We'll see a failure if we run our tests again (because we're not passing the bonus yet):

``````\$ python3 test_rectangle.py
test_area (__main__.RectangleTests) ... ok
test_default_values (__main__.RectangleTests) ... ok
test_equality (__main__.RectangleTests) ... FAIL
test_length_width (__main__.RectangleTests) ... ok
test_perimeter (__main__.RectangleTests) ... ok
test_string_representation (__main__.RectangleTests) ... ok

======================================================================
FAIL: test_equality (__main__.RectangleTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/trey/python_morsels/rectangle/test_rectangle.py", line 38, in test_equality
self.assertEqual(a, b)
AssertionError: Rectangle(1, 1) != Rectangle(1, 1)

----------------------------------------------------------------------
Ran 6 tests in 0.001s

FAILED (failures=1)
``````

If we update our `Rectangle` class (in our `rectangle.py` module) to implement equality checks:

``````class Rectangle:

"""The rectangle class"""

def __init__(self, length=1, width=1) -> None:
self.length = length
self.width = width
self.perimeter = 2 * (self.length + self.width)
self.area = self.length * self.width

def __repr__(self) -> str:
return f"Rectangle({self.length}, {self.width})"

def __eq__(self, other):
if isinstance(other, Rectangle):
return (self.length, self.width) == (other.length, other.width)
return NotImplemented
``````

when we run the tests again we'll see that they all pass:

``````\$ python3 test_rectangle.py
test_area (__main__.RectangleTests) ... ok
test_default_values (__main__.RectangleTests) ... ok
test_equality (__main__.RectangleTests) ... ok
test_length_width (__main__.RectangleTests) ... ok
test_perimeter (__main__.RectangleTests) ... ok
test_string_representation (__main__.RectangleTests) ... ok

----------------------------------------------------------------------
Ran 6 tests in 0.001s

OK
``````

In the output instead of expected failure we will see `OK` this time.

## Have questions?

Still have questions on local testing?