diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000000000000000000000000000000000000..9a8b02b988c941e3be27da4f6599b8615df49f77
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,7 @@
+[run]
+omit=
+	setup.py
+	ucamlookup/tests/*
+	ucamlookup/wsgi.py
+	.tox/*
+	ucamlookup/migrations/*
diff --git a/.flake8 b/.flake8
new file mode 100644
index 0000000000000000000000000000000000000000..467858906d36240c19f79a56e08c3ee63523654f
--- /dev/null
+++ b/.flake8
@@ -0,0 +1,3 @@
+[flake8]
+max-line-length=120
+exclude = venv,.tox,ibisclient,build/*
diff --git a/.gitignore b/.gitignore
index b23187f78f5421f133637a37b9624026618256a6..538ef73e5af708403be0f1e6c5e620d5d2aace22 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,4 +3,7 @@
 dist
 .DS_Store
 django_ucamlookup.egg-info
-MANIFEST
\ No newline at end of file
+MANIFEST
+.tox
+build
+.coverage
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000000000000000000000000000000000000..a4511d60e68129c5d1cabec8fa2a4979539b66b5
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,66 @@
+# Tox runner configuration
+#
+# The following optional environment variables can change behaviour. See the
+# comments where they are used for more information.
+#
+# - TOXINI_ARTEFACT_DIR
+# - TOXINI_FLAKE8_VERSION
+# - TOXINI_WORK_DIR
+#
+[tox]
+# Envs which should be run by default. This will execute a matrix of tests
+envlist =
+    py{27,34,35,36}-django111
+    py{34,35,36, 37}-django20
+    py{35,36, 37}-django21
+    coverage
+    flake8
+# Allow overriding toxworkdir via environment variable
+toxworkdir={env:TOXINI_WORK_DIR:{toxinidir}/.tox}
+
+# The "_vars" section is ignored by tox but we place some useful shared
+# variables in it to avoid needless repetition.
+[_vars]
+# Where to write build artefacts. We default to the "build" directory in the
+# tox.ini file's directory. Override with the TOXINI_ARTEFACT_DIR environment
+# variable.
+build_root={env:TOXINI_ARTEFACT_DIR:{toxinidir}/build}
+
+[testenv]
+# Additional dependencies
+deps=
+    coverage
+    django111: Django>=1.11,<2.0
+    django20: Django>=2.0,<2.1
+    django21: Django>=2.1,<2.2
+# Specify the default environment.
+commands=
+    coverage run --source={toxinidir} ./runtests.py
+
+# Check for PEP8 violations
+[testenv:flake8]
+basepython=python3
+deps=
+    -rrequirements.txt
+#   We specify a specific version of flake8 to avoid introducing "false"
+#   regressions when new checks are introduced. The version of flake8 used may
+#   be overridden via the TOXINI_FLAKE8_VERSION environment variable.
+    flake8=={env:TOXINI_FLAKE8_VERSION:3.6.0}
+commands=
+    flake8 --version
+    flake8 .
+
+# Check for PEP8 violations
+[testenv:coverage]
+basepython=python3
+deps=
+    coverage
+    django
+# Specify the default environment. Note that tox will *always* use the testsuite
+# settings unless overridden by TOX_DJANGO_SETTINGS_MODULE. Just setting
+# DJANGO_SETTINGS_MODULE will not override it.
+commands=
+    coverage run --source={toxinidir} ./runtests.py
+    coverage html --directory {[_vars]build_root}/htmlcov/
+    coverage report
+    coverage xml -o {env:COVERAGE_XML_FILE:{[_vars]build_root}/coverage.xml}