Previous Lecture Lecture 5 Next Lecture

Lecture 5, Thu 04/17

Container Classes and Operator Overloading


Student Questions

Square and Rectangle Example

Creating Square, a subclass of Rectangle

class Square(Rectangle):
    def __init__(self, side):
        # equivalent to calling Rectangle.__init__(self, side, side)
        super().__init__(side, side)

# Rectangle parent class for context
class Rectangle:
    """A class that describes the properties of a rectangle"""
    def __init__(self, width, height):
        self.width = width
        self.height = height

Explanations

Notes:

In-class Instructions

Let’s create a Drink base class as well as defining specific classes for a couple types of Drinks (Tea and Juice). The DrinkOrder class will organize Drinks and will provide a summary of a specific drink order.

In addition to defining classes for various Drinks and a Drink Order, you will test your code for correctness by unit testing various scenarios using pytest.

You will need to create five files:

For testing, you will create the TestDrink class in the testFile.py, so that you can write the tests as you are implementing the class and its methods.

Drink class

The Drink.py file will contain the class definition of a general beverage.

We will define this class’ attributes as follows:

You should write a constructor that passes in values for all the fields. You may assume calls to the constructor will always contain a non-empty str representing the beverage’s size and a positive float representing the beverage’s price.

In addition to your constructor, your class definition should also support “setter” and “getter” methods that can update and retrieve the state of the Drink objects:

Each Drink object should be able to call a method info(self) that you will implement, which returns a str with the current beverage’s size and price. Since there are many beverages, the following output represents what will be returned if we call the info method after constructing a Drink object:

bev1 = Drink('medium', 20.5)
print(bev1.info())

Output:

medium: $20.50

Note: The bev1.info() return value in the example above does not contain a newline character (\n) at the end.

Note: The quotation marks around the returned string in IDLE shell tell us that the value was returned, not printed, hence the string representation is shown.

Hint: Note that the return string should contain a price with two decimal places (as traditionally used when displaying prices). Use the f-string to show the floating point values with 2 decimal places. For example:

>>> price = 5
>>> f"${price:.2f}"
'$5.00'

Template for the Drink and TestDrink classes

Below is the skeleton template for the Drink class that you can use as a starting point for your Drink.py file:

class Drink:
    def __init__(self):
        pass

    def get_size(self):
        pass

    def get_price(self):
        pass

    def update_size(self):
        pass

    def update_price(self):
        pass

    def info(self):
        pass

Immediately, we can add the corresponding test class and its testing methods to the testFile.py like so:

from Drink import Drink

class TestDrink:
    def test_init(self):
        pass

    def test_get_size(self):
        pass

    def test_get_price(self):
        pass

    def test_update_size(self):
        pass

    def test_update_price(self):
        pass

    def test_info(self):
        pass

The way the TestDrink template was created:

Write tests for the TestDrink class

Now, inside each test function in testFile.py, we test each class’s methods using assert statements.

For example, we can use the example that we saw above, to create a sample Drink object and test that it was correctly created:


from Drink import Drink

class TestDrink:
    def test_init(self):
        drink = Drink('medium', 20.5)
        assert drink.size == 'medium'
        assert drink.price == 20.5

Continue in this way to test the rest of the methods of the class:

    def test_get_size(self):
        drink = Drink('large', 20.95)
        assert drink.get_size() == 'large'

Before submitting your code to Gradescope, run your testFile.py using pytest to verify that all your tests are correct and are passing.


You are now ready to implement the rest of the classes and add their tests to testFile.py.

Tea class

The Tea.py file will contain the class definition of a tea drink. Since a tea IS-A drink, it should inherit the values we defined in the Drink class.

HINT: Think of the super keyword discussed in the lecture

Your Tea class definition should support the following constructor and method:

>>> drink1 = Tea('small', 3.0, "Camomile")
>>> drink1.info()
'Camomile Tea, small: $3.00'

Note: The drink1.info() return value in the example above does not contain a newline character (\n) at the end.

Note: The quotation marks around the returned string in IDLE tell us that the value was returned, not printed, hence the string representation is shown.


Juice class

The Juice.py file will contain the class definition of what a juice drink will have. Since a juice IS-A drink, it should inherit the values we defined in the Drink class.

Your Juice class definition should support the following constructor and method:

>>> juice1 = Juice('large', 8.5, ["Apple", "Guava"])
>>> juice1.info()
'Apple/Guava Juice, large: $8.50'

Note: The juice.info() return value in the example above does not contain a newline character (\n) at the end.

Note: The quotation marks around the returned string in IDLE tell us that the value was returned, not printed, hence the string representation is shown.


DrinkOrder class

The DrinkOrder.py file will contain the class definition of what a customer’s drink order will contain, along with the total price of all beverages in the drink order.

Your DrinkOrder class definition should support the following constructor and methods:

drink1 = Tea('small', 3.0, "Camomile")
juice1 = Juice('large', 8.5, ["Apple", "Guava"])
order = DrinkOrder()
order.add(drink1)
order.add(juice1)
print(order.total())

Output:

Order Items:
* Camomile Tea, small: $3.00
* Apple/Guava Juice, large: $8.50
Total Price: $11.50

IMPORTANT: be careful with the string formatting in the DrinkOrder class; especially the new line character and the space after the * for every new order.

An example of what the return string format of the total() method when there are no drinks in the Drink Order is shown below:

Order Items:
Total Price: $0.00

Note: There is NO space after the colon on the first line, just a newline (i.e., "Order Items:\n"). The order.total() return value in the examples above do not contain a newline character (\n) at the end.

Testing DrinkOrder

How to write a correct assert statement when a method’s return value is a string that is very long and contains newlines?

We have two options: see the “miscellaneous” section in the document that’s linked above in Step 4. In that example, the test creates two drink orders order1 and order2, which are both tested in the TestDrinkOrder class’s test_total() method.


Testing your code

testFile.py pytests

This file will contain unit tests using pytest to test if your functionality is correct. You should create your own tests different than the examples given in this writeup. Think of various scenarios and method calls to be certain that the state of your objects and return values are correct (provide enough tests such that all method calls in Drink, Tea, Juice and DrinkOrder are covered). Even though Gradescope will not use this file when running the automated tests, it is important to provide this file with various test cases (testing is important!).

We will manually grade your testFile.py to make sure your unit tests cover the defined methods in Drink, Tea, and Juice and DrinkOrder.

Submission

Once you’re done with writing your class definition and tests, Submit your Drink.py, Tea.py, Juice.py, DrinkOrder.py, and testFile.py files to the Lab02 assignment on Gradescope. There will be various unit tests Gradescope will run to ensure your code is working correctly based on the specifications given in this lab.

Note on grading for labs with testing component: For this lab assignment (and all lab assignments requiring a testFile.py), we will manually grade the tests you write. In general, your lab score will be based on the autograder’s score (out of 100 points).

Additionally, if the instructions are asking you to implement something in a certain way, e.g., to minimize code duplication, we might subtract points if your implementation does not follow these instructions.

Troubleshooting

If Gradescope’s tests don’t pass, you may get some error message that may or may not be obvious. Don’t worry - if the tests didn’t pass, take a minute to think about what may have caused the error. Try to think of your pytests and see if you can write a test to help you debug the error (if you haven’t already). If you’re still not sure why you’re getting the error, feel free to post on the forum or ask your TAs or Learning Assistants.

Some of the common issues that students encountered in this lab:

Interpreting the autograder output on Gradescope

If you see the error "The autograder failed to execute correctly. Please ensure that your submission is valid. Contact your course staff for help in debugging this issue. Make sure to include a link to this page so that they can help you most effectively." Make sure to remove any print() statements from your code or add them in the if __name__ == "__main__" section.


Below is an example output for a failed test on Gradescope:

- * Berry/Grape Juice, small: $ 5.00
?                              -
+ * Berry/Grape Juice, small: $5.00
- * Chamomile, large: $4.00
+ * Chamomile Tea, large: $4.00
?            ++++

In the first case, see the hint in the instructions about the price formatting using f-strings. In the second case, make sure that the .info() is correctly defined for the DrinkOrder class.

If you run into any other issues, make a post on the forum (remember to follow the posting guidelines) or ask during the lab / office hours.