diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..5398e23
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,2 @@
+[run]
+omit = qurator/dinglehopper/tests/*
diff --git a/.editorconfig b/.editorconfig
index d9a5689..ea42d71 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -7,7 +7,7 @@ indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
-max_line_length = 90
+max_line_length = 88
tab_width = 4
[{*.cfg, *.ini, *.html, *.yaml, *.yml}]
@@ -20,3 +20,10 @@ insert_final_newline = false
# trailing spaces in markdown indicate word wrap
[*.md]
trim_trailing_whitespace = false
+
+[*.py]
+multi_line_output = 3
+include_trailing_comma = True
+force_grid_wrap = 0
+use_parentheses = True
+ensure_newline_before_comments = True
diff --git a/.gitignore b/.gitignore
index 3d3a74a..756850f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,25 +1,26 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
+*$py.class
# Distribution / packaging
*.egg-info/
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
+# Unit test / coverage reports
+htmlcov/
+.coverage
+.coverage.*
+
+# Environments
+.env
+.venv
+env/
+venv/
-# Generated files
-.idea/**/contentModel.xml
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
-# Sensitive or high-churn files
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-.idea/**/dbnavigator.xml
+# User-specific stuff
+.idea
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index a55e7a1..0000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/dinglehopper.iml b/.idea/dinglehopper.iml
deleted file mode 100644
index 0f3d9e5..0000000
--- a/.idea/dinglehopper.iml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 105ce2d..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 88565d3..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 6035afb..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/README-DEV.md b/README-DEV.md
index ba35796..2f16df7 100644
--- a/README-DEV.md
+++ b/README-DEV.md
@@ -1,10 +1,37 @@
-Testing
--------
+# Testing
+
Use `pytest` to run the tests in [the tests directory](qurator/dinglehopper/tests):
-~~~
+```bash
virtualenv -p /usr/bin/python3 venv
. venv/bin/activate
pip install -r requirements.txt
-pip install pytest
+pip install -r requirements-dev.txt
pytest
-~~~
+```
+
+### Test running examples
+### Only unit tests
+```bash
+pytest -m "not integration"
+```
+
+### Only integration tests
+```bash
+pytest -m integration
+```
+
+### All tests
+```bash
+pytest
+```
+
+### All tests with code coverage
+```bash
+pytest --cov=qurator --cov-report=html
+```
+
+### Static code analysis
+```bash
+pytest -k "not test" --flake8
+pytest -k "not test" --mypy
+```
diff --git a/qurator/dinglehopper/.gitignore b/qurator/dinglehopper/.gitignore
deleted file mode 100644
index e70d1f9..0000000
--- a/qurator/dinglehopper/.gitignore
+++ /dev/null
@@ -1,6 +0,0 @@
-# User-specific stuff
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/**/usage.statistics.xml
-.idea/**/dictionaries
-.idea/**/shelf
diff --git a/qurator/dinglehopper/.idea/dinglehopper.iml b/qurator/dinglehopper/.idea/dinglehopper.iml
deleted file mode 100644
index e273926..0000000
--- a/qurator/dinglehopper/.idea/dinglehopper.iml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/qurator/dinglehopper/.idea/misc.xml b/qurator/dinglehopper/.idea/misc.xml
deleted file mode 100644
index ba209a1..0000000
--- a/qurator/dinglehopper/.idea/misc.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/qurator/dinglehopper/.idea/modules.xml b/qurator/dinglehopper/.idea/modules.xml
deleted file mode 100644
index 6035afb..0000000
--- a/qurator/dinglehopper/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/qurator/dinglehopper/tests/test_integ_ocrd_cli.py b/qurator/dinglehopper/tests/test_integ_ocrd_cli.py
index 5cf6a41..54bd748 100644
--- a/qurator/dinglehopper/tests/test_integ_ocrd_cli.py
+++ b/qurator/dinglehopper/tests/test_integ_ocrd_cli.py
@@ -4,6 +4,7 @@ import json
import sys
from pathlib import Path
+import pytest
from click.testing import CliRunner
from .util import working_directory
@@ -13,6 +14,7 @@ from ..ocrd_cli import ocrd_dinglehopper
data_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "data")
+@pytest.mark.skipif(sys.platform == 'win32', reason="only on unix")
def test_ocrd_cli(tmp_path):
"""Test OCR-D interface"""
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..9403f15
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,5 @@
+pytest
+pytest-flake8
+pytest-cov
+pytest-mypy
+black
diff --git a/setup.cfg b/setup.cfg
index 43d7a3a..aeec880 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,12 @@
[flake8]
-max-line-length = 90
+max-line-length = 88
+extend-ignore = E203, W503
+
+[pylint]
+max-line-length = 88
+
+[pylint.messages_control]
+disable = C0330, C0326
+
+[mypy]
+ignore_missing_imports = True
diff --git a/setup.py b/setup.py
index 56ae184..1551c2d 100644
--- a/setup.py
+++ b/setup.py
@@ -4,6 +4,9 @@ from setuptools import find_packages, setup
with open("requirements.txt") as fp:
install_requires = fp.read()
+with open('requirements-dev.txt') as fp:
+ tests_require = fp.read()
+
setup(
name="dinglehopper",
author="Mike Gerber, The QURATOR SPK Team",
@@ -16,6 +19,7 @@ setup(
namespace_packages=["qurator"],
packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]),
install_requires=install_requires,
+ tests_require=tests_require,
package_data={
"": ["*.json", "templates/*"],
},