Budget App - Chart is the same but still gives error

Tell us what’s happening:
I believe the project is totally complete, but it still tells me the chart is 1066 characters different from the original. I can’t seem to find the problem though

Edit: I think it’s mostly about blankspaces. I’m in doubt if they would refuse a perfectly working project if the output didn’t match 100% with the original

Thanks in advance!

Error message:

Traceback (most recent call last):
File “/home/runner/Budget-App/test_module.py”, line 94, in test_create_spend_chart
self.assertEqual(actual, expected, ‘Expected different chart representation. Check that all spacing is exact.’)
AssertionError: ‘Perc[25 chars]n100|\n 90|\n 80|\n 70| o \n 60| o \n 50| o \n[264 chars]t \n’ != 'Perc[25 chars]n100| \n 90| \n 80| [349 chars] t ’
Diff is 1066 characters long. Set self.maxDiff to None to see it. : Expected different chart representation. Check that all spacing is exact.

Your code so far

class Category:
    def __init__(self, name):
        self.name = name
        self.ledger = []

    def __str__(self):
        title = self.name.center(30, '*') + '\n'
        middle = ''
        for i in self.ledger:
            middle += f'{i["description"]:23.23}' + f'{i["amount"]:>7.2f}' + '\n'
        total = f'Total: {self.get_balance()}'

        return title + middle + total

    def deposit(self, amount, description=''):
        self.ledger.append({'amount': amount, 'description': description})

    def withdraw(self, amount, description=''):
        if self.check_funds(amount):
            self.ledger.append({'amount': -amount, 'description': description})
            return True
        return False

    def get_balance(self):
        total = 0
        for i in self.ledger:
            total += i['amount']
        return total

    def transfer(self, amount, category):
        if self.check_funds(amount):
            self.withdraw(amount, f'Transfer to {category.name}')
            category.deposit(amount, f'Transfer from {self.name}')
            return True
        return False

    def check_funds(self, amount):
        if amount > self.get_balance():
            return False
        return True


def create_spend_chart(categories):
    from math import floor
    total_categories, lines = {}, {}
    total_chart = 0

    for category in categories:
        for i in category.ledger:

            if category.name not in total_categories:
                total_categories[category.name] = 0

            if i['amount'] < 0:
                total_chart += i['amount']
                total_categories[category.name] += i['amount']

    for category in categories:
        total_categories[category.name] /= total_chart
        total_categories[category.name] = round(total_categories[category.name], 2)

        for i in range(-1, floor(total_categories[category.name] * 10)):
            if category.name not in lines:
                lines[category.name] = ''
            lines[category.name] += 'o'

    output = 'Percentage spent by category\n'
    for i in range(10, -1, -1):
        bar = ''
        for key, value in lines.items():
            try:
                bar += value[i].center(3)
            except IndexError:
                continue
        output += f'{i * 10:>3}|{bar}\n'
    output += f'{"":4}' + ''.rjust(len(categories) * 3 + 1, '-') + '\n'

    categories_names = ''
    categories_str = []
    for i in range(0, len(categories)):
        categories_str.insert(i, str(categories[i].name))

    for i in range(0, len(max(categories_str, key=len))):
        categories_names += f'{"":^4}'
        for j in range(0, len(categories)):
            category_name = categories_str[j]
            try:
                categories_names += f'{category_name[i]:^3}'
            except IndexError:
                categories_names += f'{"":^3}'
                continue
        categories_names += '\n'

    output += categories_names
    return output

Your browser information:

User Agent is: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62.

Challenge: Budget App

Link to the challenge:

Welcome to the forums @eddg-stolben. You need to do like the test output asks here

and set self.maxDiff = None in the failing test to see the differences between your output and the expected, which contains lines like

-  20| o  o 
+  20|    o  o  
?      +++     +
-  10| o  o 
+  10|    o  o  
?      +++     +
-   0| o  o  o 
+   0| o  o  o  
?              +

which shows your output (- lines) versus the expected output (+) lines with a handy difference line afterward (? lines). If you look, all the lines have space issues (even the ones not included here), but the 10 and 20 lines (and on up) have the bar in the wrong place. I didn’t look at the code, but it looks like the spaces for the first (empty) bar are not being included either.

Anytime you are debugging a failing test, it can be useful to set self.maxDiff = None for unittest style tests (like these) or use the -vvv option to pytest if that’s your testing suite of choice.

Thanks! I’ll have a look at that

Where should I put the self.maxDiff = None? I’m 99% sure I’m doing this wrong, but I tried writing it in test_module.py by adding

def test_create_spend_chart(self):
        self.maxDiff = None

after the last function and the output is the same, the only difference is that I don’t get errors anymore

Code of test_module.py so far:

  import unittest
  import budget
  from budget import create_spend_chart


  class UnitTests(unittest.TestCase):
      def setUp(self):
          self.food = budget.Category("Food")
          self.entertainment = budget.Category("Entertainment")
          self.business = budget.Category("Business")

      def test_deposit(self):
          self.food.deposit(900, "deposit")
          actual = self.food.ledger[0]
          expected = {"amount": 900, "description": "deposit"}
          self.assertEqual(actual, expected, 'Expected `deposit` method to create a specific object in the ledger instance variable.')

      def test_deposit_no_description(self):
          self.food.deposit(45.56)
          actual = self.food.ledger[0]
          expected = {"amount": 45.56, "description": ""}
          self.assertEqual(actual, expected, 'Expected calling `deposit` method with no description to create a blank description.')

      def test_withdraw(self):
          self.food.deposit(900, "deposit")
          self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
          actual = self.food.ledger[1]
          expected = {"amount": -45.67, "description": "milk, cereal, eggs, bacon, bread"}
          self.assertEqual(actual, expected, 'Expected `withdraw` method to create a specific object in the ledger instance variable.')

      def test_withdraw_no_description(self):
          self.food.deposit(900, "deposit")
          good_withdraw = self.food.withdraw(45.67)
          actual = self.food.ledger[1]
          expected = {"amount": -45.67, "description": ""}
          self.assertEqual(actual, expected, 'Expected `withdraw` method with no description to create a blank description.')
          self.assertEqual(good_withdraw, True, 'Expected `transfer` method to return `True`.')

      def test_get_balance(self):
          self.food.deposit(900, "deposit")
          self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
          actual = self.food.get_balance()
          expected = 854.33
          self.assertEqual(actual, expected, 'Expected balance to be 854.33')

      def test_transfer(self):
          self.food.deposit(900, "deposit")
          self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
          good_transfer = self.food.transfer(20, self.entertainment)
          actual = self.food.ledger[2]
          expected = {"amount": -20, "description": "Transfer to Entertainment"}
          self.assertEqual(actual, expected, 'Expected `transfer` method to create a specific ledger item in food object.')
          self.assertEqual(good_transfer, True, 'Expected `transfer` method to return `True`.')
          actual = self.entertainment.ledger[0]
          expected = {"amount": 20, "description": "Transfer from Food"}
          self.assertEqual(actual, expected, 'Expected `transfer` method to create a specific ledger item in entertainment object.')

      def test_check_funds(self):
          self.food.deposit(10, "deposit")
          actual = self.food.check_funds(20)
          expected = False
          self.assertEqual(actual, expected, 'Expected `check_funds` method to be False')
          actual = self.food.check_funds(10)
          expected = True
          self.assertEqual(actual, expected, 'Expected `check_funds` method to be True')

      def test_withdraw_no_funds(self):
          self.food.deposit(100, "deposit")
          good_withdraw = self.food.withdraw(100.10)
          self.assertEqual(good_withdraw, False, 'Expected `withdraw` method to return `False`.')

      def test_transfer_no_funds(self):
          self.food.deposit(100, "deposit")
          good_transfer = self.food.transfer(200, self.entertainment)
          self.assertEqual(good_transfer, False, 'Expected `transfer` method to return `False`.')

      def test_to_string(self):
          self.food.deposit(900, "deposit")
          self.food.withdraw(45.67, "milk, cereal, eggs, bacon, bread")
          self.food.transfer(20, self.entertainment)
          actual = str(self.food)
          expected = f"*************Food*************\ndeposit                 900.00\nmilk, cereal, eggs, bac -45.67\nTransfer to Entertainme -20.00\nTotal: 834.33"
          self.assertEqual(actual, expected, 'Expected different string representation of object.')

      def test_create_spend_chart(self):
          self.food.deposit(900, "deposit")
          self.entertainment.deposit(900, "deposit")
          self.business.deposit(900, "deposit")
          self.food.withdraw(105.55)
          self.entertainment.withdraw(33.40)
          self.business.withdraw(10.99)
          actual = create_spend_chart([self.business, self.food, self.entertainment])
          expected = "Percentage spent by category\n100|          \n 90|          \n 80|          \n 70|    o     \n 60|    o     \n 50|    o     \n 40|    o     \n 30|    o     \n 20|    o  o  \n 10|    o  o  \n  0| o  o  o  \n    ----------\n     B  F  E  \n     u  o  n  \n     s  o  t  \n     i  d  e  \n     n     r  \n     e     t  \n     s     a  \n     s     i  \n           n  \n           m  \n           e  \n           n  \n           t  "
          self.assertEqual(actual, expected, 'Expected different chart representation. Check that all spacing is exact.')

      def test_create_spend_chart(self):
          self.maxDiff = None

          
  if __name__ == "__main__":
      unittest.main()

This redefines the test_create_spend_chart(), with no assertions, so it can’t fail. Add the self.maxDiff to the existing test.

I’m sorry if I’m not seeing something obvious here, but where exactly to add self.maxDiff in the code? I don’t know what test you’re referring to

test_create_spend_chart(), the one that is failing. You could add it to all of them if you wanted.

Thank you!! Three lines of code were enough to solve all the problems, the test you said helped a lot

Well done, here’s my solution for reference:

from itertools import zip_longest
from typing import List

class Category:
    def __init__(self, name: str) -> None:
        self.name = name
        self.ledger = []
        self.balance = 0

    def __str__(self) -> str:
        return "\n".join([
            self.name.center(30, "*"),
            *(
                "{:23}{:7.2f}".format(
                    trans["description"][:23], trans["amount"]
                )
                for trans in self.ledger
            ),
            f"Total: {self.balance}",
        ])

    def get_balance(self) -> float:
        return self.balance

    def check_funds(self, amount: float) -> bool:
        return amount <= self.balance

    def deposit(self, amount: float, description: str = "") -> None:
        self.ledger.append({"amount": amount, "description": description})
        self.balance += amount

    def withdraw(self, amount: float, description: str = "") -> bool:
        if self.check_funds(amount):
            self.ledger.append({"amount": -amount, "description": description})
            self.balance -= amount
            return True
        else:
            return False

    def transfer(self, amount: float, dest_cat: "Category") -> bool:
        if self.check_funds(amount):
            self.withdraw(amount, f"Transfer to {dest_cat.name}")
            dest_cat.deposit(amount, f"Transfer from {self.name}")
            return True
        else:
            return False

def create_spend_chart(categories: List[Category]) -> str:
    spending = {
        cat.name: sum(
            -trans["amount"] for trans in cat.ledger if trans["amount"] < 0
        )
        for cat in categories
    }
    total_spending = sum(spending.values())
    percentages = {
        cat: spent / total_spending * 100 for cat, spent in spending.items()
    }

    lines = ["Percentage spent by category"]
    for pc in range(100, -1, -10):
        dots = "".join(
            "{}  ".format("o" if percentages[cat.name] >= pc else " ")
            for cat in categories
        )
        lines.append("{:3}| {}".format(pc, dots))

    lines.append(
        "{}-{}".format(
            " "*4, "-"*(3 * len(categories))
        )
    )

    for letters in zip_longest(*spending.keys(), fillvalue=" "):
        lines.append(
            "{}{}".format(" "*5, "".join(f"{letter}  " for letter in letters))
        )

    return "\n".join(lines)

(Don’t worry about the type annotations for now, just know that they make autocomplete a bit more effective)

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.