Skip to content

Commit

Permalink
0.1.3 automated with tox and 97% coverage
Browse files Browse the repository at this point in the history
* bump version to 0.1.3
* __init__.py refactor code into testable functions
* test_mhered_test_pkg.py add tests to increase coverage to 97%
* Update README.md
* Update CHANGELOG.md
  • Loading branch information
mhered committed Aug 2, 2022
1 parent d6670c4 commit 91c4aa1
Show file tree
Hide file tree
Showing 9 changed files with 197 additions and 15 deletions.
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
<a id='changelog-0.1.3'></a>

# 0.1.3 — 2022-08-03

## Added

- Automated formatting, linting, testing and coverage with tox
- README.md sections on tox and coverage

- Tests coverage 97%

## Changed

- Refactor code into testable functions

<a id='changelog-0.1.2'></a>
# 0.1.2 — 2022-08-01
Expand Down
46 changes: 44 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,6 @@ Then, to create a release:
- collect all fragments to `CHANGELOG.md`
- commit changes to `pyproject.toml README.md CHANGELOG.md mhered_test_pkg/__init__.py`
- create tag
- create a release with
- push commit and tag

```bash
Expand All @@ -517,6 +516,8 @@ $ git tag -a 0.1.2 -m "Add tests"
$ git push origin 0.1.2
```

* try to create a release with `scriv github-release`

This time `$ scriv github-release -v DEBUG` gives an error message so I rename tag `0.1.2` to `v0.1.2`:

```bash
Expand Down Expand Up @@ -549,7 +550,7 @@ warning: Version 0.1.1 has no tag. No release will be made.
warning: Version 0.1.0 has no tag. No release will be made.
```

Apparently this is because I need to add my PAT as environment variable `GITHUB_TOKEN`, see [scriv docs](https://scriv.readthedocs.io/en/latest/commands.html#scriv-github-release). I tried though, and it does not work...
Apparently this is because I need to add my PAT as environment variable `GITHUB_TOKEN`, see [scriv docs](https://scriv.readthedocs.io/en/latest/commands.html#scriv-github-release). I tried though, and it does not work... I create the release manually in github.

### Automating with `tox`

Expand Down Expand Up @@ -648,3 +649,44 @@ We can execute the checks in a more nuanced way including all files in the packa
$ coverage run --source=mhered_test_pkg --branch -m pytest .
$ coverage html
```

![22pc_coverage](assets/22pc_coverage.png)

To add `coverage` to `tox` modify `tox.ini` to add the relevant lines:

```toml
# tox (https://tox.readthedocs.io/) is a tool for running tests
# in multiple virtualenvs. This configuration file will run the
# test suite on all supported python versions. To use it, "pip install tox"
# and then run "tox" from this directory.

[tox]
isolated_build = True
envlist = py38

[testenv]
deps =
toml
black
flake8
isort
mccabe
pylint
pytest
coverage # development dependency

commands =
black --check mhered_test_pkg
isort --check mhered_test_pkg
flake8 mhered_test_pkg --max-complexity 10
pylint mhered_test_pkg
coverage run --source=mhered_test_pkg --branch -m pytest . # execute
coverage report -m --fail-under 90 # report & fail below 90%

```

Added tests to increase coverage up to 97% - i.e. all lines of code covered except the case `"__name__" == "__main__:"` because tests import as module.

![97pc_coverage](assets/97pc_coverage.png)

In the process I learned about monkeypatching user input, using iterators to simulate a sequence of inputs, or testing for sys exit, see references in [./tests/test_mhered_test_pkg.py](./tests/test_mhered_test_pkg.py)
Binary file added assets/22pc_coverage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/97pc_coverage.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 3 additions & 8 deletions mhered_test_pkg/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""A simple Rock Paper Scissors game"""

__version__ = "0.1.2"
__version__ = "0.1.3"

import random
import sys
Expand Down Expand Up @@ -49,17 +49,12 @@ def compare_moves(player_move, computer_move):
(player_move == "p" and computer_move == "r"),
(player_move == "s" and computer_move == "p"),
]
computer_wins = [
(player_move == "s" and computer_move == "r"),
(player_move == "r" and computer_move == "p"),
(player_move == "p" and computer_move == "s"),
]

if player_move == computer_move:
outcome = "tie"
elif any(player_wins):
outcome = "win"
elif any(computer_wins):
else:
outcome = "lose"
return outcome

Expand Down Expand Up @@ -92,7 +87,7 @@ def rock_paper_scissors():
elif outcome == "lose":
print("You lose!")
losses = losses + 1
elif outcome == "tie":
else: # outcome == "tie":
print("It is a tie!")
ties = ties + 1

Expand Down
19 changes: 18 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "mhered-test-pkg"
version = "0.1.2"
version = "0.1.3"
description = "A simple demo package to practice how to create python packages."
authors = ["Manuel Heredia <manolo.heredia@gmail.com>"]
readme = "README.md"
Expand All @@ -16,6 +16,7 @@ tox = "^3.25.1"
toml = "^0.10.2"
black = "^22.6.0"
coverage = "^6.4.2"
mock = "^4.0.3"

[build-system]
requires = ["poetry-core>=1.0.0"]
Expand Down
113 changes: 112 additions & 1 deletion tests/test_mhered_test_pkg.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
from pathlib import Path

import toml
from pytest import raises

from mhered_test_pkg import __version__, move_msg
from mhered_test_pkg import (
__version__,
compare_moves,
computer_play,
move_msg,
player_play,
rock_paper_scissors,
)


def test_versions_are_in_sync():
Expand Down Expand Up @@ -31,3 +39,106 @@ def test_move_msg_p():
def test_move_msg_s():
"""Checks message for s is SCISSORS"""
assert move_msg("s") == "SCISSORS"


def test_compare_moves():
"""Checks outcome for all move pairs"""
cases = [
{
"player": "r",
"computer": "s",
"outcome": "win",
},
{
"player": "r",
"computer": "p",
"outcome": "lose",
},
{
"player": "r",
"computer": "r",
"outcome": "tie",
},
{
"player": "p",
"computer": "r",
"outcome": "win",
},
{
"player": "p",
"computer": "s",
"outcome": "lose",
},
{
"player": "p",
"computer": "p",
"outcome": "tie",
},
{
"player": "s",
"computer": "p",
"outcome": "win",
},
{
"player": "s",
"computer": "r",
"outcome": "lose",
},
{
"player": "s",
"computer": "s",
"outcome": "tie",
},
]

for case in cases:
assert compare_moves(case["player"], case["computer"]) == case["outcome"]


def test_computer_play():
"""Test computer plays only 'r' 'p' 's'"""
for i in range(100):
assert computer_play() in ["r", "p", "s"]


def test_player_play_returns_r_p_s(monkeypatch):
"""Player_play() returns r p s"""

# Adapted from stack overflow: monkeypatching user input
# https://stackoverflow.com/questions/52248499/pytest-how-to-test-a-separate-function-with-input-call
# monkeypatch the "input" function, so that it returns "r".
# This simulates the user entering "r" in the terminal:

for user_input in ["r", "s", "p"]:
monkeypatch.setattr("builtins.input", lambda prompt="": user_input)
# go about using input() like you normally would:
player_move = player_play()
assert player_move == user_input


def test_player_play_exits_with_q(monkeypatch):
"""Player_play exits with q"""
# Adapted from python-pandemonium: testing for sys exit
# https://medium.com/python-pandemonium/testing-sys-exit-with-pytest-10c6e5f7726f

with raises(SystemExit) as pytest_wrapped_e:
monkeypatch.setattr("builtins.input", lambda prompt="": "q")
player_move = player_play()
assert pytest_wrapped_e.type == SystemExit
# assert pytest_wrapped_e.value.code == 42


def test_rock_paper_scissors_overall(monkeypatch):
"""Full run"""
# Adapted from stack overflow: iterator to simulate a sequence of inputs
# https://stackoverflow.com/questions/53472142/pytest-user-input-simulation

# creating iterator object
user_inputs = iter(
["r", "s", "p", "r", "s", "p", "r", "s", "p", "r", "s", "p", "a", "q"]
)

with raises(SystemExit) as pytest_wrapped_e:
monkeypatch.setattr("builtins.input", lambda prompt="": next(user_inputs))
rock_paper_scissors()
assert pytest_wrapped_e.type == SystemExit
6 changes: 4 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@ deps =
mccabe
pylint
pytest
coverage

commands =
black --check mhered_test_pkg
isort --check mhered_test_pkg
flake8 mhered_test_pkg --max-complexity 10
pylint mhered_test_pkg
pytest .
black --check mhered_test_pkg
coverage run --source=mhered_test_pkg --branch -m pytest .
coverage report -m --fail-under 60

0 comments on commit 91c4aa1

Please sign in to comment.