Skip to content

Commit 3a909a3

Browse files
committed
code and requirements for pytest
1 parent 8ce5999 commit 3a909a3

File tree

5 files changed

+206
-0
lines changed

5 files changed

+206
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
def fizzbuzz(n):
2+
if n % 3 == 0 and n % 5 == 0:
3+
return 'Fizz Buzz'
4+
if n % 3 == 0:
5+
return 'Fizz'
6+
if n % 5 == 0:
7+
return 'Buzz'
8+
return n
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import pytest
2+
3+
from fizzbuzz import fizzbuzz
4+
5+
@pytest.mark.parametrize("arg, ret",[
6+
(1, 1),
7+
(2, 2),
8+
(3, 'Fizz'),
9+
(4, 4),
10+
(5, 'Buzz'),
11+
(6, 'Fizz'),
12+
(7, 7),
13+
(8, 8),
14+
(9, 'Fizz'),
15+
(10, 'Buzz'),
16+
(11, 11),
17+
(12, 'Fizz'),
18+
(13, 13),
19+
(14, 14),
20+
(15, 'Fizz Buzz'),
21+
(16, 16),
22+
])
23+
def test_fizzbuzz(arg, ret):
24+
assert fizzbuzz(arg) == ret

days/10-12-pytest/guess/guess.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import random
2+
3+
MAX_GUESSES = 5
4+
START, END = 1, 20
5+
6+
7+
def get_random_number():
8+
"""Get a random number between START and END, returns int"""
9+
return random.randint(START, END)
10+
11+
12+
class Game:
13+
"""Number guess class, make it callable to initiate game"""
14+
15+
def __init__(self):
16+
"""Init _guesses, _answer, _win to set(), get_random_number(), False"""
17+
self._guesses = set()
18+
self._answer = get_random_number()
19+
self._win = False
20+
21+
def guess(self):
22+
"""Ask user for input, convert to int, raise ValueError outputting
23+
the following errors when applicable:
24+
'Please enter a number'
25+
'Should be a number'
26+
'Number not in range'
27+
'Already guessed'
28+
If all good, return the int"""
29+
guess = input(f'Guess a number between {START} and {END}: ')
30+
if not guess:
31+
raise ValueError('Please enter a number')
32+
33+
try:
34+
guess = int(guess)
35+
except ValueError:
36+
raise ValueError('Should be a number')
37+
38+
if guess not in range(START, END+1):
39+
raise ValueError('Number not in range')
40+
41+
if guess in self._guesses:
42+
raise ValueError('Already guessed')
43+
44+
self._guesses.add(guess)
45+
return guess
46+
47+
def _validate_guess(self, guess):
48+
"""Verify if guess is correct, print the following when applicable:
49+
{guess} is correct!
50+
{guess} is too high
51+
{guess} is too low
52+
Return a boolean"""
53+
if guess == self._answer:
54+
print(f'{guess} is correct!')
55+
return True
56+
else:
57+
high_or_low = 'low' if guess < self._answer else 'high'
58+
print(f'{guess} is too {high_or_low}')
59+
return False
60+
61+
@property
62+
def num_guesses(self):
63+
return len(self._guesses)
64+
65+
def __call__(self):
66+
"""Entry point / game loop, use a loop break/continue,
67+
see the tests for the exact win/lose messaging"""
68+
while len(self._guesses) < MAX_GUESSES:
69+
try:
70+
guess = self.guess()
71+
except ValueError as ve:
72+
print(ve)
73+
continue
74+
75+
win = self._validate_guess(guess)
76+
if win:
77+
guess_str = self.num_guesses == 1 and "guess" or "guesses"
78+
print(f'It took you {self.num_guesses} {guess_str}')
79+
self._win = True
80+
break
81+
else:
82+
# else on while/for = anti-pattern? do find it useful in this case!
83+
print(f'Guessed {MAX_GUESSES} times, answer was {self._answer}')
84+
85+
86+
if __name__ == '__main__':
87+
game = Game()
88+
game()

days/10-12-pytest/guess/test_guess.py

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
from unittest.mock import patch
2+
import random
3+
4+
import pytest
5+
6+
from guess import get_random_number, Game
7+
8+
9+
@patch.object(random, 'randint')
10+
def test_get_random_number(m):
11+
m.return_value = 17
12+
assert get_random_number() == 17
13+
14+
15+
@patch("builtins.input", side_effect=[11, '12', 'Bob', 12, 5,
16+
-1, 21, 7, None])
17+
def test_guess(inp):
18+
game = Game()
19+
# good
20+
assert game.guess() == 11
21+
assert game.guess() == 12
22+
# not a number
23+
with pytest.raises(ValueError):
24+
game.guess()
25+
# already guessed 12
26+
with pytest.raises(ValueError):
27+
game.guess()
28+
# good
29+
assert game.guess() == 5
30+
# out of range values
31+
with pytest.raises(ValueError):
32+
game.guess()
33+
with pytest.raises(ValueError):
34+
game.guess()
35+
# good
36+
assert game.guess() == 7
37+
# user hit enter
38+
with pytest.raises(ValueError):
39+
game.guess()
40+
41+
42+
def test_validate_guess(capfd):
43+
game = Game()
44+
game._answer = 2
45+
46+
assert not game._validate_guess(1)
47+
out, _ = capfd.readouterr()
48+
assert out.rstrip() == '1 is too low'
49+
50+
assert not game._validate_guess(3)
51+
out, _ = capfd.readouterr()
52+
assert out.rstrip() == '3 is too high'
53+
54+
assert game._validate_guess(2)
55+
out, _ = capfd.readouterr()
56+
assert out.rstrip() == '2 is correct!'
57+
58+
59+
@patch("builtins.input", side_effect=[4, 22, 9, 4, 6])
60+
def test_game_win(inp, capfd):
61+
game = Game()
62+
game._answer = 6
63+
64+
game()
65+
assert game._win is True
66+
67+
out = capfd.readouterr()[0]
68+
expected = ['4 is too low', 'Number not in range',
69+
'9 is too high', 'Already guessed',
70+
'6 is correct!', 'It took you 3 guesses']
71+
72+
output = [line.strip() for line
73+
in out.split('\n') if line.strip()]
74+
for line, exp in zip(output, expected):
75+
assert line == exp
76+
77+
78+
@patch("builtins.input", side_effect=[None, 5, 9, 14, 11, 12])
79+
def test_game_lose(inp, capfd):
80+
game = Game()
81+
game._answer = 13
82+
83+
game()
84+
assert game._win is False

days/10-12-pytest/requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pytest
2+
pytest-cov

0 commit comments

Comments
 (0)