For an e-commerce web application I have written a shopping cart, and some tests for it.
I have used a course on Lynda.com, on building an e-commerce website, as inspiration on how to build the shopping cart. So it shares a lot of code with the one being thought there.
The shopping cart is obviously not finished, but I want to start testing as much of it's functionality as possible while in the process of developing it.
So, I am curious how more experienced programmers judge this piece of code.
Shopping cart code
from django.conf import settings
from plant_data.models import Product
def __init__(self, session):
"""
Initialize shopping cart.
"""
self.session = session
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
def add(self, product, quantity=1, update_quantity=False):
"""
Add new product to cart, or update quantity of item already in cart.
"""
product_id = str(product.id)
if product_id not in self.cart:
self.cart[product_id] = {'quantity': 0,
'price': str(product.price),
'name': product.name,
}
if update_quantity:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
def get_cart_list(self):
"""
Returns the cart as a list, this format is more suitable than a dictionary for
the frontend to make a detail view with.
"""
cart_list = []
print(self.cart)
for product_id, value in self.cart.items():
cart_list.append({
"id": product_id,
"name": value['name'],
"price": value['price'],
"quantity": value['quantity'],
})
return cart_list
def save(self):
self.session[settings.CART_SESSION_ID] = self.cart
self.session.modified = True
The tests
from decimal import Decimal
from django.contrib.sessions.middleware import SessionMiddleware
from django.test import TestCase, RequestFactory
from .cart import Cart
from plant_data.models import Product
class CartInitializeTestCase(TestCase):
# Test cart initialization
# Possible outcomes:
# - cart.cart is an empty dictionary (with input: session that contains no cart)
# - cart.cart contains is equal to the cart in the session
def setUp(self):
self.request = RequestFactory().get('/')
# adding session
middleware = SessionMiddleware()
middleware.process_request(self.request)
self.request.session.save()
def test_initialize_cart_clean_session(self):
"""
The cart is initialized with a session that contains no cart.
In the end it should have a variable cart which is an empty dict.
"""
request = self.request
cart = Cart(request.session)
self.assertEqual(cart.cart, {})
def test_initialize_cart_filled_session(self):
"""
The cart is initialized with a session that contains a cart.
In the end it should have a variable cart which equal to the cart that
is in the initial session.
"""
existing_cart = {
'1': {
'name': 'first name',
'price': '1.01',
},
'2': {
'name': 'second name',
'price': '34.1',
}
}
request = self.request
request.session['cart'] = existing_cart
cart = Cart(request.session)
self.assertEqual(cart.cart, existing_cart)
class CartAddTestCase(TestCase):
# Test the add function
# Possible outcomes:
# Add to existing product:
# - Add to an existing quantity
# - Update an existing quantity
# Add non existing product
# - Add non existing product
def setUp(self):
self.request = RequestFactory().get('/')
# adding session
middleware = SessionMiddleware()
middleware.process_request(self.request)
self.request.session['cart'] = {
'1': {
'name': 'some name',
'price': '1.01',
'quantity': 8,
},
}
self.request.session.save()
self.existing_product = Product(
id=1,
name='some name',
price=Decimal('1.01'),
stock=8,
)
self.new_product = Product(
id=2,
name='other name',
price=Decimal('2.23'),
stock=12,
)
def test_cart_add_to_existing_quantity(self):
"""
Test adding a quantity to an existing quantity.
"""
cart = Cart(self.request.session)
cart.add(product=self.existing_product,
quantity=4,
update_quantity=False,
)
new_cart = {
'1': {
'name': 'some name',
'price': '1.01',
'quantity': 12,
},
}
self.assertEqual(cart.cart, new_cart)
def test_cart_add_update_existing_quantity(self):
"""
Test updating existing item quantity.
"""
cart = Cart(self.request.session)
cart.add(product=self.existing_product,
quantity=4,
update_quantity=True,
)
new_cart = {
'1': {
'name': 'some name',
'price': '1.01',
'quantity': 4,
},
}
self.assertEqual(cart.cart, new_cart)
def test_cart_add_new_product(self):
cart = Cart(self.request.session)
cart.add(product=self.new_product,
quantity=4,
update_quantity=False,
)
new_cart = {
'1': {
'name': 'some name',
'price': '1.01',
'quantity': 8,
},
'2': {
'name': 'other name',
'price': '2.23',
'quantity': 4,
}
}
self.assertEqual(cart.cart, new_cart)
class CartGetCartListTestCase(TestCase):
# Test get_cart_list function
# Possible outcome:
# - returns a list of the cart that has been put in
def test_get_cart_list(self):
request = RequestFactory().get('/')
# adding session
middleware = SessionMiddleware()
middleware.process_request(request)
request.session['cart'] = {
'1': {
'name': 'some name',
'price': '1.01',
'quantity': 8,
},
'2': {
'name': 'other name',
'price': '2.23',
'quantity': 4,
}
}
request.session.save()
test_cart_list = [
{
"id": "1",
"name": "some name",
"price": "1.01",
"quantity": 8,
},
{
"id": "2",
"name": "other name",
"price": "2.23",
"quantity": 4,
},
]
cart = Cart(request.session)
cart_list = cart.get_cart_list()
self.assertEqual(cart_list, test_cart_list)
1 Answer 1
The unit tests look good. Good job!
def __init__(self, session):
"""
Initialize shopping cart.
"""
self.session = session
cart = self.session.get(settings.CART_SESSION_ID)
if not cart:
cart = self.session[settings.CART_SESSION_ID] = {}
self.cart = cart
N.B. You are missing the class signature
class Cart:
Assuming the session is a dict like object, .get
will implement a default
keyword argument, which will simplify this code.
def __init__(self, session):
"""
Initialize shopping cart.
"""
self.session = session
self.cart = self.session.get(settings.CART_SESSION_ID, {})
def add(self, product, quantity=1, update_quantity=False):
"""
Add new product to cart, or update quantity of item already in cart.
"""
product_id = str(product.id)
if product_id not in self.cart:
self.cart[product_id] = {'quantity': 0,
'price': str(product.price),
'name': product.name,
}
if update_quantity:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()
Storing the product price as a string ('price': str(product.price)
) can behave weirdly you are converting from floating point numbers. Using decimal does make sense here, but may be overkill if you will only ever need 2 places of precision. It might be easier to work in cents/pennies.
I would move creating a new dictionary representation of a product. It may have non-trivial business logic in the future, and you may want to move it somewhere else. Both of these tasks will be easier when all the logic is encapsulated into a small function.
I think update_quantity
would be better named as set_quantity
. Update implies the new value depends on the previous value, whereas that is not the case here. If anything, I would have expected the statements inside if update_quantity: else:
to be the other way around.
def product_dict(product):
""""""
return {
'quantity': 0,
'price': str(product.price),
'name': product.name,
}
def add(self, product, quantity=1, set_quantity=False):
"""
Add a new product to cart, or set the quantity of item already in cart.
"""
product_id = str(product.id)
if product_id not in self.cart:
self.cart[product_id] = product_dict(product)
if set_quantity:
self.cart[product_id]['quantity'] = quantity
else:
self.cart[product_id]['quantity'] += quantity
self.save()