Add a testing tool chain

- use pytest as the base, measure coverage with pytest-cov
  + configure coverage to include branches and specify source locations
  + configure pytest to enforce explicit markers
- add a package for the test suite under tests/
- add a `__version__` identifier at the package's root
  + it is dynamically assigned the version of the installed package
  + the version is PEP440 compliant and follows a strict subset of
    semantic versioning: x.y.z[.devN] where x, y, z, and N are all
    non-negative integers
  + add module with tests for the __version__
- add a nox session "test" that runs the test suite
- use flake8 to lint pytest for consistent style
This commit is contained in:
Alexander Hess 2020-08-04 00:09:29 +02:00
parent c7989e0040
commit 9fc5b4816a
Signed by: alexander
GPG key ID: 344EA5AB10D868E0
7 changed files with 406 additions and 3 deletions

View file

@ -14,8 +14,11 @@ MAIN_PYTHON = '3.8'
# Keep the project is forward compatible.
NEXT_PYTHON = '3.9'
# Path to the test suite.
PYTEST_LOCATION = 'tests/'
# Paths with *.py files.
SRC_LOCATIONS = 'noxfile.py', 'src/'
SRC_LOCATIONS = 'noxfile.py', 'src/', PYTEST_LOCATION
# Use a unified .cache/ folder for all tools.
@ -70,6 +73,7 @@ def lint(session: Session) -> None:
'flake8-annotations',
'flake8-black',
'flake8-expression-complexity',
'flake8-pytest-style',
'mypy',
'pylint',
'wemake-python-styleguide',
@ -96,7 +100,27 @@ def lint(session: Session) -> None:
@nox.session(python=[MAIN_PYTHON, NEXT_PYTHON])
def test(session: Session) -> None:
"""Test the code base."""
# Re-using an old environment is not so easy here as
# `poetry install --no-dev` removes previously installed packages.
# We keep things simple and forbid such usage.
if session.virtualenv.reuse_existing:
raise RuntimeError('The "test" session must be run with the "-r" option')
_begin(session)
# Install only the non-develop dependencies
# and the testing tool chain.
session.run('poetry', 'install', '--no-dev', external=True)
_install_packages(session, 'pytest', 'pytest-cov')
# Interpret extra arguments as options for pytest.
args = session.posargs or (
'--cov',
'--no-cov-on-fail',
'--cov-branch',
'--cov-fail-under=100',
'--cov-report=term-missing:skip-covered',
PYTEST_LOCATION,
)
session.run('pytest', *args)
@nox.session(name='pre-commit', python=MAIN_PYTHON, venv_backend='none')

202
poetry.lock generated
View file

@ -49,6 +49,15 @@ lazy-object-proxy = ">=1.4.0,<1.5.0"
six = ">=1.12,<2.0"
wrapt = ">=1.11,<2.0"
[[package]]
category = "dev"
description = "Atomic file writes."
marker = "sys_platform == \"win32\""
name = "atomicwrites"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "1.4.0"
[[package]]
category = "dev"
description = "Classes Without Boilerplate"
@ -137,6 +146,17 @@ version = "4.2.1"
[package.dependencies]
colorama = "*"
[[package]]
category = "dev"
description = "Code coverage measurement for Python"
name = "coverage"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
version = "5.2.1"
[package.extras]
toml = ["toml"]
[[package]]
category = "dev"
description = "A utility for ensuring Google-style docstrings stay up to date with the source code."
@ -341,6 +361,14 @@ version = ">=4.3.5,<5"
[package.extras]
test = ["pytest (>=4.0.2,<6)"]
[[package]]
category = "dev"
description = "The package provides base classes and utils for flake8 plugin writing"
name = "flake8-plugin-utils"
optional = false
python-versions = ">=3.6,<4.0"
version = "1.3.0"
[[package]]
category = "dev"
description = "Polyfill package for Flake8 plugins"
@ -352,6 +380,17 @@ version = "1.0.2"
[package.dependencies]
flake8 = "*"
[[package]]
category = "dev"
description = "A flake8 plugin checking common style issues or inconsistencies with pytest-based tests."
name = "flake8-pytest-style"
optional = false
python-versions = ">=3.6,<4.0"
version = "1.2.2"
[package.dependencies]
flake8-plugin-utils = ">=1.3.0,<2.0.0"
[[package]]
category = "dev"
description = "Flake8 lint for quotes."
@ -408,6 +447,14 @@ version = "3.1.7"
[package.dependencies]
gitdb = ">=4.0.1,<5"
[[package]]
category = "dev"
description = "iniconfig: brain-dead simple config-ini parsing"
name = "iniconfig"
optional = false
python-versions = "*"
version = "1.0.1"
[[package]]
category = "dev"
description = "A Python utility / library to sort Python imports."
@ -438,6 +485,14 @@ optional = false
python-versions = "*"
version = "0.6.1"
[[package]]
category = "dev"
description = "More routines for operating on iterables, beyond itertools"
name = "more-itertools"
optional = false
python-versions = ">=3.5"
version = "8.4.0"
[[package]]
category = "dev"
description = "Optional static typing for Python"
@ -479,6 +534,18 @@ virtualenv = ">=14.0.0"
[package.extras]
tox_to_nox = ["jinja2", "tox"]
[[package]]
category = "dev"
description = "Core utilities for Python packages"
name = "packaging"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "20.4"
[package.dependencies]
pyparsing = ">=2.0.2"
six = "*"
[[package]]
category = "dev"
description = "Utility library for gitignore style pattern matching of file paths."
@ -506,6 +573,17 @@ version = "0.9.1"
[package.dependencies]
flake8-polyfill = ">=1.0.2,<2"
[[package]]
category = "dev"
description = "plugin and hook calling mechanisms for python"
name = "pluggy"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
version = "0.13.1"
[package.extras]
dev = ["pre-commit", "tox"]
[[package]]
category = "dev"
description = "library with cross-python path, ini-parsing, io, code, log facilities"
@ -564,6 +642,52 @@ isort = ">=4.2.5,<5"
mccabe = ">=0.6,<0.7"
toml = ">=0.7.1"
[[package]]
category = "dev"
description = "Python parsing module"
name = "pyparsing"
optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
version = "2.4.7"
[[package]]
category = "dev"
description = "pytest: simple powerful testing with Python"
name = "pytest"
optional = false
python-versions = ">=3.5"
version = "6.0.1"
[package.dependencies]
atomicwrites = ">=1.0"
attrs = ">=17.4.0"
colorama = "*"
iniconfig = "*"
more-itertools = ">=4.0.0"
packaging = "*"
pluggy = ">=0.12,<1.0"
py = ">=1.8.2"
toml = "*"
[package.extras]
checkqa_mypy = ["mypy (0.780)"]
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
[[package]]
category = "dev"
description = "Pytest plugin for measuring coverage."
name = "pytest-cov"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
version = "2.10.0"
[package.dependencies]
coverage = ">=4.4"
pytest = ">=4.6"
[package.extras]
testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "pytest-xdist", "virtualenv"]
[[package]]
category = "dev"
description = "YAML parser and emitter for Python"
@ -719,7 +843,7 @@ python-versions = "*"
version = "1.12.1"
[metadata]
content-hash = "e69534c9133ef3fb975f33937c97711f69281a8ffb931097a7b9e4ae1132fa09"
content-hash = "1a6ddbc4c05cb41329cbac4792210897f11dda8e8ba3aa7e814a4a0d0d9c4fa8"
lock-version = "1.0"
python-versions = "^3.8"
@ -744,6 +868,10 @@ astroid = [
{file = "astroid-2.4.2-py3-none-any.whl", hash = "sha256:bc58d83eb610252fd8de6363e39d4f1d0619c894b0ed24603b881c02e64c7386"},
{file = "astroid-2.4.2.tar.gz", hash = "sha256:2f4078c2a41bf377eea06d71c9d2ba4eb8f6b1af2135bec27bbbb7d8f12bb703"},
]
atomicwrites = [
{file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"},
{file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"},
]
attrs = [
{file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"},
{file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"},
@ -771,6 +899,42 @@ colorlog = [
{file = "colorlog-4.2.1-py2.py3-none-any.whl", hash = "sha256:43597fd822ce705190fc997519342fdaaf44b9b47f896ece7aa153ed4b909c74"},
{file = "colorlog-4.2.1.tar.gz", hash = "sha256:75e55822c3a3387d721579241e776de2cf089c9ef9528b1f09e8b04d403ad118"},
]
coverage = [
{file = "coverage-5.2.1-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:40f70f81be4d34f8d491e55936904db5c527b0711b2a46513641a5729783c2e4"},
{file = "coverage-5.2.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:675192fca634f0df69af3493a48224f211f8db4e84452b08d5fcebb9167adb01"},
{file = "coverage-5.2.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2fcc8b58953d74d199a1a4d633df8146f0ac36c4e720b4a1997e9b6327af43a8"},
{file = "coverage-5.2.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:64c4f340338c68c463f1b56e3f2f0423f7b17ba6c3febae80b81f0e093077f59"},
{file = "coverage-5.2.1-cp27-cp27m-win32.whl", hash = "sha256:52f185ffd3291196dc1aae506b42e178a592b0b60a8610b108e6ad892cfc1bb3"},
{file = "coverage-5.2.1-cp27-cp27m-win_amd64.whl", hash = "sha256:30bc103587e0d3df9e52cd9da1dd915265a22fad0b72afe54daf840c984b564f"},
{file = "coverage-5.2.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9ea749fd447ce7fb1ac71f7616371f04054d969d412d37611716721931e36efd"},
{file = "coverage-5.2.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ce7866f29d3025b5b34c2e944e66ebef0d92e4a4f2463f7266daa03a1332a651"},
{file = "coverage-5.2.1-cp35-cp35m-macosx_10_13_x86_64.whl", hash = "sha256:4869ab1c1ed33953bb2433ce7b894a28d724b7aa76c19b11e2878034a4e4680b"},
{file = "coverage-5.2.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:a3ee9c793ffefe2944d3a2bd928a0e436cd0ac2d9e3723152d6fd5398838ce7d"},
{file = "coverage-5.2.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:28f42dc5172ebdc32622a2c3f7ead1b836cdbf253569ae5673f499e35db0bac3"},
{file = "coverage-5.2.1-cp35-cp35m-win32.whl", hash = "sha256:e26c993bd4b220429d4ec8c1468eca445a4064a61c74ca08da7429af9bc53bb0"},
{file = "coverage-5.2.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4186fc95c9febeab5681bc3248553d5ec8c2999b8424d4fc3a39c9cba5796962"},
{file = "coverage-5.2.1-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b360d8fd88d2bad01cb953d81fd2edd4be539df7bfec41e8753fe9f4456a5082"},
{file = "coverage-5.2.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:1adb6be0dcef0cf9434619d3b892772fdb48e793300f9d762e480e043bd8e716"},
{file = "coverage-5.2.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:098a703d913be6fbd146a8c50cc76513d726b022d170e5e98dc56d958fd592fb"},
{file = "coverage-5.2.1-cp36-cp36m-win32.whl", hash = "sha256:962c44070c281d86398aeb8f64e1bf37816a4dfc6f4c0f114756b14fc575621d"},
{file = "coverage-5.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b1ed2bdb27b4c9fc87058a1cb751c4df8752002143ed393899edb82b131e0546"},
{file = "coverage-5.2.1-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:c890728a93fffd0407d7d37c1e6083ff3f9f211c83b4316fae3778417eab9811"},
{file = "coverage-5.2.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:538f2fd5eb64366f37c97fdb3077d665fa946d2b6d95447622292f38407f9258"},
{file = "coverage-5.2.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:27ca5a2bc04d68f0776f2cdcb8bbd508bbe430a7bf9c02315cd05fb1d86d0034"},
{file = "coverage-5.2.1-cp37-cp37m-win32.whl", hash = "sha256:aab75d99f3f2874733946a7648ce87a50019eb90baef931698f96b76b6769a46"},
{file = "coverage-5.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:c2ff24df02a125b7b346c4c9078c8936da06964cc2d276292c357d64378158f8"},
{file = "coverage-5.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:304fbe451698373dc6653772c72c5d5e883a4aadaf20343592a7abb2e643dae0"},
{file = "coverage-5.2.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c96472b8ca5dc135fb0aa62f79b033f02aa434fb03a8b190600a5ae4102df1fd"},
{file = "coverage-5.2.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8505e614c983834239f865da2dd336dcf9d72776b951d5dfa5ac36b987726e1b"},
{file = "coverage-5.2.1-cp38-cp38-win32.whl", hash = "sha256:700997b77cfab016533b3e7dbc03b71d33ee4df1d79f2463a318ca0263fc29dd"},
{file = "coverage-5.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:46794c815e56f1431c66d81943fa90721bb858375fb36e5903697d5eef88627d"},
{file = "coverage-5.2.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:16042dc7f8e632e0dcd5206a5095ebd18cb1d005f4c89694f7f8aafd96dd43a3"},
{file = "coverage-5.2.1-cp39-cp39-manylinux1_i686.whl", hash = "sha256:c1bbb628ed5192124889b51204de27c575b3ffc05a5a91307e7640eff1d48da4"},
{file = "coverage-5.2.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4f6428b55d2916a69f8d6453e48a505c07b2245653b0aa9f0dee38785939f5e4"},
{file = "coverage-5.2.1-cp39-cp39-win32.whl", hash = "sha256:9e536783a5acee79a9b308be97d3952b662748c4037b6a24cbb339dc7ed8eb89"},
{file = "coverage-5.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:b8f58c7db64d8f27078cbf2a4391af6aa4e4767cc08b37555c4ae064b8558d9b"},
{file = "coverage-5.2.1.tar.gz", hash = "sha256:a34cb28e0747ea15e82d13e14de606747e9e484fb28d63c999483f5d5188e89b"},
]
darglint = [
{file = "darglint-1.5.2-py3-none-any.whl", hash = "sha256:049a98cf3aec8cf6ea344a863c68112d80b7f8de214459b5fa6853371f89c3e7"},
{file = "darglint-1.5.2.tar.gz", hash = "sha256:6b9461f96694c2cf1d8edb1597a783fe6840953b0eb18cc6cc1e72a26f196d79"},
@ -838,10 +1002,18 @@ flake8-isort = [
{file = "flake8-isort-3.0.1.tar.gz", hash = "sha256:5d976da513cc390232ad5a9bb54aee8a092466a15f442d91dfc525834bee727a"},
{file = "flake8_isort-3.0.1-py2.py3-none-any.whl", hash = "sha256:df1dd6dd73f6a8b128c9c783356627231783cccc82c13c6dc343d1a5a491699b"},
]
flake8-plugin-utils = [
{file = "flake8-plugin-utils-1.3.0.tar.gz", hash = "sha256:965931e7c17a760915e38bb10dc60516b414ef8210e987252a8d73dcb196a5f5"},
{file = "flake8_plugin_utils-1.3.0-py3-none-any.whl", hash = "sha256:305461c4fbf94877bcc9ccf435771b135d72a40eefd92e70a4b5f761ca43b1c8"},
]
flake8-polyfill = [
{file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"},
{file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"},
]
flake8-pytest-style = [
{file = "flake8-pytest-style-1.2.2.tar.gz", hash = "sha256:93830b18d961613a012061867b15b1434e433662197ec3a7594db8389637d5f9"},
{file = "flake8_pytest_style-1.2.2-py3-none-any.whl", hash = "sha256:63e93d10b9cca6c73309319e89e7b1a8d316c7d1f12407faca975c498192fde3"},
]
flake8-quotes = [
{file = "flake8-quotes-2.1.2.tar.gz", hash = "sha256:c844c9592940c8926c60f00bc620808912ff2acd34923ab5338f3a5ca618a331"},
]
@ -860,6 +1032,10 @@ gitpython = [
{file = "GitPython-3.1.7-py3-none-any.whl", hash = "sha256:fa3b92da728a457dd75d62bb5f3eb2816d99a7fe6c67398e260637a40e3fafb5"},
{file = "GitPython-3.1.7.tar.gz", hash = "sha256:2db287d71a284e22e5c2846042d0602465c7434d910406990d5b74df4afb0858"},
]
iniconfig = [
{file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"},
{file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"},
]
isort = [
{file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"},
{file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"},
@ -891,6 +1067,10 @@ mccabe = [
{file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"},
{file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"},
]
more-itertools = [
{file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"},
{file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"},
]
mypy = [
{file = "mypy-0.782-cp35-cp35m-macosx_10_6_x86_64.whl", hash = "sha256:2c6cde8aa3426c1682d35190b59b71f661237d74b053822ea3d748e2c9578a7c"},
{file = "mypy-0.782-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9c7a9a7ceb2871ba4bac1cf7217a7dd9ccd44c27c2950edbc6dc08530f32ad4e"},
@ -915,6 +1095,10 @@ nox = [
{file = "nox-2020.5.24-py3-none-any.whl", hash = "sha256:c4509621fead99473a1401870e680b0aadadce5c88440f0532863595176d64c1"},
{file = "nox-2020.5.24.tar.gz", hash = "sha256:61a55705736a1a73efbd18d5b262a43d55a1176546e0eb28b29064cfcffe26c0"},
]
packaging = [
{file = "packaging-20.4-py2.py3-none-any.whl", hash = "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181"},
{file = "packaging-20.4.tar.gz", hash = "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8"},
]
pathspec = [
{file = "pathspec-0.8.0-py2.py3-none-any.whl", hash = "sha256:7d91249d21749788d07a2d0f94147accd8f845507400749ea19c1ec9054a12b0"},
{file = "pathspec-0.8.0.tar.gz", hash = "sha256:da45173eb3a6f2a5a487efba21f050af2b41948be6ab52b6a1e3ff22bb8b7061"},
@ -927,6 +1111,10 @@ pep8-naming = [
{file = "pep8-naming-0.9.1.tar.gz", hash = "sha256:a33d38177056321a167decd6ba70b890856ba5025f0a8eca6a3eda607da93caf"},
{file = "pep8_naming-0.9.1-py2.py3-none-any.whl", hash = "sha256:45f330db8fcfb0fba57458c77385e288e7a3be1d01e8ea4268263ef677ceea5f"},
]
pluggy = [
{file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"},
{file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"},
]
py = [
{file = "py-1.9.0-py2.py3-none-any.whl", hash = "sha256:366389d1db726cd2fcfc79732e75410e5fe4d31db13692115529d34069a043c2"},
{file = "py-1.9.0.tar.gz", hash = "sha256:9ca6883ce56b4e8da7e79ac18787889fa5206c79dcc67fb065376cd2fe03f342"},
@ -951,6 +1139,18 @@ pylint = [
{file = "pylint-2.5.3-py3-none-any.whl", hash = "sha256:d0ece7d223fe422088b0e8f13fa0a1e8eb745ebffcb8ed53d3e95394b6101a1c"},
{file = "pylint-2.5.3.tar.gz", hash = "sha256:7dd78437f2d8d019717dbf287772d0b2dbdfd13fc016aa7faa08d67bccc46adc"},
]
pyparsing = [
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
{file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
]
pytest = [
{file = "pytest-6.0.1-py3-none-any.whl", hash = "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"},
{file = "pytest-6.0.1.tar.gz", hash = "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4"},
]
pytest-cov = [
{file = "pytest-cov-2.10.0.tar.gz", hash = "sha256:1a629dc9f48e53512fcbfda6b07de490c374b0c83c55ff7a1720b3fccff0ac87"},
{file = "pytest_cov-2.10.0-py2.py3-none-any.whl", hash = "sha256:6e6d18092dce6fad667cd7020deed816f858ad3b49d5b5e2b1cc1c97a4dba65c"},
]
pyyaml = [
{file = "PyYAML-5.3.1-cp27-cp27m-win32.whl", hash = "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f"},
{file = "PyYAML-5.3.1-cp27-cp27m-win_amd64.whl", hash = "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76"},

View file

@ -41,6 +41,12 @@ flake8 = "^3.8.3"
flake8-annotations = "^2.3.0"
flake8-black = "^0.2.1"
flake8-expression-complexity = "^0.0.8"
flake8-pytest-style = "^1.2.2"
mypy = "^0.782"
pylint = "^2.5.3"
wemake-python-styleguide = "^0.14.1" # flake8 plug-in
# Test Suite
packaging = "^20.4" # used to test the packaged version
pytest = "^6.0.1"
pytest-cov = "^2.10.0"

View file

@ -2,6 +2,23 @@
# black's settings are in pyproject.toml => [tool.black]
[coverage:paths]
source =
src/
*/site-packages/
[coverage:report]
show_missing = true
skip_covered = true
skip_empty = true
[coverage:run]
branch = true
data_file = .cache/coverage/data
source =
urban_meal_delivery
[flake8]
# Include error classes only explicitly
# to avoid forward compatibility issues.
@ -57,6 +74,8 @@ select =
BLK1, BLK9,
# flake8-expression-complexity => not too many expressions at once
ECE001,
# flake8-pytest-style => enforce a consistent style with pytest
PT0,
# By default, flake8 ignores some errors.
# Instead, do not ignore anything.
@ -70,6 +89,10 @@ extend-ignore =
E203, W503,
# f-strings are ok.
WPS305,
# Classes should not have to specify a base class.
WPS306,
# Putting logic into __init__.py files may be justified.
WPS412,
per-file-ignores =
noxfile.py:
@ -79,6 +102,11 @@ per-file-ignores =
WPS213,
# No overuse of string constants (e.g., '--version').
WPS226,
tests/*.py:
# `assert` statements are ok in the test suite.
S101,
# Shadowing outer scopes occurs naturally with mocks.
WPS442,
# Explicitly set mccabe's maximum complexity to 10 as recommended by
# Thomas McCabe, the inventor of the McCabe complexity, and the NIST.
@ -106,6 +134,17 @@ docstring-convention = google
# flake8-eradicate
eradicate-aggressive = true
# ==============================
# flake8-pytest-style's settings
# ==============================
# Prefer @pytest.fixture over @pytest.fixture().
pytest-fixture-no-parentheses = true
# Prefer @pytest.mark.parametrize(['param1', 'param2'], [(1, 2), (3, 4)])
# over @pytest.mark.parametrize(('param1', 'param2'), ([1, 2], [3, 4]))
pytest-parametrize-names-type = list
pytest-parametrize-values-row-type = tuple
pytest-parametrize-values-type = list
[isort]
atomic = true
@ -134,7 +173,7 @@ single_line_exclusions = typing
[mypy]
cache_dir = .cache/mypy
[mypy-nox.*]
[mypy-nox.*,packaging.*,pytest]
ignore_missing_imports = true
@ -165,6 +204,15 @@ disable =
missing-class-docstring, missing-function-docstring, missing-module-docstring,
# pyflakes
undefined-variable, unused-import, unused-variable,
# wemake-python-styleguide
redefined-outer-name,
[pylint.REPORTS]
score = no
[tool:pytest]
addopts =
--strict-markers
cache_dir = .cache/pytest
console_output_style = count

View file

@ -1 +1,9 @@
"""Source code for the urban-meal-delivery research project."""
from importlib import metadata
try:
__version__ = metadata.version(__name__)
except metadata.PackageNotFoundError: # pragma: no cover
__version__ = 'unknown'

1
tests/__init__.py Normal file
View file

@ -0,0 +1 @@
"""Test the urban-meal-delivery package."""

116
tests/test_version.py Normal file
View file

@ -0,0 +1,116 @@
"""Test the package's version identifier.
This packaged version identifier must adhere to PEP440
and a strict subset of semantic versioning:
- version identifiers must follow the x.y.z format
where x.y.z are non-negative integers
- non-final versions are only allowed with a .devN suffix
where N is also a non-negative integer
"""
import re
import pytest
from packaging import version as pkg_version
from packaging.version import Version
import urban_meal_delivery
@pytest.fixture
def parsed_version() -> str:
"""The packaged version."""
return pkg_version.Version(urban_meal_delivery.__version__) # noqa:WPS609
class TestPEP404Compliance:
"""Packaged version identifier is PEP440 compliant."""
# pylint:disable=no-self-use
def test_parsed_version_has_no_epoch(self, parsed_version: Version) -> None:
"""PEP440 compliant subset of semantic versioning: no epoch."""
assert parsed_version.epoch == 0
def test_parsed_version_is_non_local(self, parsed_version: Version) -> None:
"""PEP440 compliant subset of semantic versioning: no local version."""
assert parsed_version.local is None
def test_parsed_version_is_no_post_release(self, parsed_version: Version) -> None:
"""PEP440 compliant subset of semantic versioning: no post releases."""
assert parsed_version.is_postrelease is False
def test_parsed_version_is_all_public(self, parsed_version: Version) -> None:
"""PEP440 compliant subset of semantic versioning: all public parts."""
assert parsed_version.public == urban_meal_delivery.__version__ # noqa:WPS609
class TestSemanticVersioning:
"""Packaged version follows a strict subset of semantic versioning."""
# pylint:disable=no-self-use
version_pattern = re.compile(
r'^(0|([1-9]\d*))\.(0|([1-9]\d*))\.(0|([1-9]\d*))(\.dev(0|([1-9]\d*)))?$',
)
def test_version_is_semantic(self) -> None:
"""Packaged version follows semantic versioning."""
result = self.version_pattern.fullmatch(
urban_meal_delivery.__version__, # noqa:WPS609
)
assert result is not None
# The next two test cases are sanity checks to validate the version_pattern.
@pytest.mark.parametrize(
'version',
[
'0.1.0',
'1.0.0',
'1.2.3',
'1.23.456',
'123.4.56',
'10.11.12',
'1.2.3.dev0',
'1.2.3.dev1',
'1.2.3.dev10',
],
)
def test_valid_semantic_versioning(self, version: str) -> None:
"""Versions follow the x.y.z or x.y.z.devN format."""
result = self.version_pattern.fullmatch(version)
assert result is not None
@pytest.mark.parametrize(
'version',
[
'1',
'1.2',
'-1.2.3',
'01.2.3',
'1.02.3',
'1.2.03',
'1.2.3.4',
'1.2.3.abc',
'1.2.3-dev0',
'1.2.3+dev0',
'1.2.3.d0',
'1.2.3.develop0',
'1.2.3.dev-1',
'1.2.3.dev01',
'1.2.3..dev0',
'1-2-3',
'1,2,3',
'1..2.3',
'1.2..3',
],
)
def test_invalid_semantic_versioning(self, version: str) -> None:
"""Versions follow the x.y.z or x.y.z.devN format."""
result = self.version_pattern.fullmatch(version)
assert result is None