Skip to content

Commit 72843c0

Browse files
takato1314regisb
authored andcommitted
refactor: add code coverage, cover CLI commands with tests
1 parent dbb79c0 commit 72843c0

36 files changed

+881
-92
lines changed

.coveragerc

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# .coveragerc to control coverage.py
2+
[run]
3+
branch = True
4+
source =
5+
./tutor
6+
./bin
7+
omit =
8+
*/templates/*
9+
10+
[report]
11+
# Regexes for lines to exclude from consideration
12+
exclude_lines =
13+
# Have to re-enable the standard pragma
14+
pragma: no cover
15+
16+
# Don't complain about missing debug-only code:
17+
def __repr__
18+
if self\.debug
19+
20+
# Don't complain if tests don't hit defensive assertion code:
21+
raise AssertionError
22+
raise NotImplementedError
23+
24+
# Don't complain if non-runnable code isn't run:
25+
if 0:
26+
if __name__ == .__main__.:
27+
28+
# Don't complain about abstract methods, they aren't run:
29+
@(abc\.)?abstractmethod
30+
31+
ignore_errors = True
32+
show_missing = True
33+
skip_empty = True
34+
precision = 2
35+
36+
[html]
37+
skip_empty = True
38+
show_contexts = True
39+
40+
[json]
41+
pretty_print = True
42+
show_contexts = True

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ __pycache__
77
/build/
88
/dist/
99
/release_description.md
10+
11+
# Unit test/ coverage reports
12+
.coverage
13+
/htmlcov/

Makefile

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
.DEFAULT_GOAL := help
22
.PHONY: docs
3-
SRC_DIRS = ./tutor ./tests ./bin
3+
SRC_DIRS = ./tutor ./tests ./bin ./docs
44
BLACK_OPTS = --exclude templates ${SRC_DIRS}
55

66
###### Development
@@ -53,6 +53,23 @@ bootstrap-dev: ## Install dev requirements
5353
bootstrap-dev-plugins: bootstrap-dev ## Install dev requirement and all supported plugins
5454
pip install -r requirements/plugins.txt
5555

56+
###### Code coverage
57+
58+
coverage: ## Run unit-tests before analyzing code coverage and generate report
59+
$(MAKE) --keep-going coverage-tests coverage-report
60+
61+
coverage-tests: ## Run unit-tests and analyze code coverage
62+
coverage run -m unittest discover
63+
64+
coverage-report: ## Generate CLI report for the code coverage
65+
coverage report
66+
67+
coverage-html: coverage-report ## Generate HTML report for the code coverage
68+
coverage html
69+
70+
coverage-browse-report: coverage-html ## Open the HTML report in the browser
71+
sensible-browser htmlcov/index.html
72+
5673
###### Deployment
5774

5875
bundle: ## Bundle the tutor package in a single "dist/tutor" executable

bin/main.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/usr/bin/env python3
22
from tutor.plugins import OfficialPlugin
3+
from tutor.commands.cli import main
34

45
# Manually install plugins (this is for creating the bundle)
56
for plugin_name in [
@@ -20,6 +21,5 @@
2021
except ImportError:
2122
pass
2223

23-
from tutor.commands.cli import main
24-
25-
main()
24+
if __name__ == "__main__":
25+
main()

docs/conf.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import io
22
import os
33
import sys
4+
from typing import Any, Dict, List
45

56
import docutils
67
import docutils.parsers.rst
@@ -28,7 +29,7 @@
2829

2930
# -- Sphinx-Click configuration
3031
# https://sphinx-click.readthedocs.io/
31-
extensions.append('sphinx_click')
32+
extensions.append("sphinx_click")
3233
# This is to avoid the addition of the local username to the docs
3334
os.environ["HOME"] = "~"
3435
# Make sure that sphinx-click can find the tutor module
@@ -63,7 +64,7 @@
6364

6465
# Custom variables
6566
here = os.path.abspath(os.path.dirname(__file__))
66-
about = {}
67+
about: Dict[str, str] = {}
6768
with io.open(
6869
os.path.join(here, "..", "tutor", "__about__.py"), "rt", encoding="utf-8"
6970
) as f:
@@ -77,17 +78,17 @@
7778

7879
# Custom directives
7980
def youtube(
80-
_name,
81-
_args,
82-
_options,
83-
content,
84-
_lineno,
85-
_contentOffset,
86-
_blockText,
87-
_state,
88-
_stateMachine,
89-
):
90-
""" Restructured text extension for inserting youtube embedded videos """
81+
_name: Any,
82+
_args: Any,
83+
_options: Any,
84+
content: List[str],
85+
_lineno: Any,
86+
_contentOffset: Any,
87+
_blockText: Any,
88+
_state: Any,
89+
_stateMachine: Any,
90+
) -> Any:
91+
"""Restructured text extension for inserting youtube embedded videos"""
9192
if not content:
9293
return []
9394
video_id = content[0]

requirements/dev.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pip-tools
44
pylint
55
pyinstaller
66
twine
7+
coverage
78

89
# Types packages
910
types-PyYAML

requirements/dev.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ click==8.0.3
3636
# pip-tools
3737
colorama==0.4.4
3838
# via twine
39+
coverage==6.2
40+
# via -r requirements/dev.in
3941
cryptography==35.0.0
4042
# via secretstorage
4143
docutils==0.17.1

setup.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import io
22
import os
33
from setuptools import find_packages, setup
4+
from typing import Dict, List
45

56
HERE = os.path.abspath(os.path.dirname(__file__))
67

78

8-
def load_readme():
9+
def load_readme() -> str:
910
with io.open(os.path.join(HERE, "README.rst"), "rt", encoding="utf8") as f:
1011
readme = f.read()
1112
# Replace img src for publication on pypi
@@ -14,23 +15,22 @@ def load_readme():
1415
)
1516

1617

17-
def load_about():
18-
about = {}
18+
def load_about() -> Dict[str, str]:
19+
about: Dict[str, str] = {}
1920
with io.open(
2021
os.path.join(HERE, "tutor", "__about__.py"), "rt", encoding="utf-8"
2122
) as f:
2223
exec(f.read(), about) # pylint: disable=exec-used
2324
return about
2425

2526

26-
def load_requirements(filename: str):
27+
def load_requirements(filename: str) -> List[str]:
2728
with io.open(
2829
os.path.join(HERE, "requirements", filename), "rt", encoding="utf-8"
2930
) as f:
3031
return [line.strip() for line in f if is_requirement(line)]
3132

32-
33-
def is_requirement(line):
33+
def is_requirement(line: str) -> bool:
3434
return not (line.strip() == "" or line.startswith("#"))
3535

3636

@@ -72,4 +72,5 @@ def is_requirement(line):
7272
"Programming Language :: Python :: 3.9",
7373
"Programming Language :: Python :: 3.10",
7474
],
75+
test_suite="tests",
7576
)

tests/commands/__init__.py

Whitespace-only changes.

tests/commands/test_cli.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import unittest
2+
3+
from click.testing import CliRunner
4+
5+
from tutor.commands.cli import cli, print_help
6+
7+
8+
class CliTests(unittest.TestCase):
9+
def test_help(self) -> None:
10+
runner = CliRunner()
11+
result = runner.invoke(print_help)
12+
self.assertEqual(0, result.exit_code)
13+
self.assertIsNone(result.exception)
14+
15+
def test_cli_help(self) -> None:
16+
runner = CliRunner()
17+
result = runner.invoke(cli, ["--help"])
18+
self.assertEqual(0, result.exit_code)
19+
self.assertIsNone(result.exception)
20+
21+
def test_cli_version(self) -> None:
22+
runner = CliRunner()
23+
result = runner.invoke(cli, ["--version"])
24+
self.assertEqual(0, result.exit_code)
25+
self.assertIsNone(result.exception)
26+
self.assertRegex(result.output, r"cli, version \d+.\d+.\d+\n")

0 commit comments

Comments
 (0)