[CS50's - Introduction to Python] Unit Tests
In this post I will show how to solve the problems from topic 4 - Unit Tests of CS50’s - Introduction to Python course.
Table of Contents
Testing my twttr
In this problem we will reimplement Setting up my twttr from Problem Set 2, restructuring our code per the below, wherein shorten expects a str as input and returns that same str but with all vowels (A, E, I, O, and U) omitted, whether inputted in uppercase or lowercase.
def main():
...
def shorten(word):
...
if __name__ == "__main__":
main()
I reimplemented Setting up my twttr as follows:
def main():
text = input("Input: ")
print(shorten(text))
def shorten(word):
str = ""
for char in word:
if (char.lower() not in "aeiou"):
str += char
return str
if __name__ == "__main__":
main()
Now we will implement the test file. It will test the our implementation of the shorten
function. For this I creted some functions inside test_twttr
file each of whose names begins with test_ so that I could execute my tests with:
pytest test_twttr.py
To use pytest we must intall it:
pip install -U pytest
The program to test the shorten
function will be as follows:
The assert is used to compare result between the value result from function and expected value.
import twttr
def main():
test_twttr()
test_number()
test_punctuation()
def test_twttr():
assert twttr.shorten("twitter") == "twttr"
assert twttr.shorten("TEXT") == "TXT"
assert twttr.shorten("LaTeX") == "LTX"
def test_number():
assert twttr.shorten("123") == "123"
def test_punctuation():
assert twttr.shorten(",.?") == ",.?"
if __name__ == "__main__":
main()
Back to the Bank
In this problem we will reimplement Home Federal Savings Bank restructuring this code per the below, wherein value
expects a str as input and returns an int, namely 0 if that str starts with “hello”, 20 if that str starts with an “h” (but not “hello”), or 100 otherwise, treating the str case-insensitively. We can assume that the string passed to the value function will not contain any leading spaces. Only main should call print.
def main():
...
def value(greeting):
...
if __name__ == "__main__":
main()
In this case I reimplemented Home Federal Savings Bank as per the bellow:
def main():
greetings = input("Greeting: ")
print(f"${value(greetings)}")
def value(greeting):
greeting = greeting.strip()
greeting = str.lower(greeting)
if greeting.startswith("hello"):
return 0
elif greeting.startswith("h"):
return 20
else:
return 100
if __name__ == "__main__":
main()
Now we create a test file to test the value
function:
To use pytest we must intall it:
pip install -U pytest
- test_hello: Testes the word “hello” to be case-insensitive.
- test_h: testes words that starts with “h”.
- test_others: tests words that starts with other character than “h”.
The assert is used to compare result between the value result from function and expected value.
import bank
def main():
test_hello()
test_h()
test_others()
def test_hello():
assert bank.value("hello") == 0
assert bank.value("Hello") == 0
assert bank.value("HELLO") == 0
def test_h():
assert bank.value("hi") == 20
assert bank.value("How are you") == 20
assert bank.value("H") == 20
assert bank.value("h") == 20
def test_others():
assert bank.value("Whats your name?") == 100
assert bank.value("Just saying hi") == 100
if __name__ == "__main__":
main()
Re-requesting a Vanity Plate
This problem asks to reimplement Vanity Plates restructuring our code per the below, wherein is_valid
still expects a str as input and returns True if that str meets all requirements and False if it does not, but main is only called if the value of __name__
is "__main__"
:
def main():
...
def is_valid(s):
...
if __name__ == "__main__":
main()
My reimplementation of Vanity Plates is as follows:
def main():
plate = input("Plate: ")
if is_valid(plate):
print("Valid")
else:
print("Invalid")
def is_valid(s):
size = len(s)
if size < 2 or size > 6:
return False
for i in range(2):
if (s[i].isalpha() != True):
return False
for i in range(size):
if i < size - 1:
j = i + 1
if s[i].isalnum() != True:
return False
# Verify if after the number has a letter
if s[i].isnumeric() and s[j].isalpha():
return False
# Verify if the first number is zero
count = 1
while s[count].isnumeric() != True and count < size - 1:
if s[count + 1] == '0':
return False
count += 1
return True
if __name__ == "__main__":
main()
The program that tests the function is_valid
will then be:
The assert is used to compare result between the value result from function and expected value.
import plates
def test_valid():
assert plates.is_valid("CS50") == True
def test_starts_with_0():
assert plates.is_valid("CS05") == False
def test_number_in_middle():
assert plates.is_valid("CS50P") == False
def test_puctuation():
assert plates.is_valid("PI3.14") == False
def test_one_letter():
assert plates.is_valid("H") == False
def test_more_than_six_digits():
assert plates.is_valid("OUTATIME") == False
def test_two_first_not_letter():
assert plates.is_valid("1CS5") == False
assert plates.is_valid("C123") == False
assert plates.is_valid("1234") == False
Refueling
In this project we must reimplement Fuel Gauge from Problem Set 3, restructuring our code per the below, wherein:
convert
expects a str in X/Y format as input, wherein each of X and Y is an integer, and returns that fraction as a percentage rounded to the nearest int between 0 and 100, inclusive. If X and/or Y is not an integer, or if X is greater than Y, thenconvert
should raise aValueError
. If Y is 0, then convert should raise aZeroDivisionError
.gauge
expects an int and returns a str that is:- “E” if that int is less than or equal to 1,
- “F” if that int is greater than or equal to 99,
- and “Z%” otherwise, wherein Z is that same int.
def main():
...
def convert(fraction):
...
def gauge(percentage):
...
if __name__ == "__main__":
main()
My reimplementation for this is:
def main():
while True:
try:
n = input("Fraction: ")
fuel = convert(n)
except (ValueError, ZeroDivisionError):
pass
else:
break
print(gauge(fuel))
def convert(fraction):
numerator, denominator = fraction.split('/')
numerator = int(numerator)
denominator = int(denominator)
if denominator == 0:
raise ZeroDivisionError
percentage = round(numerator / denominator * 100)
if percentage < 0 or percentage > 100:
raise ValueError
else:
return percentage
def gauge(percentage):
if percentage <= 1:
return "E"
elif percentage >= 99:
return "F"
else:
return f"{percentage}%"
if __name__ == "__main__":
main()
The program that tests the function convert
and gauge
will then be:
We import pytest to use the method raises to raise the errors.
import fuel
import pytest
def test_convert():
assert fuel.convert("3/4") == 75
assert fuel.convert("1/4") == 25
assert fuel.convert("4/4") == 100
assert fuel.convert("0/4") == 0
def test_errors():
with pytest.raises(ZeroDivisionError):
fuel.convert("4/0")
with pytest.raises(ValueError):
fuel.convert("three/four")
with pytest.raises(ValueError):
fuel.convert("1.5/3")
def test_gauge():
assert fuel.gauge(75) == "75%"
assert fuel.gauge(33) == "33%"
assert fuel.gauge(0) == "E"
assert fuel.gauge(1) == "E"
assert fuel.gauge(99) == "F"
assert fuel.gauge(100) == "F"
The assert is used to compare result between the value result from function and expected value.
This is all for week five - Unit Tests, set of problems of CS50’s Introduction to Programming with Python course.