lab02 : Tea Shop

num ready? description assigned due
lab02 true Tea Shop Tue 04/16 11:00AM Tue 04/23 11:59PM

Table of Contents

Learning Goals

In this lab, we’ll utilize inheritance functionality and define various Drink objects and its properties. You’ll have the opportunity to practice:

Note: In general, it is always important to work on labs and reading early so you can gain the proper context and utilize our office hours to seek assistance / ask clarifying questions during the weekdays before the deadline if needed!

It may be a good idea to read up on some concepts we’ll be using in this lab before you get started, specifically Chapter 1.4.6.2 (Inheritance).


Testing your code

Step 1: Installing pytest

For this step, you will need to use the Terminal (on MacOS). See the link below for the installation instructions for Windows.

Pytest will need to be installed on your computer since it does not come with Python by default. Some links for you to use when installing pytest are:

Step 2: Create testFile.py

In your lab02 folder, create a file that will contain the tests for the classes from this lab and their corresponding methods.

Following the Test-Driven Development (TDD), for every class that you’ll write in its own file, you will create a corresonding Test class in the testFile.py. Each Test class will contain test_ functions for the corresponding methods of that class (see examples below).

Your testFile will have 2 parts:

  1. the import statements at the top of the file (one for each class; from [filename] import [classname] substituting the correct filename and classname)
  2. the Test classes with their functions.

At the moment, your testFile.py should be empty, saved in the lab02 folder where you will create the files for the class definitions for this lab.

Your next task is to locate this lab02 folder using the Terminal (on MacOS) or Command Prompt (on Windows). These two programs give us access to command line - an interface that allows us to run commands that interact with the operating system (OS).

Step 3: run testFile.py using pytest

There are 2 things you need to do to run a testFile: navigation and execution.

Step 3.1: Navigating to the correct folder on the command Line

In order to run your pytests, you can navigate to your folder where your lab02 code is located using the command line interface.

If you are familiar with the Unix cd command: open the command line and cd to the lab folder.

If you are not familiar with the cd command:

Step 3.2: Executing testFile.py

Execution: After you open the terminal and navigate to the lab folder following the steps above, type the following command to run testFile.py using pytest.

On Mac:

python3 -m pytest testFile.py

On Windows:

python -m pytest testFile.py

If you see something like this, congratulations! You successfully installed and ran pytest.

=================== test session starts ====================
platform darwin -- Python 3.8.0, pytest-5.3.1, py-1.8.0, pluggy-0.13.1
rootdir: THE FOLDER YOU COPIED
collected 0 items

================== no tests ran in 0.01s ===================

Step 4: understand pytest output messages

See Step 4 in the Step-by-step instructions for using pytest for this lab (some of them are included below as well).


Lab Instructions

In this lab, we will 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:

To help you organize your code and use it for reference in the future labs, we will provide you with a template code for the Drink class, so that you can model the other classes accordingly.

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 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.