8
\$\begingroup\$

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)
\$\endgroup\$
1
\$\begingroup\$

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()
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.