1

If I "return None" explicitly for exceptions in a regular .py file, when I do the test.py file, is it correct to test with a "assert function(argument) is None?" I did a pytest and it worked fine, but when I did a CS50 check, it gave me:

":( correct fuel.py passes all test_fuel checks
expected exit code 0, not 1"

If I remove the test cases under test_convert_invalid(), CS50 did not give me the exist code 0 prompt, but it gave errors on checks for "ValueError" and "ZeroDivisionError".

Is my assert wrong under def test_convert_invalid()? If it is, why does the test file passed with pytest? How can I fix the issue?

I returned "None" explicitly because return False will be equivalent to 0, which will result in "E" (Empty tank).

`fuel.py`

import sys


def main():
    while True:
        try:
            # convert() expects a str and returns an int
            percentage = convert(input("Fraction: "))

            # gauge() expects an int and returns a str
            print(gauge(percentage))
        except:
            continue
        else:
            sys.exit()


def convert(fraction):
    try:
        x, y = fraction.split("/")

        if not x[:].isdigit() or not y[:].isdigit():
            raise TypeError

        if y == "0":
            raise ZeroDivisionError

        if int(x) > int(y) and int(y) != 0:
            raise ValueError

    except TypeError:
        return None
    except ZeroDivisionError:
        return None
    except ValueError:
        return None
    else:
        return int((float(x) / float(y)) * 100)


def gauge(percentage):
    if 0 <= percentage <= 1:
        return "E"
    elif percentage >= 99:
        return "F"
    else:
        return str(percentage) + "%"


if __name__ == "__main__":
    main()



`test_fuel.py`

from fuel import convert, gauge

def test_convert_valid():
    assert convert("3/4") == 75
    assert convert("100/100") == 100
    assert convert("99/100") == 99
    assert convert("0/100") == 0
    assert convert("1/100") == 1


def test_convert_invalid():
    assert convert("1/0") is None
    assert convert("4/3") is None
    assert convert("test") is None
    assert convert("t/1") is None
    assert convert("1/t") is None


def test_gauge():
    assert gauge(0) == "E"
    assert gauge(1) == "E"
    assert gauge(100) == "F"
    assert gauge(99) == "F"
    assert gauge(75) == "75%"

3 Answers 3

5

Please have A look at my code, please upvote if helps,your code a kind of complicated. I tested with check50 as well, All Passed. fuel.py

def main():
    fraction = input("Fraction: ")
    result = gauge(convert(fraction))
    print(result)

def convert(fraction):
    x,y = fraction.split("/")
    try:
        return (int(x)/int(y)) * 100
    except (ValueError,ZeroDivisionError):
        raise

def gauge(percentage):
    if 0 <= percentage <= 100:
        if percentage >= 99:
            return "F"
        elif percentage <= 1:
            return "E"
        else:
            return f"{percentage:.0f}%"

if __name__ == "__main__":
    main()

test_fuel.py

from fuel import convert,gauge
import pytest

def test_convert():
    assert convert("1/2") == 50
    assert convert("1/4") == 25
    with pytest.raises(ValueError):
        convert("cat/dog")
    with pytest.raises(ZeroDivisionError):
        convert("2/0")
def test_gauge():
    assert gauge(50) == "50%"
    assert gauge(100) == "F"
    assert gauge(0) == "E"
    assert gauge(1) == "E"
    assert gauge(99) == "F"
3
  • Hello, How did you handle the case when x is greater than y. I don't see it covered in your code. Luckily this case was not covered by the check50 even though the condition is clearly mentioned in the exercise. What are your thoughts on this?
    – helpme_pls
    Commented Feb 25, 2023 at 12:09
  • @helpme_pls i think he opt not to handle it in 'convert()' and simply passed the value to 'gauge' where it was handled by the first condition '0 <= percentage <= 100'. Technically(imo), the code is wrong since 'convert()' is supposed to handle it. If another function (relying on pset as the documentation) is to call 'convert()' it may prompt an error and he has to rewrite the code but since its only 'gauge()' asking for it, then i think its probably fine.
    – fart_east
    Commented Feb 26, 2023 at 6:27
  • @fart_east Thanks. I have written the right code in the answer section which covers the case of y > x and raises a ValueError.
    – helpme_pls
    Commented Feb 27, 2023 at 13:03
1

@souttawther has a small fallacy in his answer, It does not take into consideration the case when y > x and prints None, which is wrong as per the problem description in the course website, ideally it must raise a value error, though this isn't covered by the check50. So here is my version of the code which covers that corner case as well.

fuel.py

def main():
    inp = input("Fraction:")
    percentage = convert(inp)

    print(gauge(percentage))

def convert(fraction):


    x,y = fraction.split("/")

    try:
        x = int(x)
        y = int(y)
        print(x,y)
    except ValueError:
            raise

    try:
        if x <= y and y!=0:
            try:
                return round((x/y)*100)
            except (ZeroDivisionError, ValueError):
                raise
        else:
            if y == 0:
                raise ZeroDivisionError
            else:
                raise ValueError
    except (ZeroDivisionError, ValueError):
        raise

def gauge(percentage):

    if percentage >= 99:
        return("F")
    elif percentage <= 1:
        return("E")
    else:
        return(f"{percentage}%")


if __name__ == "__main__":
    main()

test_fuel.py

from fuel import convert
from fuel import gauge
import pytest

def test_gauge():
    assert gauge(100) == "F"
    assert gauge(99) == "F"
    assert gauge(98) == "98%"
    assert gauge(1) == "E"
    assert gauge(2) == "2%"
    assert gauge(0) == "E"

def test_convert():
    assert convert("10/10") == 100
    assert convert("1/100") == 1
    assert convert("1/1000") == 0
    with pytest.raises(ValueError):
        convert("5/3")
    with pytest.raises(ZeroDivisionError):
        convert("1/0")
    with pytest.raises(ZeroDivisionError):
        convert("7/0")
    with pytest.raises(ValueError):
        convert("3/cat")
    with pytest.raises(ValueError):
        convert("bat/cat")

Please feel free to optimize this code as I'm not very good at it :) Thanks.

0

When there is an Exception, you want to return the exception, so your code can handle it properly. You don't want to return None; that is meaningless to the calling function. Google try/except/raise. You should find examples like this:

try:
    1 or more statements
except:
    raise # passes the exception back

A bare raise statement re-raises the current exception in the exception handler, so it can handled further up the call stack (e.g. the calling function). See these SO articles:

When trying to learn a new code feature, I write simple examples to understand the behavior. Take a look at this. It shows how a bare raise statement in function() is re-raised to main(). Once you understand how it works, you can adapt to your project. Good luck!

def main():
    test_list = [('1', '2'), ('e', '2'), ('1', '0'), ('4.4', '2')  ]
    for x,y in test_list:
        try:
            z = function(x,y)
            print(f'For x={x} and y={y}; z={z}\n')
        except ValueError:
            print(f'For x={x} and y={y}; ValueError raised by function\n')
        except ZeroDivisionError:
            print(f'For x={x} and y={y}; ZeroDivisionError raised by function\n')       
            
def function(x,y):
    try:
        z = int(x)/int(y)
        return z
    except Exception as e:
        print(f'Exception raised in function: {e}')
        raise # raises the exception to calling function -> main()

if __name__ == "__main__":
    main()
    
5
  • thanks for the suggestions. I previously had the correct error message printed for each type of errors (e.g. TypeError, print("Enter a fraction with two integer"), but the CS50 problem requires us not to do any print statement, but rather re-prompt the user for an input if what they either didn't meet the input specifications. I also am aware by default, it'll return "None" if the exception is not specified, I explicitly returned "None" as I'll need some action under "exceptions", I guess another alternative is to use "pass". Will keep digging for a fix. Thanks.
    – TimL
    Commented May 26, 2022 at 6:56
  • The code in my answer does not return None. The bare raise statement re-raises the exception from the try/except. So if your statement was x = 1./0., it would throw a ZeroDivisionError exception, pass it to the except block which will raise that exception back to the calling program. Try it and see. (FYI, I am also doing CS50 for Python, so have done these psets.)
    – kcw78
    Commented May 26, 2022 at 12:33
  • If anyone else comes across this, follow the pytest documentation for checking for exceptions. docs.pytest.org/en/latest/how-to/… Commented Jun 15, 2022 at 17:19
  • I figured this out. It's a bit misleading. Your fuel.py code is irrelevant and it doesn't matter (though you still want pytest to pass it it). It's what they are looking for. Only ONE assert for the check_convert function. "3/4" == 75 is just fine. Once you introduce more asserts to test, it fails with exit code 1. I used separate functions for gauge (obv) , zero error, and str error, and it comes back 100%!
    – KptKrunch
    Commented Sep 26, 2022 at 17:53
  • This last comment saved my time which I would have wasted just because everything was correct and I couldn't figure out why it was returning 1. Thanks! Commented Apr 23, 2023 at 0:04

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .