diff --git a/Makefile b/Makefile
index 2190f815fc..af060fcad1 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ PROJECT=nibabel
 # The Python executable to be used
 #
 PYTHON ?= python
-NOSETESTS = $(PYTHON) $(shell which nosetests)
+TEST_RUNNER ?= pytest
 
 #
 # Determine details on the Python/system
@@ -66,7 +66,6 @@ distclean: clean
 		 -o -iname '#*#' | xargs -L10 rm -f
 	-rm -r dist
 	-rm build-stamp
-	-rm -r .tox
 #	-rm tests/data/*.hdr.* tests/data/*.img.* tests/data/something.nii \
 #		tests/data/noise* tests/data/None.nii
 
@@ -83,22 +82,23 @@ $(WWW_DIR):
 # Tests
 #
 
-test: unittest testmanual
+test: test-style unittest testmanual
 
-
-ut-%: build
-	@PYTHONPATH=.:$(PYTHONPATH) $(NOSETESTS) nibabel/tests/test_$*.py
-
-
-unittest: build
-	@PYTHONPATH=.:$(PYTHONPATH) $(NOSETESTS) nibabel --with-doctest
+unittest: build test-clean
+	export CHECK_TYPE=test; ./tools/ci/check.sh
 
 testmanual: build
-	@cd doc/source && PYTHONPATH=../..:$(PYTHONPATH) $(NOSETESTS) --with-doctest --doctest-extension=.rst . dicom
+	export CHECK_TYPE=doc; ./tools/ci/check.sh
 
+coverage: unittest
 
-coverage: build
-	@PYTHONPATH=.:$(PYTHONPATH) $(NOSETESTS) --with-coverage --cover-package=nibabel
+.PHONY: test-style
+test-style:
+	export CHECK_TYPE=style; ./tools/ci/check.sh
+
+.PHONY: test-clean
+test-clean:
+	rm -rf for_testing
 
 
 #
@@ -252,11 +252,13 @@ sdist-venv: clean
 	rm -rf dist venv
 	unset PYTHONPATH && $(PYTHON) setup.py sdist --formats=zip
 	virtualenv --system-site-packages --python=$(PYTHON) venv
-	. venv/bin/activate && pip install --ignore-installed nose
+	. venv/bin/activate && pip install -r dev-requirements.txt
 	mkdir venv/tmp
 	cd venv/tmp && unzip ../../dist/*.zip
 	. venv/bin/activate && cd venv/tmp/nibabel* && python setup.py install
-	unset PYTHONPATH && . venv/bin/activate && cd venv && nosetests --with-doctest nibabel nisext
+	unset PYTHONPATH && . \
+		venv/bin/activate && \
+		cd venv && $(TEST_RUNNER) --doctest-modules --doctest-plus nibabel nisext
 
 source-release: distclean
 	$(PYTHON) -m compileall .
@@ -269,17 +271,7 @@ venv-tests:
 	make distclean
 	- rm -rf $(VIRTUAL_ENV)/lib/python$(PYVER)/site-packages/nibabel
 	$(PYTHON) setup.py install
-	cd .. && nosetests $(VIRTUAL_ENV)/lib/python$(PYVER)/site-packages/nibabel
-
-tox-fresh:
-	# tox tests with fresh-installed virtualenvs.  Needs network.  And
-	# pytox, obviously.
-	tox -c tox.ini
-
-tox-stale:
-	# tox tests with MB's already-installed virtualenvs (numpy and nose
-	# installed)
-	tox -e python25,python26,python27,python32,np-1.2.1
+	cd .. && $(TEST_RUNNER) $(VIRTUAL_ENV)/lib/python$(PYVER)/site-packages/nibabel
 
 refresh-readme:
 	$(PYTHON) tools/refresh_readme.py
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 69302061bc..a6485b1706 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -1,3 +1,5 @@
 # Requirements for running tests
 -r requirements.txt
 pytest
+pytest-doctestplus
+flake8
diff --git a/doc-requirements.txt b/doc-requirements.txt
index c934d76e6b..517bde85e9 100644
--- a/doc-requirements.txt
+++ b/doc-requirements.txt
@@ -1,6 +1,8 @@
 # Requirements for building docs
 -r requirements.txt
 sphinx<3
-numpydoc
+jinja2<3
+numpydoc<1.3
+MarkupSafe<2.1
 texext
 matplotlib >=1.3.1
diff --git a/doc/source/dicom/derivations/spm_dicom_orient.py b/doc/source/dicom/derivations/spm_dicom_orient.py
index 936e807ce1..033abc486e 100644
--- a/doc/source/dicom/derivations/spm_dicom_orient.py
+++ b/doc/source/dicom/derivations/spm_dicom_orient.py
@@ -46,7 +46,7 @@ def numbered_vector(nrows, symbol_prefix):
 R = zeros(4, 2)
 R[:3, :] = R3
 
-# The following is specific to the SPM algorithm. 
+# The following is specific to the SPM algorithm.
 x1 = ones(4, 1)
 y1 = ones(4, 1)
 y1[:3, :] = pos_pat_0
diff --git a/doc/source/scripts/make_coord_examples.py b/doc/source/scripts/make_coord_examples.py
index f763b28c28..d54650eb6f 100644
--- a/doc/source/scripts/make_coord_examples.py
+++ b/doc/source/scripts/make_coord_examples.py
@@ -68,6 +68,7 @@
 x, y = 0, 1
 # Make a rectangular box with these sides
 
+
 def make_ortho_box(bl, x_len, y_len):
     """ Make a box with sides parallel to the axes
     """
@@ -76,6 +77,7 @@ def make_ortho_box(bl, x_len, y_len):
                      [bl[x], bl[y] + y_len],
                      [bl[x] + x_len, bl[y] + y_len]))
 
+
 orth_epi_box = make_ortho_box(epi_bl, epi_x_len, epi_y_len)
 
 # Structural bounding box
@@ -126,6 +128,7 @@ def plot_localizer():
 def save_plot():
     # Plot using global variables
     plot_localizer()
+
     def vx2mm(pts):
         return pts - iso_center
     plot_box(vx2mm(rot_box), label='EPI bounding box')
diff --git a/doc/tools/build_modref_templates.py b/doc/tools/build_modref_templates.py
index 6ec6848579..d19ea12081 100755
--- a/doc/tools/build_modref_templates.py
+++ b/doc/tools/build_modref_templates.py
@@ -37,7 +37,7 @@ def abort(error):
 
     try:
         __import__(package)
-    except ImportError as e:
+    except ImportError:
         abort("Can not import " + package)
 
     module = sys.modules[package]
@@ -71,7 +71,11 @@ def abort(error):
     print('***', source_version)
 
     if source_version != installed_version:
-        abort("Installed version does not match source version")
+        abort(
+            "Installed version does not match source version. "
+            "source_version=" + str(source_version) +
+            "installed_version=" + str(installed_version)
+        )
 
     docwriter = ApiDocWriter(package, rst_extension='.rst',
                              other_defines=other_defines)
diff --git a/setup.cfg b/setup.cfg
index e81b1db10b..d7f4d730eb 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -96,10 +96,17 @@ nibabel =
 max-line-length = 100
 ignore = D100,D101,D102,D103,D104,D105,D200,D201,D202,D204,D205,D208,D209,D210,D300,D301,D400,D401,D403,E24,E121,E123,E126,E226,E266,E402,E704,E731,F821,I100,I101,I201,N802,N803,N804,N806,W503,W504,W605
 exclude =
+    venv
+    build
     *test*
     *sphinx*
     nibabel/externals/*
     */__init__.py
+    # temporary -- should be removed in another PR
+    ./doc/source/conf.py
+    ./doc/tools/apigen.py
+    ./nisext
+    ./tools
 
 [versioneer]
 VCS = git
diff --git a/tools/ci/activate.sh b/tools/ci/activate.sh
index ebef3b650b..6dbde8294e 100644
--- a/tools/ci/activate.sh
+++ b/tools/ci/activate.sh
@@ -1,10 +1,10 @@
-if [ -e virtenv/bin/activate ]; then
-    source virtenv/bin/activate
-elif [ -e virtenv/Scripts/activate ]; then
-    source virtenv/Scripts/activate
+if [ -e venv/bin/activate ]; then
+    source venv/bin/activate
+elif [ -e venv/Scripts/activate ]; then
+    source venv/Scripts/activate
 else
     echo Cannot activate virtual environment
-    ls -R virtenv
+    ls -R venv
     false
 fi
 
diff --git a/tools/ci/check.sh b/tools/ci/check.sh
index e497833521..f40052bc0c 100755
--- a/tools/ci/check.sh
+++ b/tools/ci/check.sh
@@ -14,8 +14,7 @@ set -x
 export NIBABEL_DATA_DIR="$PWD/nibabel-data"
 
 if [ "${CHECK_TYPE}" == "style" ]; then
-    # Run styles only on core nibabel code.
-    flake8 nibabel
+    flake8
 elif [ "${CHECK_TYPE}" == "doc" ]; then
     cd doc
     make html && make doctest
diff --git a/tools/ci/create_venv.sh b/tools/ci/create_venv.sh
index 7a28767396..b3441af010 100755
--- a/tools/ci/create_venv.sh
+++ b/tools/ci/create_venv.sh
@@ -12,7 +12,7 @@ echo SETUP_REQUIRES = $SETUP_REQUIRES
 set -x
 
 python -m pip install --upgrade pip virtualenv
-virtualenv --python=python virtenv
+virtualenv --python=python venv
 source tools/ci/activate.sh
 python --version
 python -m pip install -U $SETUP_REQUIRES
diff --git a/tox.ini b/tox.ini
deleted file mode 100644
index a0002e12b6..0000000000
--- a/tox.ini
+++ /dev/null
@@ -1,20 +0,0 @@
-[tox]
-# From-scratch tox-default-name virtualenvs
-envlist = py25,py26,py27,py32
-[testenv]
-deps =
-    nose
-    numpy
-commands=nosetests --with-doctest
-# MBs virtualenvs; numpy, nose already installed.  Run these with:
-# tox -e python25,python26,python27,python32,np-1.2.1
-[testenv:python25]
-deps =
-[testenv:python26]
-deps =
-[testenv:python27]
-deps =
-[testenv:python32]
-deps =
-[testenv:np-1.2.1]
-deps =