Repository: openpathsampling/openpathsampling (language: python)

Issue:
Test against Python 3.12; deprecate PyEMMA
Let's see how much of Python 3.12 we can support... we might need to only support the `MINIMAL` build in 3.12, because I think some optional dependencies (like OpenMMTools) still don't have a 3.12 build. Currently, this tries to bring us up to SPEC0/NEP29 Python versions (3.10 and above), although I may keep 3.9 around if we can't build on 3.12.

This also cleans up a bunch of old devtools/CI things. I had the habit of leaving around old approaches so I wouldn't have to dig through git to switch back if I found my new approach didn't work. A few things this removes:

* `after_success.sh`: Replaced by an actions workflow that builds docs when we merge to `master`.
* `pytests.sh`, `.travis.yml.disabled`: specific to Travis; there's no way we're going back there
* `miniconda_install.sh`: We now use actions maintained by others to install conda/mamba
* `minimal.txt`, `minimal_testing.txt`, `experimental_reqs.txt`: These were requirements.txt-style files to get our requirements. They've been replaced by a script that extracts requirements from the setup.cfg, so that setup.cfg can be a single source of truth (although `experimental_reqs` was used until this PR).

I'm a little concerned about removing `miniconda_install.sh`, just because I'm afraid I may have, at some point, recommended using this repo as a single source of truth on that file. There may be CI somewhere that is still using it. If so, hopefully anyone who needs it can vendor an old copy of it into their repos, or, as we have, update to better approaches provisioning conda/mamba.


Discussion / hints:

Fix patch:
diff --git a/.coveragerc b/.coveragerc
index 4cc9f5177..5c8936335 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,7 +1,6 @@
 [report]
 omit = 
     */python?.?/*
-    */site-packages/nose/*
     */openpathsampling/tests/*
     */mdtraj/*
     */openpathsampling/experimental/*
diff --git a/.github/workflows/autorelease-default-env.sh b/.github/workflows/autorelease-default-env.sh
index 198618484..4a09393a9 100644
--- a/.github/workflows/autorelease-default-env.sh
+++ b/.github/workflows/autorelease-default-env.sh
@@ -1,6 +1,6 @@
-# Vendored from Autorelease 0.3.2
+# Vendored from Autorelease 0.5.0
 # Update by updating Autorelease and running `autorelease vendor actions`
-INSTALL_AUTORELEASE="python -m pip install autorelease==0.3.2"
+INSTALL_AUTORELEASE="python -m pip install autorelease==0.5.0"
 if [ -f autorelease-env.sh ]; then
     source autorelease-env.sh
 fi
diff --git a/.github/workflows/autorelease-deploy.yml b/.github/workflows/autorelease-deploy.yml
index 1cfd18325..73fca082a 100644
--- a/.github/workflows/autorelease-deploy.yml
+++ b/.github/workflows/autorelease-deploy.yml
@@ -1,4 +1,4 @@
-# Vendored from Autorelease 0.3.2
+# Vendored from Autorelease 0.5.0
 # Update by updating Autorelease and running `autorelease vendor actions`
 name: "Autorelease Deploy"
 on:
diff --git a/.github/workflows/autorelease-gh-rel.yml b/.github/workflows/autorelease-gh-rel.yml
index 68506f577..8393d79fc 100644
--- a/.github/workflows/autorelease-gh-rel.yml
+++ b/.github/workflows/autorelease-gh-rel.yml
@@ -1,4 +1,4 @@
-# Vendored from Autorelease 0.3.2
+# Vendored from Autorelease 0.5.0
 # Update by updating Autorelease and running `autorelease vendor actions`
 name: "Autorelease Release"
 on:
diff --git a/.github/workflows/autorelease-prep.yml b/.github/workflows/autorelease-prep.yml
index 93c72aeda..d68853a03 100644
--- a/.github/workflows/autorelease-prep.yml
+++ b/.github/workflows/autorelease-prep.yml
@@ -1,4 +1,4 @@
-# Vendored from Autorelease 0.3.2
+# Vendored from Autorelease 0.5.0
 # Update by updating Autorelease and running `autorelease vendor actions`
 name: "Autorelease testpypi"
 on:
@@ -57,7 +57,7 @@ jobs:
       - uses: actions/checkout@v2
       - uses: actions/setup-python@v2
         with:
-          python-version: "3.x"
+          python-version: "3.11"
       - run: |  # TODO: move this to an action
           source ./.github/workflows/autorelease-default-env.sh
           if [ -f "autorelease-env.sh" ]; then
diff --git a/.github/workflows/check-openmm-rc.yml b/.github/workflows/check-openmm-rc.yml
index d0b41efd3..1d2c0b176 100644
--- a/.github/workflows/check-openmm-rc.yml
+++ b/.github/workflows/check-openmm-rc.yml
@@ -11,7 +11,7 @@ on:
     #branch: master
 
 jobs:
-  check-rc:
+  check_rc:
     if: ${{ github.repository == 'openpathsampling/openpathsampling' }}
     runs-on: ubuntu-latest
     name: "Check for OpenMM RC"
@@ -24,8 +24,11 @@ jobs:
           package: openmm
           ndays: 3
           labels: openmm_rc
-      - uses: benc-uk/workflow-dispatch@v1
-        with:
-          workflow: Test OpenMM Release Candidate
-          token: ${{ secrets.DISPATCH_TOKEN }}
-        if: ${{ steps.checkrc.outputs.hasrc == 'True' }}
+    outputs:
+      hasrc: ${{ steps.checkrc.outputs.hasrc }}
+
+  test_rc:
+    needs: check_rc
+    uses: ./.github/workflows/test-openmm-rc.yml
+    secrets: inherit
+    if: ${{ needs.check_rc.outputs.hasrc == 'True' }}
diff --git a/.gitignore b/.gitignore
index 295a7407d..607e931bb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -96,7 +96,6 @@ htmlcov/
 .tox/
 .coverage
 .cache
-nosetests.xml
 coverage.xml
 
 # Translations
diff --git a/.travis.yml.disabled b/.travis.yml.disabled
deleted file mode 100644
index 8a564e5c6..000000000
--- a/.travis.yml.disabled
+++ /dev/null
@@ -1,69 +0,0 @@
-version: ~> 1.0
-
-language: python
-filter_secrets: false   # https://github.com/travis-ci/travis-ci/issues/8934
-
-branches:
-  only:
-    - master
-    - stable
-    - docs_deploy  # used for experimenting with docs builds
-    - /^v\d+(\.\d+)+/
-
-
-notifications:
-  webhooks: https://coveralls.io/webhook
-
-before_install:
-  - deactivate  # virtual envs don't play nice with conda
-  - export PYTHONUNBUFFERED=true  # immediately flush stdout to terminal
-  - source devtools/ci/miniconda_install.sh
-  #- conda install -y pyyaml  # I don't think this is needed now...
-
-install:
-  - export OPS_ENV="openpathsampling-py${CONDA_PY}"
-  - if [ -z "$MINIMAL" ] ; then source devtools/conda_install_reqs.sh; else pip install -r devtools/minimal.txt -r devtools/minimal_testing.txt; fi
-  # for the first OPS install, we will run the ops_fixes autorelease branch
-  #- pip install git+https://github.com/dwhswenson/autorelease.git@v0.1.2
-  - pip install autorelease
-  - pip install --no-deps -e .
-
-before_script: 
-  - python --version
-  - python -c "import openpathsampling"
-  - source devtools/ci/git_hash.sh
-  - conda info --envs
-  - conda list
-
-script:
-  - python devtools/autorelease_check.py --branch ${TRAVIS_BRANCH} --event ${TRAVIS_EVENT_TYPE}
-  - source devtools/ci/pytests.sh
-  - if [ -z "$MINIMAL" ] ; then source devtools/ci/ipythontests.sh; fi
-
-  # Upload new docs
-  - if [ -z "$MINIMAL" ] ; then bash -x devtools/ci/after_sucess.sh; fi
-
-addons:
-  apt:
-    packages:
-    - pandoc
-
-env:
-  matrix:
-    - CONDA_PY=3.7 MINIMAL=true
-    - CONDA_PY=2.7
-    - CONDA_PY=3.6
-    - CONDA_PY=3.7
-  global:
-    - COVERALLS_PARALLEL=true
-    - TWINE_USERNAME="dwhswenson"
-    # TWINE_PASSWORD (set via web)
-    #- secure: "zs/3PN46wXFzTDli8LE9jJmNov27xAy7REaTK4s3DV9Ocmf5grqIUbUfFfa7XyOvamr6C7TSHib1SqQvEbHv416AcOD9FWZm9iNj0MO2dBimyXrvR50TFKG/dcp0Qzlp54hYFM4bEzWJZUVN120xynIKgfy74YBOA8DQ6B7FlyY="
-    # AUTORELEASE_TOKEN (set via web)
-    #- secure: "r9u8xvXo3avaloOogrqIVF/oFDeMLSYvZbBYv3y3nBN17p3I9C2FhUbu/VpiRBA0kX6U2BsAHh5DF8ymRD254/f3s8L1BapanLfmoRjp9cXAJ95RZhZ8Cxw9W36VEGcIa+JG1G31oqabEBW4ozQmXsGPSYI3+wfFmhchJPpaDRE="
-    - secure: "NJvoSrLNd2ZR3HluJjEqI36gD5lsucwIvgnYjNmM4cwnnA77aLV9FRYTwlLRZn3XY9FL8KOzL5l0amNzMD7sQrf7bWwWv7iCUBddH549q9RSgiuOugtodYJ6VaXi76hk1rOgcJpDoCj9wTCIlMtWibPUzr1QHmdihfdM2iA2kkE="
-    - secure: "l9NJkZDD0ALhkErUvhRrreLsrcWErd+CXpWv8dxHGtkjemNx6CwVtyL+a30jz/QwMANSZbKll/cPK5yJQvuwDaWxja6UPLLKVNGtma+CmwKcIC/wwTwbMoxcS62fyLJ3kS0qR8oCQz2nCPKiYyRGADtPLWVMZckY1SJfNYcKuCM="
-    - secure: "kb37xmsSV3pEnESnINzwlW2Cju/UFzA/G+m+NsihAwO8RMPZwKCrZK/rptgkUDACXJxom5M690WEukQkHnOt+OTrWhu7WKZgYeVuWUs2++RohYv/m5npaOHMMn+uYmF328v4PvPmXxbD02zzg5Tgdn82x8oa6J8BKX8ohOQ6Xpg="
-
-import:
-  - dwhswenson/autorelease:autorelease-travis.yml@travis-deploy
diff --git a/LICENSE b/LICENSE
index 5d7c512a6..598d6aa5a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2014-2019 the contributors to OpenPathSampling
+Copyright (c) 2014-2022 the contributors to OpenPathSampling
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/devtools/ci/after_sucess.sh b/devtools/ci/after_sucess.sh
deleted file mode 100755
index 87a90b739..000000000
--- a/devtools/ci/after_sucess.sh
+++ /dev/null
@@ -1,73 +0,0 @@
-#!/usr/bin/env bash
-
-DEPLOY_PY="3.7"
-
-echo "Running after success for:"
-echo "* Python version: ${CONDA_PY}\n* Branch: ${TRAVIS_BRANCH}"
-echo "* Pull request: $TRAVIS_PULL_REQUEST"
-
-if [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then
-    echo "This is a pull request. No deployment will be done."; exit 0
-fi
-
-if [[ "$CONDA_PY" != "$DEPLOY_PY" ]]; then
-    echo "Only deploy from Python ${DEPLOY_PY}. This is Python ${CONDA_PY}."
-    exit 0
-fi
-
-#if [[ "$TRAVIS_BRANCH" != "master" ]]; then
-    #echo "No deployment on BRANCH='$TRAVIS_BRANCH'"; exit 0
-#fi
-
-# this is for installing openpathsampling-dev, which we don't do anymore
-#echo travis_fold:start:binstar.upload
-#PACKAGE_NAME=openpathsampling-dev
-#GROUP_NAME=omnia
-#BUILD_PATH=~/miniconda2/conda-bld
-#if [[ "$CONDA_PY" == "$DEPLOY_PY" ]]; then
-    #conda install --yes anaconda-client jinja2
-    #conda convert -p all ${BUILD_PATH}/linux-64/${PACKAGE_NAME}*.tar.bz2 -o ${BUILD_PATH}/
-    #if [[ "$TRAVIS_BRANCH" != "master" ]]; then
-        #echo "No conda deploy on branch $TRAVIS_BRANCH"
-    #else
-        #anaconda -t ${ANACONDA_TOKEN}  upload  --force -u ${GROUP_NAME} -p ${PACKAGE_NAME} ${BUILD_PATH}/*/${PACKAGE_NAME}*.tar.bz2
-    #fi
-#else
-    #echo "Only deploy from Python ${DEPLOY_PY}. This is Python ${CONDA_PY}."
-    #exit 0
-#fi
-
-#echo travis_fold:end:binstar.upload
-
-echo travis_fold:start:build.docs
-# Create the docs and push them to S3
-# -----------------------------------
-conda install --yes pip
-#conda config --add channels http://conda.binstar.org/omnia
-#conda install --yes `conda build devtools/conda-recipe --output`
-pip install numpydoc s3cmd
-conda install --yes -c conda-forge -c omnia --file docs/requirements.txt
-
-conda list -e
-
-# Install pandoc for markdown support
-
-(cd docs && python premake.py && make html && cd -)
-ls -lt docs/_build
-ls -lt docs/_build/html
-pwd
-echo travis_fold:end:build.docs
-
-echo travis_fold:start:upload.docs
-
-if [[ "$TRAVIS_BRANCH" == "master" ]]; then
-    python devtools/ci/push-docs-to-s3.py
-elif [[ "$TRAVIS_BRANCH" == "docs_deploy" ]]; then
-    # change the behavior for the docs testing branch (docs_deploy branch in
-    # the openpathsampling/openpathsampling GitHub repo) in this block
-    #echo "No docs deploy on branch $TRAVIS_BRANCH"
-    python devtools/ci/push-docs-to-s3.py #--clobber
-else
-    echo "No docs deploy on branch $TRAVIS_BRANCH"
-fi
-echo travis_fold:end:upload.docs
diff --git a/devtools/ci/git_hash.sh b/devtools/ci/git_hash.sh
deleted file mode 100644
index b49042de1..000000000
--- a/devtools/ci/git_hash.sh
+++ /dev/null
@@ -1,8 +0,0 @@
-#!/usr/bin/env sh
-
-# NOTE: the pyversion is determined by ensuring we're not in the git repo
-# directory, where the local openpathsampling directory (without a
-# version.py) is the version we load
-pyversion=`python -c "import os; os.chdir(os.environ['HOME']); import openpathsampling as paths; print('%s [%s]' % (paths.version.full_version, paths.version.short_version))"`
-gitversion=`git rev-parse HEAD`
-echo "Installed version $pyversion from git commit hash $gitversion"
diff --git a/devtools/ci/miniconda_install.sh b/devtools/ci/miniconda_install.sh
deleted file mode 100644
index ba2d891bb..000000000
--- a/devtools/ci/miniconda_install.sh
+++ /dev/null
@@ -1,34 +0,0 @@
-#!/bin/bash
-# This script was taken from https://github.com/pandegroup/mdtraj/tree/master/devtools
-
-### Install Miniconda
-
-if [ -z "$CONDA_PY" ]
-then
-    CONDA_PY=2.7
-fi
-
-pyV=${CONDA_PY:0:1}
-conda_version="latest"
-#conda_version="4.4.10"  # can pin a miniconda version like this, if needed
-
-MINICONDA=Miniconda${pyV}-${conda_version}-Linux-x86_64.sh
-MINICONDA_MD5=$(curl -sL https://repo.continuum.io/miniconda/ | grep -A3 $MINICONDA | sed -n '4p' | sed -n 's/ *<td>\(.*\)<\/td> */\1/p')
-wget https://repo.continuum.io/miniconda/$MINICONDA
-if [[ $MINICONDA_MD5 != $(md5sum $MINICONDA | cut -d ' ' -f 1) ]]; then
-    echo "Miniconda MD5 mismatch"
-    echo "Expected: $MINICONDA_MD5"
-    echo "Found: $(md5sum $MINICONDA | cut -d ' ' -f 1)"
-    exit 1
-fi
-bash $MINICONDA -b
-
-export PATH=$HOME/miniconda${pyV}/bin:$PATH
-
-conda config --add channels http://conda.anaconda.org/conda-forge
-
-# next two lines are workaround for conda/conda#9337
-pip uninstall -y setuptools
-conda install --yes setuptools
-
-conda update --yes conda
diff --git a/devtools/conda_install_reqs.sh b/devtools/conda_install_reqs.sh
index ffbd57286..eaa12518f 100644
--- a/devtools/conda_install_reqs.sh
+++ b/devtools/conda_install_reqs.sh
@@ -24,7 +24,7 @@ else
     INSTALL="install"
 fi
 
-INSTALL_CMD="$EXE $INSTALL -y -q -c conda-forge -c omnia --override-channels"
+INSTALL_CMD="$EXE $INSTALL -y -q -c conda-forge --override-channels"
 
 # for some reason, these approaches to pinning don't always work (but conda
 # always obeys if you explicitly request a pinned version)
@@ -36,7 +36,7 @@ WORKAROUNDS=""
 REQUIREMENTS=`python ${DEVTOOLS_DIR}/setup_cfg_reqs.py`
 TESTING=`python ${DEVTOOLS_DIR}/setup_cfg_reqs.py --extra test`
 INTEGRATIONS=`cat ${DEVTOOLS_DIR}/tested_integrations.txt | tr "\n" " "`
-EXPERIMENTAL=`cat ${DEVTOOLS_DIR}/experimental_reqs.txt | tr "\n" " "`
+EXPERIMENTAL=$(python ${DEVTOOLS_DIR}/setup_cfg_reqs.py --extra simstore)
 PY_INSTALL="python=$CONDA_PY"
 
 # PIP_INSTALLS is used for debugging installation problems -- override the
diff --git a/devtools/experimental_reqs.txt b/devtools/experimental_reqs.txt
deleted file mode 100644
index bfd6cc288..000000000
--- a/devtools/experimental_reqs.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-sqlalchemy!=1.4.0
-dill
diff --git a/devtools/minimal.txt b/devtools/minimal.txt
deleted file mode 100644
index c058cdc41..000000000
--- a/devtools/minimal.txt
+++ /dev/null
@@ -1,10 +0,0 @@
-future
-numpy
-scipy
-pandas
-netcdf4
-networkx
-matplotlib
-svgwrite
-ujson
-psutil
diff --git a/docs/conf.py b/docs/conf.py
index 07daa9dc2..1db2ab3d1 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -107,7 +107,7 @@
 
 # General information about the project.
 project = u'OpenPathSampling'
-copyright = u'2014-2021, David W.H. Swenson, Jan-Hendrik Prinz, John Chodera, Peter Bolhuis'
+copyright = u'2014-2022, David W.H. Swenson, Jan-Hendrik Prinz, John Chodera, Peter Bolhuis'
 
 # The version info for the project you're documenting, acts as replacement for
 # |version| and |release|, also used in various other places throughout the
diff --git a/docs/developers/index.rst b/docs/developers/index.rst
index 24d401e1d..c0d20c4d6 100644
--- a/docs/developers/index.rst
+++ b/docs/developers/index.rst
@@ -32,7 +32,7 @@ standard.
 .. rubric:: Tests and Examples
 
 Additions to OPS should include thorough tests. We use `pytest`_ to run our
-tests, although we require `nose`_ for some legacy portions of the tests.
+tests.
 Unit/integration tests are located in the ``openpathsampling/tests/``
 directory.  Examples and integration tests should be written as Jupyter
 notebooks and placed in the ``examples/`` directory.  Pull requests to the
@@ -40,7 +40,6 @@ OPS GitHub repository will automatically run all unit tests with each push.
 We also run a subset of the system tests on each push, using `ipynbtest`_.
 
 .. _pytest: http://pytest.org
-.. _nose: http://nose.readthedocs.io/
 .. _ipynbtest: https://github.com/jhprinz/ipynb-test
 
 -----
diff --git a/docs/install.rst b/docs/install.rst
index 856e2e47c..0e9c93100 100644
--- a/docs/install.rst
+++ b/docs/install.rst
@@ -120,10 +120,10 @@ Testing your installation
 
 OpenPathSampling includes a thorough test suite, and running the test suite
 is a good start to troubleshooting any installation problems. The OPS test
-suite requires the packages ``pytest`` and (for legacy reasons) ``nose``.
-These can be  installed with either ``conda`` or ``pip``. For example: ::
+suite requires ``pytest``, which
+can be installed with either ``conda`` or ``pip``. For example: ::
 
-  $ conda install pytest nose
+  $ conda install pytest
 
 Once those are installed, you can run the test suite on your installation
 with the command: ::
diff --git a/openpathsampling/collectivevariable.py b/openpathsampling/collectivevariable.py
index 5160cab93..d3fe41288 100644
--- a/openpathsampling/collectivevariable.py
+++ b/openpathsampling/collectivevariable.py
@@ -576,90 +576,4 @@ def to_dict(self):
             'cv_requires_lists': self.cv_requires_lists,
             'cv_wrap_numpy_array': self.cv_wrap_numpy_array,
             'cv_scalarize_numpy_singletons': self.cv_scalarize_numpy_singletons
-        }
-
-
-class PyEMMAFeaturizerCV(CoordinateGeneratorCV):
-    """Make a CV from a function that takes mdtraj.trajectory as input.
-
-    This is identical to :class:`CoordinateGeneratorCV` except that the
-    function is called with an :class:`mdraj.Trajetory` object instead of the
-    :class:`openpathsampling.Trajectory` one using ``fnc(traj.to_mdtraj(),
-    **kwargs)``
-
-    """
-
-    def __init__(
-            self,
-            name,
-            featurizer,
-            topology,
-            **kwargs
-    ):
-        """
-
-        Parameters
-        ----------
-        name
-        featurizer : :class:`pyemma.coordinates.featurizer`
-            the pyemma featurizer used as a callable class
-        topology : :obj:`openpathsampling.engines.topology.MDTrajTopology`
-            the mdtraj topology wrapper from OPS that is used to initialize
-            the featurizer in ``pyemma.coordinates.featurizer(topology)``
-        **kwargs : dict
-            a dictionary of named arguments which should be given to the
-            ``featurizer`` (for example, the atoms which define a specific
-            distance/angle).
-            Finally an instance ``instance = cls(**kwargs)`` is created when
-            the CV is created and using the CV will call
-            ``instance(snapshots)``
-
-        Notes
-        -----
-        All trajectories or snapshots passed in kwargs will be converted
-        to mdtraj objects for convenience
-        """
-
-        md_kwargs = dict()
-        md_kwargs.update(kwargs)
-
-        # turn Snapshot and Trajectory into md.trajectory
-        for key in md_kwargs:
-            if isinstance(md_kwargs[key], paths.BaseSnapshot):
-                md_kwargs[key] = md_kwargs[key].to_mdtraj()
-            elif isinstance(md_kwargs[key], paths.Trajectory):
-                md_kwargs[key] = md_kwargs[key].to_mdtraj()
-
-        self.topology = topology
-
-        import pyemma.coordinates
-        self._instance = pyemma.coordinates.featurizer(self.topology.mdtraj)
-
-        featurizer(self._instance, **md_kwargs)
-
-        super(GeneratorCV, self).__init__(
-            name,
-            cv_callable=featurizer,
-            cv_requires_lists=True,
-            cv_wrap_numpy_array=True,
-            cv_scalarize_numpy_singletons=True,
-            **kwargs
-        )
-
-    @property
-    def featurizer(self):
-        return self.cv_callable
-
-    def _eval(self, items):
-        trajectory = paths.Trajectory(items)
-
-        t = trajectory_to_mdtraj(trajectory, self.topology.mdtraj)
-        return self._instance.transform(t)
-
-    def to_dict(self):
-        return {
-            'name': self.name,
-            'featurizer': ObjectJSON.callable_to_dict(self.featurizer),
-            'topology': self.topology,
-            'kwargs': self.kwargs
-        }
+        }
\ No newline at end of file
diff --git a/openpathsampling/collectivevariables/__init__.py b/openpathsampling/collectivevariables/__init__.py
index 4add9d1e3..9c2ee1a91 100644
--- a/openpathsampling/collectivevariables/__init__.py
+++ b/openpathsampling/collectivevariables/__init__.py
@@ -2,7 +2,6 @@
     CollectiveVariable, FunctionCV, CoordinateFunctionCV, GeneratorCV,
     CoordinateGeneratorCV, InVolumeCV, CallableCV,
     MDTrajFunctionCV,
-    PyEMMAFeaturizerCV,
 )
 from .plumed_wrapper import (
                              PLUMEDCV, PLUMEDInterface
diff --git a/openpathsampling/deprecations.py b/openpathsampling/deprecations.py
index dd53b8ca4..5be1b287e 100644
--- a/openpathsampling/deprecations.py
+++ b/openpathsampling/deprecations.py
@@ -181,7 +181,15 @@ def version_tuple_to_string(version_tuple):
     deprecated_in=(1, 6, 0)
 )
 
-
+SIMSTORE_CALLABLE_CODEC_STRING_NOT_BYTES = Deprecation(
+    problem=("The storage representation of callables stored by SimStore "
+             "has changed. Some functions stored in this file may not "
+             "work starting in {OPS} {version}"),
+    remedy=("Re-saving the data from this file into another file will "
+            "create something that can be loaded in that version."),
+    remove_version=(2, 0),
+    deprecated_in=(1, 6, 0)
+)
 
 # has_deprecations and deprecate hacks to change docstrings inspired by:
 # https://stackoverflow.com/a/47441572/4205735
diff --git a/openpathsampling/engines/external_engine.py b/openpathsampling/engines/external_engine.py
index f53859873..09abcfff2 100644
--- a/openpathsampling/engines/external_engine.py
+++ b/openpathsampling/engines/external_engine.py
@@ -13,6 +13,8 @@
 import shlex
 import time
 
+from subprocess import PIPE
+
 import sys
 if sys.version_info > (3, ):
     long = int
@@ -257,9 +259,10 @@ def start(self, snapshot=None):
         self.start_time = time.time()
         try:
             logger.info(self.engine_command())
-            # TODO: add the ability to have handlers for stdin and stdout
             self.proc = psutil.Popen(shlex.split(self.engine_command()),
-                                     preexec_fn=os.setsid)
+                                     preexec_fn=os.setsid,
+                                     stdout=PIPE,
+                                     stderr=PIPE)
         except OSError:  # pragma: no cover
             raise  # TODO: need to handle this, but do what?
         else:
@@ -268,6 +271,10 @@ def start(self, snapshot=None):
         if self.first_frame_in_file:
             _ = self.generate_next_frame()  # throw away repeat first frame
 
+    def _communicate(self):
+        # this is primarily for debug purposes
+        return self.proc.communicate()
+
     def stop(self, trajectory):
         super(ExternalEngine, self).stop(trajectory)
         logger.info("total_time {:.4f}".format(time.time() - self.start_time))
diff --git a/openpathsampling/engines/openmm/engine.py b/openpathsampling/engines/openmm/engine.py
index 0f5fc1d05..4995d35cc 100644
--- a/openpathsampling/engines/openmm/engine.py
+++ b/openpathsampling/engines/openmm/engine.py
@@ -277,11 +277,19 @@ def initialize(self, platform=None):
                     platformProperties=self.openmm_properties
                 )
             elif platform is None:
+                # as of OpenMM 8.1, we can't give an empty props dict when
+                # platform is None. This will still raise the internal
+                # OpenMM error is platform is None and properties are
+                # provided.
+                openmm_props = self.openmm_properties
+                if openmm_props == {}:
+                    openmm_props = None
+
                 self._simulation = openmm.app.Simulation(
                     topology=self.topology.mdtraj.to_openmm(),
                     system=self.system,
                     integrator=self.integrator,
-                    platformProperties=self.openmm_properties
+                    platformProperties=openmm_props,
                 )
             else:
                 self._simulation = openmm.app.Simulation(
diff --git a/openpathsampling/experimental/simstore/callable_codec.py b/openpathsampling/experimental/simstore/callable_codec.py
index 077a2062e..d369cc66a 100644
--- a/openpathsampling/experimental/simstore/callable_codec.py
+++ b/openpathsampling/experimental/simstore/callable_codec.py
@@ -1,7 +1,10 @@
 import types
 import dis
 import dill
+import codecs
 from .serialization_helpers import has_uuid, do_import
+from openpathsampling.deprecations \
+        import SIMSTORE_CALLABLE_CODEC_STRING_NOT_BYTES
 
 GLOBALS_ERROR_MESSAGE="""
 The function you tried to save uses information from global scope, which
@@ -125,9 +128,10 @@ def default(self, obj):
             if errors:
                 raise RuntimeError("Cannot store function! \n\n" + errors)
 
+            dillbytes = dill.dumps(obj, recurse=True)
             return {
                 '__callable_name__': obj.__name__,
-                '_dilled': dill.dumps(obj, recurse=True)
+                '_dilled': str(codecs.encode(dillbytes, 'base64'), 'utf-8')
             }
         return obj
 
@@ -138,7 +142,16 @@ def object_hook(self, dct):
             elif '__module__' in dct:
                 func = do_import(dct['__module__'], dct['__callable_name__'])
             elif '_dilled' in dct:
-                func =  dill.loads(dct['_dilled'])
+                dilled = dct['_dilled']
+
+                if isinstance(dilled, bytes):
+                    SIMSTORE_CALLABLE_CODEC_STRING_NOT_BYTES.warn(
+                        category=FutureWarning
+                    )
+                    dilled = str(codecs.encode(dilled, 'base64'), 'utf-8')
+
+                dilledbytes = bytes(dilled, 'utf-8')  # or ascii
+                func =  dill.loads(codecs.decode(dilledbytes, 'base64'))
             else:  # pragma: no cover
                 raise RuntimeError("Error reloading ",
                                    dct['__callable_name__'])
diff --git a/openpathsampling/experimental/simstore/sql_backend.py b/openpathsampling/experimental/simstore/sql_backend.py
index 49cc8e54d..f0e3b9511 100644
--- a/openpathsampling/experimental/simstore/sql_backend.py
+++ b/openpathsampling/experimental/simstore/sql_backend.py
@@ -156,7 +156,7 @@ def __init__(self, filename, mode='r', sql_dialect='sqlite', **kwargs):
 
     def _initialize_from_engine(self, engine):
         self.engine = engine
-        self._metadata = sql.MetaData(bind=self.engine)
+        self._metadata = sql.MetaData()
         self._initialize_with_mode(self.mode)
 
     @property
@@ -298,7 +298,7 @@ def _add_table_to_tables_list(self, table_name, table_schema, cls_):
         module = cls_.__module__
         class_name = cls_.__name__
 
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             conn.execute(tables.insert().values(name=table_name,
                                                 idx=n_tables,
                                                 module=module,
@@ -319,7 +319,7 @@ def _load_from_table(self, table_name, idx_list):
         with self.engine.connect() as conn:
             for block in grouper(idx_list, self.MAX_SQL_ITEMS):
                 or_stmt = sql.or_(*(table.c.idx == idx for idx in block))
-                sel = table.select(or_stmt)
+                sel = table.select().where(or_stmt)
                 results.extend(list(conn.execute(sel)))
 
         return results
@@ -390,7 +390,7 @@ def register_storable_function(self, table_name, result_type):
 
         self.metadata.create_all(self.engine)
         sfr_result_types = self.metadata.tables['sfr_result_types']
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             conn.execute(sfr_result_types.insert(),
                          {'uuid': table_name, 'result_type': result_type})
         self.sfr_result_types[table_name] = result_type
@@ -424,7 +424,7 @@ def add_storable_function_results(self, table_name, result_dict):
                    for uuid in unknown_uuids]
         table = self.metadata.tables[table_name]
         if results:
-            with self.engine.connect() as conn:
+            with self.engine.begin() as conn:
                 conn.execute(table.insert(), results)
 
         # update the cache
@@ -480,7 +480,7 @@ def load_storable_function_table(self, table_name):
     def add_tag(self, table_name, name, content):
         table = self.metadata.tables[table_name]
 
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             conn.execute(table.insert(), [{'name': name,
                                            'content': content}])
 
@@ -502,15 +502,15 @@ def add_to_table(self, table_name, objects):
         # this is if we don't use the UUID in the schema... but doing so
         # would be another option (redundant data, but better sanity checks)
 
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             conn.execute(table.insert(), objects)
 
         res = []
         uuids = [obj['uuid'] for obj in objects]
         for uuid_block in tools.block(uuids, self.MAX_SQL_ITEMS):
-            sel_uuids_idx = sql.select([table.c.uuid, table.c.idx]).\
+            sel_uuids_idx = sql.select(table.c.uuid, table.c.idx).\
                     where(table.c.uuid.in_(uuid_block))
-            with self.engine.connect() as conn:
+            with self.engine.begin() as conn:
                 res += list(conn.execute(sel_uuids_idx))
 
         uuid_to_rows = {r[0]: r[1] for r in res}
@@ -520,7 +520,7 @@ def add_to_table(self, table_name, objects):
         uuid_insert_dicts = [{'uuid': k, 'table': table_num, 'row':v}
                              for (k, v) in uuid_to_rows.items()]
 
-        with self.engine.connect() as conn:
+        with self.engine.begin() as conn:
             # here we use executemany for performance
             conn.execute(uuid_table.insert(), uuid_insert_dicts)
 
@@ -658,7 +658,7 @@ def table_iterator(self, table_name):
 
     def table_len(self, table_name):
         table = self.metadata.tables[table_name]
-        count_query = sql.select([sql.func.count()]).select_from(table)
+        count_query = sql.select(sql.func.count()).select_from(table)
         with self.engine.connect() as conn:
             results = conn.execute(count_query)
             count_list = [r for r in results]
diff --git a/openpathsampling/experimental/storage/monkey_patches.py b/openpathsampling/experimental/storage/monkey_patches.py
index 0b1f866ff..e9a95741a 100644
--- a/openpathsampling/experimental/storage/monkey_patches.py
+++ b/openpathsampling/experimental/storage/monkey_patches.py
@@ -64,7 +64,14 @@ def inner(cls, dct):
     paths
 ]
 
+_IS_PATCHED_SAVING = False
+_IS_PATCHED_LOADING = False
+
 def monkey_patch_saving(paths):
+    global _IS_PATCHED_SAVING
+    if _IS_PATCHED_SAVING:
+        return paths
+
     paths.netcdfplus.FunctionPseudoAttribute.to_dict = \
             function_pseudo_attribute_to_dict
     paths.TPSNetwork.to_dict = tuple_keys_to_dict(
@@ -73,9 +80,14 @@ def monkey_patch_saving(paths):
     paths.MISTISNetwork.to_dict = tuple_keys_to_dict(
         paths.MISTISNetwork.to_dict, 'input_transitions'
     )
+    _IS_PATCHED_SAVING = True
     return paths
 
 def monkey_patch_loading(paths):
+    global _IS_PATCHED_LOADING
+    if _IS_PATCHED_LOADING:
+        return paths
+
     paths.CallableCV.from_dict = classmethod(callable_cv_from_dict)
     paths.netcdfplus.FunctionPseudoAttribute.from_dict = \
             classmethod(from_dict_attr_to_class(
@@ -89,6 +101,7 @@ def monkey_patch_loading(paths):
     paths.MISTISNetwork.from_dict = classmethod(tuple_keys_from_dict(
         paths.MISTISNetwork.from_dict, 'input_transitions'
     ))
+    _IS_PATCHED_LOADING = True
     return paths
 
 def monkey_patch_all(paths):
@@ -107,4 +120,8 @@ def unpatch(paths):
     for module in _UNPATCH_MODULES * 2:
         importlib.reload(module)
 
+    global _IS_PATCHED_SAVING
+    global _IS_PATCHED_LOADING
+    _IS_PATCHED_SAVING = False
+    _IS_PATCHED_LOADING = False
     return paths
diff --git a/openpathsampling/experimental/storage/ops_storage.py b/openpathsampling/experimental/storage/ops_storage.py
index f3e07d2c0..351992e6d 100644
--- a/openpathsampling/experimental/storage/ops_storage.py
+++ b/openpathsampling/experimental/storage/ops_storage.py
@@ -47,6 +47,9 @@
     simtk_quantity_codec = None
     SimtkQuantityHandler = None
 
+
+from .mdtraj_json import mdtraj_codecs
+
 import logging
 logger = logging.getLogger(__name__)
 
@@ -77,6 +80,7 @@
 
 # this defines the simulation object serializer for OPS
 EXTRA_CODECS = [simtk_quantity_codec] if simtk_quantity_codec else []
+EXTRA_CODECS += mdtraj_codecs
 EXTRA_HANDLERS = [SimtkQuantityHandler] if SimtkQuantityHandler else []
 CODECS = DEFAULT_CODECS + EXTRA_CODECS
 HANDLERS = DEFAULT_HANDLERS + EXTRA_HANDLERS
diff --git a/openpathsampling/integration_tools.py b/openpathsampling/integration_tools.py
index 5bbdc2740..76fe6ab40 100644
--- a/openpathsampling/integration_tools.py
+++ b/openpathsampling/integration_tools.py
@@ -44,7 +44,7 @@ def _chain_import(*packages):
     HAS_SIMTK_UNIT = True
 
 def error_if_no_simtk_unit(name):
-    return error_if_no(name, "simtk.unit", HAS_SIMTK_UNIT)
+    return error_if_no(name, "openmm.unit or simtk.unit", HAS_SIMTK_UNIT)
 
 # mdtraj ############################################################
 try:
diff --git a/openpathsampling/netcdfplus/attribute.py b/openpathsampling/netcdfplus/attribute.py
index 21509095a..77b31d4a3 100644
--- a/openpathsampling/netcdfplus/attribute.py
+++ b/openpathsampling/netcdfplus/attribute.py
@@ -230,7 +230,7 @@ def __init__(
         This function is abstract and need _eval to be implemented to work.
         Problem is that there are two types of callable functions:
         1. direct functions: these can be called and give the wanted value
-           `c(object, \**kwargs)` would be the typical call
+           `c(object, **kwargs)` would be the typical call
         2. a generating function: a function the creates the callable object
            `c(**kwargs)(object)` is the typical call. This is usually used
            for classes. Create the instance and then use it.
@@ -408,7 +408,7 @@ class GeneratorPseudoAttribute(CallablePseudoAttribute):
     """Turn a callable class or function generating a callable object into a CV
 
     The class instance will be called with objects. The instance itself
-    will be created using the given \**kwargs.
+    will be created using the given **kwargs.
     """
 
     def __init__(
@@ -434,7 +434,7 @@ def __init__(
         kwargs
             additional arguments which should be given to `c` (for example, the
             atoms which define a specific distance/angle). Finally an instance
-            `instance = cls(\**kwargs)` is create when the CV is created and
+            `instance = cls(**kwargs)` is create when the CV is created and
             using the CV will call `instance(objects)`
 
         Notes
diff --git a/openpathsampling/netcdfplus/dictify.py b/openpathsampling/netcdfplus/dictify.py
index 613bf8bab..a0927ac34 100644
--- a/openpathsampling/netcdfplus/dictify.py
+++ b/openpathsampling/netcdfplus/dictify.py
@@ -40,6 +40,18 @@
     get_callable_name = lambda c: c.func_name
     import builtins
 
+def make_callable_codec(safemode):
+    # starting in Python 3.11, we use SimStore to serialize callables
+    from openpathsampling.experimental.simstore.callable_codec \
+            import CallableCodec
+    only_req = ObjectJSON.prevent_unsafe_modules
+    codec = CallableCodec(settings={
+        'only_allow_required_modules': only_req,
+        'required_modules': ObjectJSON.safe_modules,
+        'safemode': safemode,
+    })
+    return codec
+
 # in Python 3.6 the opcodes have changed width
 if sys.version_info > (3, 6):
     opcode_arg_width = 1
@@ -53,6 +65,7 @@
 else:
     ujson_kwargs = {"reject_bytes": False}
 
+
 class ObjectJSON(object):
     """
     A simple implementation of a pickle algorithm to create object that can be
@@ -294,7 +307,7 @@ def build(self, obj):
                 else:
                     return None
 
-            elif '_marshal' in obj or '_module' in obj:
+            elif '_marshal' in obj or '_module' in obj or '_dilled' in obj:
                 if self.safemode:
                     return None
 
@@ -366,6 +379,11 @@ def callable_to_dict(c):
 
         # if the easy way did not work, try saving it using bytecode
         if ObjectJSON.allow_marshal and callable(c):
+            if sys.version_info > (3, 11):
+                codec = make_callable_codec(safemode=False)
+                return codec.default(c)
+
+            # back to normal for older Python versions
             # use marshal
             global_vars = ObjectJSON._find_var(c, opcode.opmap['LOAD_GLOBAL'])
             import_vars = ObjectJSON._find_var(c, opcode.opmap['IMPORT_NAME'])
@@ -481,6 +499,12 @@ def callable_from_dict(c_dict):
                     imp = importlib.import_module(module)
                     c = getattr(imp, c_dict['_name'])
 
+            elif '_dilled' in c_dict:
+                # safemode has to get handled by original netcdfplus, since
+                # this is a staticmethod
+                codec = make_callable_codec(safemode=False)
+                c = codec.object_hook(c_dict)
+
         return c
 
     @staticmethod
diff --git a/openpathsampling/pathmover.py b/openpathsampling/pathmover.py
index c6e00cf82..a715e724e 100644
--- a/openpathsampling/pathmover.py
+++ b/openpathsampling/pathmover.py
@@ -17,8 +17,7 @@
 from .treelogic import TreeMixin
 
 from openpathsampling.deprecations import deprecate, has_deprecations
-from openpathsampling.deprecations import (SAMPLE_DETAILS, MOVE_DETAILS,
-                                           NEW_SNAPSHOT_KWARG_SELECTOR)
+from openpathsampling.deprecations import NEW_SNAPSHOT_KWARG_SELECTOR
 
 from future.utils import with_metaclass
 
diff --git a/openpathsampling/treelogic.py b/openpathsampling/treelogic.py
index 147458ace..d6326fc24 100644
--- a/openpathsampling/treelogic.py
+++ b/openpathsampling/treelogic.py
@@ -288,7 +288,7 @@ def map_tree(self, fnc):
         Returns
         -------
         list :
-            tree (fnc(node, \*\*kwargs))
+            tree (fnc(node, **kwargs))
             nested list of the results of the map
         """
         return [fnc(self)] + [ch.map_tree(fnc) for ch in self._subnodes]
@@ -349,7 +349,7 @@ def map_post_order(self, fnc, **kwargs):
 
         Returns
         -------
-        list (fnc(node, \*\*kwargs))
+        list (fnc(node, **kwargs))
             flattened list of the results of the map
 
         Notes
@@ -372,7 +372,7 @@ def depth_post_order(self, fnc, level=0, **kwargs):
 
         Parameters
         ----------
-        fnc : function(node, \*\*kwargs)
+        fnc : function(node, **kwargs)
             the function run at each node. It is given the node and the
             optional (fixed) parameters
         level : int
@@ -408,7 +408,7 @@ def map_pre_order(self, fnc, **kwargs):
 
         Parameters
         ----------
-        fnc : function(node, \*\*kwargs)
+        fnc : function(node, **kwargs)
             the function run at each node. It is given the node and the
             optional parameters
         kwargs : named arguments
@@ -416,7 +416,7 @@ def map_pre_order(self, fnc, **kwargs):
 
         Returns
         -------
-        list (fnc(node, \*\*kwargs))
+        list (fnc(node, **kwargs))
             flattened list of the results of the map
 
         Notes
@@ -439,7 +439,7 @@ def depth_pre_order(self, fnc, level=0, only_canonical=False, **kwargs):
 
         Parameters
         ----------
-        fnc : function(node, \*\*kwargs)
+        fnc : function(node, **kwargs)
             the function run at each node. It is given the node and the
             optional parameters
         level : int
diff --git a/setup.cfg b/setup.cfg
index 27a4e7a7f..09e296679 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -14,10 +14,9 @@ classifiers =
     Intended Audience :: Developers
     License :: OSI Approved :: MIT License
     Programming Language :: Python
-    Programming Language :: Python :: 2.7
-    Programming Language :: Python :: 3.7
-    Programming Language :: Python :: 3.8
     Programming Language :: Python :: 3.9
+    Programming Language :: Python :: 3.10
+    Programming Language :: Python :: 3.11
     Topic :: Scientific/Engineering :: Bio-Informatics
     Topic :: Scientific/Engineering :: Chemistry
     Topic :: Scientific/Engineering :: Physics
@@ -27,6 +26,7 @@ classifiers =
 
 [options]
 include_package_data = True
+python_requires = >=3.10
 install_requires = 
     future
     psutil
@@ -37,22 +37,24 @@ install_requires =
     svgwrite
     networkx
     matplotlib
-    ujson!=2.*,!=3.*,!=4.0.0,!=4.0.1
+    ujson>=4.0.2
+    dill
+    sqlalchemy>=1.4.1
     mdtraj
 # mdtraj is not technically required, but we co-package it because it is
 # required for many integrations with other packages
+# dill and sqlalchemy are technically only required for Python 3.11+, but
+# we'll include them for all Python versions
 packages = find:
 
 [options.extras_require]
 test = 
-    nose
     pytest
     pytest-cov
     coveralls
-    nbval
+    nbval>=0.10.0
 simstore =
-    sqlalchemy!=1.4.0
-    dill
+    # simstore requirements have been moved to main
 
 [bdist_wheel]
 universal = 1


Test patch:
diff --git a/.autorelease/test-testpypi b/.autorelease/test-testpypi
index fd77e73aa..f1fc4c695 100644
--- a/.autorelease/test-testpypi
+++ b/.autorelease/test-testpypi
@@ -1,4 +1,4 @@
 #!/bin/bash
 
-python -m pip install pytest nose
+python -m pip install pytest sqlalchemy
 py.test --pyargs openpathsampling.tests
diff --git a/.github/workflows/test-2.0.yml b/.github/workflows/test-2.0.yml
index 82f4dcf09..727c2e50b 100644
--- a/.github/workflows/test-2.0.yml
+++ b/.github/workflows/test-2.0.yml
@@ -24,12 +24,12 @@ jobs:
       #   CVs) can differ between minor Python versions.
       matrix:
         CONDA_PY:
-          - 3.7
-          - 3.8
-          - 3.9
+          - "3.12"
+          - "3.11"
+          - "3.10"
         MINIMAL: [""]
         include:
-          - CONDA_PY: 3.7
+          - CONDA_PY: "3.10"
             MINIMAL: "minimal"
 
     steps:
@@ -41,6 +41,7 @@ jobs:
         with:
           auto-update-conda: true
           python-version: ${{ matrix.CONDA_PY }}
+          miniforge-variant: Mambaforge
       - name: "Install requirements"
         env:
           MINIMAL: ${{ matrix.MINIMAL }}
@@ -49,21 +50,27 @@ jobs:
           if [ -z "$MINIMAL" ] ; then
             source devtools/conda_install_reqs.sh
           else
-            python -m pip install -r devtools/minimal.txt \
-                                  -r devtools/minimal_testing.txt
+            # In this case we're actually double-installing (not just
+            # installing requirements here). We prefer this to using the
+            # conda_install_reqs script so we can test deps coming from PyPI
+            python -m pip install -e .[test]
           fi
           python -m pip install autorelease
       - name: "Install"
         run: |
           python -m pip install --no-deps -e .
-          python -c "import openpathsampling"
+      - name: "Check installation"
+        run: |
+          python -c "import openpathsampling; print(openpathsampling.version.full_version)"
       - name: "Versions"
         run: |
           conda info --envs
           conda list
       - name: "Autorelease check"
         run: python devtools/autorelease_check.py
-      - name: "Unit tests"
+      #- name: "DEBUG: enable SSH login"
+        #uses: mxschmitt/action-tmate@v3
+      - name: "Unit Tests"
         env:
           PY_COLORS: "1"
         run: py.test -vv -s --cov=openpathsampling --cov-report xml
diff --git a/.github/workflows/test-openmm-rc.yml b/.github/workflows/test-openmm-rc.yml
index f23fc8273..4f6e72e62 100644
--- a/.github/workflows/test-openmm-rc.yml
+++ b/.github/workflows/test-openmm-rc.yml
@@ -3,6 +3,7 @@
 name: "Test OpenMM Release Candidate"
 on:
   workflow_dispatch:
+  workflow_call:
   # use this for debugging
   #pull_request:
     #branch: master
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index b0388434c..b23b5e3f7 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -31,12 +31,12 @@ jobs:
       #   CVs) can differ between minor Python versions.
       matrix:
         CONDA_PY:
-          - "3.9"
-          - "3.8"
-          - "3.7"
+          - "3.12"
+          - "3.11"
+          - "3.10"
         MINIMAL: [""]
         include:
-          - CONDA_PY: "3.7"
+          - CONDA_PY: "3.10"
             MINIMAL: "minimal"
 
     steps:
@@ -57,14 +57,18 @@ jobs:
           if [ -z "$MINIMAL" ] ; then
             source devtools/conda_install_reqs.sh
           else
-            python -m pip install -r devtools/minimal.txt \
-                                  -r devtools/minimal_testing.txt
+            # In this case we're actually double-installing (not just
+            # installing requirements here). We prefer this to using the
+            # conda_install_reqs script so we can test deps coming from PyPI
+            python -m pip install -e .[test]
           fi
           python -m pip install autorelease
       - name: "Install"
         run: |
           python -m pip install --no-deps -e .
-          python -c "import openpathsampling"
+      - name: "Check installation"
+        run: |
+          python -c "import openpathsampling; print(openpathsampling.version.full_version)"
       - name: "Versions"
         run: conda list
       - name: "Autorelease check"
diff --git a/devtools/ci/ipythontests.sh b/devtools/ci/ipythontests.sh
deleted file mode 100755
index 2c2a09169..000000000
--- a/devtools/ci/ipythontests.sh
+++ /dev/null
@@ -1,7 +0,0 @@
-#!/bin/sh
-# Run ipython notebook tests
-
-# TODO: completely remove this in favor of putting everything into
-# examples/ipynbtests.sh
-
-pushd examples/ && ./ipynbtests.sh || exit 1 && popd
diff --git a/devtools/ci/pytests.sh b/devtools/ci/pytests.sh
deleted file mode 100755
index 97fe8c8e0..000000000
--- a/devtools/ci/pytests.sh
+++ /dev/null
@@ -1,29 +0,0 @@
-#!/bin/sh
-
-echo travis_fold:start:pytests
-echo Run pytest tests ...
-
-testfail=0
-pytest -vv -s --cov --cov-report xml:cov.xml || testfail=1
-#nosetests -v -s --with-coverage || testfail=1
-COVERALLS_PARALLEL=true coveralls
-echo travis_fold:end:pytests
-
-if [ $testfail -eq 1 ]
-then
-    exit 1
-fi
-
-if [ $CONDA_PY != "2.7" ] && [ -z "$MINIMAL" ]; then
-    # experimental does not need to support Python 2
-    echo travis_fold:start:experimental
-    echo Running tests on experimental features
-    pytest openpathsampling/experimental/ -vv -s || testfail=1
-    echo travis_fold:end:experimental
-fi
-
-
-if [ $testfail -eq 1 ]
-then
-    exit 1
-fi
diff --git a/devtools/minimal_testing.txt b/devtools/minimal_testing.txt
deleted file mode 100644
index c3f4152f8..000000000
--- a/devtools/minimal_testing.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-nose
-pytest
-pytest-cov
-coveralls
diff --git a/devtools/tested_integrations.txt b/devtools/tested_integrations.txt
index 0cbaab86b..19c98c83c 100644
--- a/devtools/tested_integrations.txt
+++ b/devtools/tested_integrations.txt
@@ -1,6 +1,4 @@
 jupyter
-openmm!=7.5.1
+openmm>=7.6
 openmmtools
 py-plumed
-pyemma
-
diff --git a/examples/ipynbtests.sh b/examples/ipynbtests.sh
index 5cdbafe8c..13c941914 100755
--- a/examples/ipynbtests.sh
+++ b/examples/ipynbtests.sh
@@ -6,7 +6,8 @@ testfail=0
 PYTHON_VERSION=`python -V 2>&1 | awk '{print $2}' | awk 'BEGIN { FS="." } { print $1 "." $2}'`
 echo "Running tests for Python version: $PYTHON_VERSION"
 
-dropbox_base_url="http://www.dropbox.com/s"
+dropbox_base_url="https://www.dropbox.com/s"
+dropbox_base_url2="https://www.dropbox.com/scl/fi"
 
 case $PYTHON_VERSION in
     "2.7")
@@ -29,14 +30,26 @@ case $PYTHON_VERSION in
         mstis=$dropbox_base_url/8rr0tt25xlm47cs/toy_mstis_1k_OPS1_py38.nc
         mistis=$dropbox_base_url/r3d5s5txbnpste0/toy_mistis_1k_OPS1_py38.nc
         ;;
+    "3.10")
+        mstis=$dropbox_base_url/8rr0tt25xlm47cs/toy_mstis_1k_OPS1_py38.nc
+        mistis=$dropbox_base_url/r3d5s5txbnpste0/toy_mistis_1k_OPS1_py38.nc
+        ;;
+    "3.11")
+        mstis="$dropbox_base_url2/c4idtymcaqvtftigce49c/toy_mstis_1k_OPS1_py311.nc?rlkey=soa6ba7okx66439egjyuscc5l&dl=1"
+        mistis="$dropbox_base_url2/s0o7br93s60q0vez88phr/toy_mistis_1k_OPS1_py311.nc?rlkey=22vdvjovt25bj4a6gh3m4vrvj&dl=1"
+        ;;
+    "3.12")
+        mstis="$dropbox_base_url2/wnqnwh0mmnhvm69h7h0br/toy_mstis_1k_OPS1_py312.nc?rlkey=6l2m6xiphvkpspp0ynix2m6zn&dl=1"
+        mistis="$dropbox_base_url2/tvajjucljm83a2eo6m9ps/toy_mistis_1k_OPS1_py312.nc?rlkey=dk99cj8nlwu1re42drqngf59c&dl=1"
+        ;;
     *)
         echo "Unsupported Python version: $PYTHON_VERSION"
 esac
 
-curl -OLk $mstis
-curl -OLk $mistis
-cp `basename $mstis` toy_mstis_1k_OPS1.nc
-cp `basename $mistis` toy_mistis_1k_OPS1.nc
+set -x
+curl -Lk --http1.1 -o toy_mstis_1k_OPS1.nc $mstis
+curl -Lk --http1.1 -o toy_mistis_1k_OPS1.nc $mistis 
+set +x
 
 # from here should be the same for all versions
 ls *nc
diff --git a/examples/tests/test_cv.ipynb b/examples/tests/test_cv.ipynb
index bc71383c1..88f1b2747 100644
--- a/examples/tests/test_cv.ipynb
+++ b/examples/tests/test_cv.ipynb
@@ -6,7 +6,7 @@
    "source": [
     "### Test of CV functionality. \n",
     "\n",
-    "Most storage related tests are nosetests now. So look in `testcollectivevariable.py` please."
+    "Most storage related tests are unit tests now. So look in `testcollectivevariable.py` please."
    ]
   },
   {
@@ -620,7 +620,7 @@
  "metadata": {
   "anaconda-cloud": {},
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -634,7 +634,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.3"
+   "version": "3.11.7"
   },
   "toc": {
    "base_numbering": 1,
@@ -680,5 +680,5 @@
   }
  },
  "nbformat": 4,
- "nbformat_minor": 1
+ "nbformat_minor": 4
 }
diff --git a/examples/tests/test_netcdfplus.ipynb b/examples/tests/test_netcdfplus.ipynb
index b2ce2bc6a..0e3525edc 100644
--- a/examples/tests/test_netcdfplus.ipynb
+++ b/examples/tests/test_netcdfplus.ipynb
@@ -235,6 +235,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
     "st.nodes.save(Node(10));"
    ]
   },
@@ -370,7 +371,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "['bool', 'float', 'index', 'int', 'json', 'jsonobj', 'lazyobj', u'lazyobj.attributes', u'lazyobj.dict', u'lazyobj.dictimmutable', u'lazyobj.nodes', u'lazyobj.nodesnamed', u'lazyobj.nodesunique', 'lazyobj.stores', u'lazyobj.varstore', u'lazyuuid.attributes', u'lazyuuid.dict', u'lazyuuid.dictimmutable', u'lazyuuid.nodes', u'lazyuuid.nodesnamed', u'lazyuuid.nodesunique', 'lazyuuid.stores', u'lazyuuid.varstore', 'length', 'long', 'numpy.float32', 'numpy.float64', 'numpy.int16', 'numpy.int32', 'numpy.int64', 'numpy.int8', 'numpy.uint16', 'numpy.uint32', 'numpy.uint64', 'numpy.uint8', 'obj', u'obj.attributes', u'obj.dict', u'obj.dictimmutable', u'obj.nodes', u'obj.nodesnamed', u'obj.nodesunique', 'obj.stores', u'obj.varstore', 'store', 'str', 'uuid', u'uuid.attributes', u'uuid.dict', u'uuid.dictimmutable', u'uuid.nodes', u'uuid.nodesnamed', u'uuid.nodesunique', 'uuid.stores', u'uuid.varstore']\n"
+      "['bool', 'float', 'index', 'int', 'json', 'jsonobj', 'lazyobj', 'lazyobj.attributes', 'lazyobj.dict', 'lazyobj.dictimmutable', 'lazyobj.nodes', 'lazyobj.nodesnamed', 'lazyobj.nodesunique', 'lazyobj.stores', 'lazyobj.varstore', 'lazyuuid.attributes', 'lazyuuid.dict', 'lazyuuid.dictimmutable', 'lazyuuid.nodes', 'lazyuuid.nodesnamed', 'lazyuuid.nodesunique', 'lazyuuid.stores', 'lazyuuid.varstore', 'length', 'long', 'numpy.float32', 'numpy.float64', 'numpy.int16', 'numpy.int32', 'numpy.int64', 'numpy.int8', 'numpy.uint16', 'numpy.uint32', 'numpy.uint64', 'numpy.uint8', 'obj', 'obj.attributes', 'obj.dict', 'obj.dictimmutable', 'obj.nodes', 'obj.nodesnamed', 'obj.nodesunique', 'obj.stores', 'obj.varstore', 'store', 'str', 'uuid', 'uuid.attributes', 'uuid.dict', 'uuid.dictimmutable', 'uuid.nodes', 'uuid.nodesnamed', 'uuid.nodesunique', 'uuid.stores', 'uuid.varstore']\n"
      ]
     }
    ],
@@ -430,84 +431,84 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "attributes_cache (u'attributes',)\n",
-      "attributes_json (u'attributes',)\n",
-      "attributes_name (u'attributes',)\n",
-      "attributes_uuid (u'attributes',)\n",
-      "bool (u'pair', u'pair', u'pair')\n",
-      "dict_json (u'dict',)\n",
-      "dict_name (u'dict',)\n",
-      "dict_uuid (u'dict',)\n",
-      "dictimmutable_json (u'dictimmutable',)\n",
-      "dictimmutable_name (u'dictimmutable',)\n",
-      "dictimmutable_uuid (u'dictimmutable',)\n",
-      "float (u'pair', u'pair', u'pair')\n",
-      "index (u'pair', u'pair', u'pair')\n",
-      "int (u'pair', u'pair', u'pair')\n",
-      "json (u'pair', u'pair', u'pair')\n",
-      "jsonobj (u'pair', u'pair', u'pair')\n",
-      "lazyobj (u'pair', u'pair', u'pair', u'pair')\n",
-      "lazyobj.attributes (u'pair', u'pair', u'pair')\n",
-      "lazyobj.dict (u'pair', u'pair', u'pair')\n",
-      "lazyobj.dictimmutable (u'pair', u'pair', u'pair')\n",
-      "lazyobj.nodes (u'pair', u'pair', u'pair')\n",
-      "lazyobj.nodesnamed (u'pair', u'pair', u'pair')\n",
-      "lazyobj.nodesunique (u'pair', u'pair', u'pair')\n",
-      "lazyobj.stores (u'pair', u'pair', u'pair')\n",
-      "lazyobj.varstore (u'pair', u'pair', u'pair')\n",
-      "lazyuuid.attributes (u'pair', u'pair', u'pair')\n",
-      "lazyuuid.dict (u'pair', u'pair', u'pair')\n",
-      "lazyuuid.dictimmutable (u'pair', u'pair', u'pair')\n",
-      "lazyuuid.nodes (u'pair', u'pair', u'pair')\n",
-      "lazyuuid.nodesnamed (u'pair', u'pair', u'pair')\n",
-      "lazyuuid.nodesunique (u'pair', u'pair', u'pair')\n",
-      "lazyuuid.stores (u'pair', u'pair', u'pair')\n",
-      "lazyuuid.varstore (u'pair', u'pair', u'pair')\n",
-      "length (u'pair', u'pair', u'pair')\n",
-      "long (u'pair', u'pair', u'pair')\n",
-      "nodes_json (u'nodes',)\n",
-      "nodes_uuid (u'nodes',)\n",
-      "nodesnamed_json (u'nodesnamed',)\n",
-      "nodesnamed_name (u'nodesnamed',)\n",
-      "nodesnamed_uuid (u'nodesnamed',)\n",
-      "nodesunique_json (u'nodesunique',)\n",
-      "nodesunique_name (u'nodesunique',)\n",
-      "nodesunique_uuid (u'nodesunique',)\n",
-      "numpy.float32 (u'pair', u'pair', u'pair')\n",
-      "numpy.float64 (u'pair', u'pair', u'pair')\n",
-      "numpy.int16 (u'pair', u'pair', u'pair')\n",
-      "numpy.int32 (u'pair', u'pair', u'pair')\n",
-      "numpy.int64 (u'pair', u'pair', u'pair')\n",
-      "numpy.int8 (u'pair', u'pair', u'pair')\n",
-      "numpy.uint16 (u'pair', u'pair', u'pair')\n",
-      "numpy.uint32 (u'pair', u'pair', u'pair')\n",
-      "numpy.uint64 (u'pair', u'pair', u'pair')\n",
-      "numpy.uint8 (u'pair', u'pair', u'pair')\n",
-      "obj (u'pair', u'pair', u'pair', u'pair')\n",
-      "obj.attributes (u'pair', u'pair', u'pair')\n",
-      "obj.dict (u'pair', u'pair', u'pair')\n",
-      "obj.dictimmutable (u'pair', u'pair', u'pair')\n",
-      "obj.nodes (u'pair', u'pair', u'pair')\n",
-      "obj.nodesnamed (u'pair', u'pair', u'pair')\n",
-      "obj.nodesunique (u'pair', u'pair', u'pair')\n",
-      "obj.stores (u'pair', u'pair', u'pair')\n",
-      "obj.varstore (u'pair', u'pair', u'pair')\n",
-      "store (u'pair', u'pair', u'pair')\n",
-      "stores_json (u'stores',)\n",
-      "stores_name (u'stores',)\n",
-      "stores_uuid (u'stores',)\n",
-      "str (u'pair', u'pair', u'pair')\n",
-      "uuid (u'pair', u'pair', u'pair')\n",
-      "uuid.attributes (u'pair', u'pair', u'pair')\n",
-      "uuid.dict (u'pair', u'pair', u'pair')\n",
-      "uuid.dictimmutable (u'pair', u'pair', u'pair')\n",
-      "uuid.nodes (u'pair', u'pair', u'pair')\n",
-      "uuid.nodesnamed (u'pair', u'pair', u'pair')\n",
-      "uuid.nodesunique (u'pair', u'pair', u'pair')\n",
-      "uuid.stores (u'pair', u'pair', u'pair')\n",
-      "uuid.varstore (u'pair', u'pair', u'pair')\n",
-      "varstore_uuid (u'varstore',)\n",
-      "varstore_value (u'varstore',)\n"
+      "attributes_cache ('attributes',)\n",
+      "attributes_json ('attributes',)\n",
+      "attributes_name ('attributes',)\n",
+      "attributes_uuid ('attributes',)\n",
+      "bool ('pair', 'pair', 'pair')\n",
+      "dict_json ('dict',)\n",
+      "dict_name ('dict',)\n",
+      "dict_uuid ('dict',)\n",
+      "dictimmutable_json ('dictimmutable',)\n",
+      "dictimmutable_name ('dictimmutable',)\n",
+      "dictimmutable_uuid ('dictimmutable',)\n",
+      "float ('pair', 'pair', 'pair')\n",
+      "index ('pair', 'pair', 'pair')\n",
+      "int ('pair', 'pair', 'pair')\n",
+      "json ('pair', 'pair', 'pair')\n",
+      "jsonobj ('pair', 'pair', 'pair')\n",
+      "lazyobj ('pair', 'pair', 'pair', 'pair')\n",
+      "lazyobj.attributes ('pair', 'pair', 'pair')\n",
+      "lazyobj.dict ('pair', 'pair', 'pair')\n",
+      "lazyobj.dictimmutable ('pair', 'pair', 'pair')\n",
+      "lazyobj.nodes ('pair', 'pair', 'pair')\n",
+      "lazyobj.nodesnamed ('pair', 'pair', 'pair')\n",
+      "lazyobj.nodesunique ('pair', 'pair', 'pair')\n",
+      "lazyobj.stores ('pair', 'pair', 'pair')\n",
+      "lazyobj.varstore ('pair', 'pair', 'pair')\n",
+      "lazyuuid.attributes ('pair', 'pair', 'pair')\n",
+      "lazyuuid.dict ('pair', 'pair', 'pair')\n",
+      "lazyuuid.dictimmutable ('pair', 'pair', 'pair')\n",
+      "lazyuuid.nodes ('pair', 'pair', 'pair')\n",
+      "lazyuuid.nodesnamed ('pair', 'pair', 'pair')\n",
+      "lazyuuid.nodesunique ('pair', 'pair', 'pair')\n",
+      "lazyuuid.stores ('pair', 'pair', 'pair')\n",
+      "lazyuuid.varstore ('pair', 'pair', 'pair')\n",
+      "length ('pair', 'pair', 'pair')\n",
+      "long ('pair', 'pair', 'pair')\n",
+      "nodes_json ('nodes',)\n",
+      "nodes_uuid ('nodes',)\n",
+      "nodesnamed_json ('nodesnamed',)\n",
+      "nodesnamed_name ('nodesnamed',)\n",
+      "nodesnamed_uuid ('nodesnamed',)\n",
+      "nodesunique_json ('nodesunique',)\n",
+      "nodesunique_name ('nodesunique',)\n",
+      "nodesunique_uuid ('nodesunique',)\n",
+      "numpy.float32 ('pair', 'pair', 'pair')\n",
+      "numpy.float64 ('pair', 'pair', 'pair')\n",
+      "numpy.int16 ('pair', 'pair', 'pair')\n",
+      "numpy.int32 ('pair', 'pair', 'pair')\n",
+      "numpy.int64 ('pair', 'pair', 'pair')\n",
+      "numpy.int8 ('pair', 'pair', 'pair')\n",
+      "numpy.uint16 ('pair', 'pair', 'pair')\n",
+      "numpy.uint32 ('pair', 'pair', 'pair')\n",
+      "numpy.uint64 ('pair', 'pair', 'pair')\n",
+      "numpy.uint8 ('pair', 'pair', 'pair')\n",
+      "obj ('pair', 'pair', 'pair', 'pair')\n",
+      "obj.attributes ('pair', 'pair', 'pair')\n",
+      "obj.dict ('pair', 'pair', 'pair')\n",
+      "obj.dictimmutable ('pair', 'pair', 'pair')\n",
+      "obj.nodes ('pair', 'pair', 'pair')\n",
+      "obj.nodesnamed ('pair', 'pair', 'pair')\n",
+      "obj.nodesunique ('pair', 'pair', 'pair')\n",
+      "obj.stores ('pair', 'pair', 'pair')\n",
+      "obj.varstore ('pair', 'pair', 'pair')\n",
+      "store ('pair', 'pair', 'pair')\n",
+      "stores_json ('stores',)\n",
+      "stores_name ('stores',)\n",
+      "stores_uuid ('stores',)\n",
+      "str ('pair', 'pair', 'pair')\n",
+      "uuid ('pair', 'pair', 'pair')\n",
+      "uuid.attributes ('pair', 'pair', 'pair')\n",
+      "uuid.dict ('pair', 'pair', 'pair')\n",
+      "uuid.dictimmutable ('pair', 'pair', 'pair')\n",
+      "uuid.nodes ('pair', 'pair', 'pair')\n",
+      "uuid.nodesnamed ('pair', 'pair', 'pair')\n",
+      "uuid.nodesunique ('pair', 'pair', 'pair')\n",
+      "uuid.stores ('pair', 'pair', 'pair')\n",
+      "uuid.varstore ('pair', 'pair', 'pair')\n",
+      "varstore_uuid ('varstore',)\n",
+      "varstore_value ('varstore',)\n"
      ]
     }
    ],
@@ -782,7 +783,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "{'Test': 3, 'Hallo': 2}\n"
+      "{'Hallo': 2, 'Test': 3}\n"
      ]
     }
    ],
@@ -809,8 +810,8 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "[u'{\"_store\":\"nodes\",\"_hex_uuid\":\"0xebbeb5f0b75311e89830000000000024L\"}'\n",
-      " u'{\"Test\":3,\"Hallo\":2}']\n"
+      "['{\"_hex_uuid\":\"0xe3d4c53a010011ef9d1f000000000026\",\"_store\":\"nodes\"}'\n",
+      " '{\"Hallo\":2,\"Test\":3}']\n"
      ]
     }
    ],
@@ -968,17 +969,17 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "[[[u'ebbeb5f0-b753-11e8-9830-00000000002a'\n",
-      "   u'ebbeb5f0-b753-11e8-9830-00000000002e']\n",
-      "  [u'ebbeb5f0-b753-11e8-9830-00000000002c' u'']]\n",
+      "[[['e3d4c53a-0100-11ef-9d1f-00000000002c'\n",
+      "   'e3d4c53a-0100-11ef-9d1f-000000000030']\n",
+      "  ['e3d4c53a-0100-11ef-9d1f-00000000002e' '']]\n",
       "\n",
-      " [[u'' u'']\n",
-      "  [u'' u'']]]\n",
-      "[u'{\"_cls\":\"Node\",\"_dict\":{\"value\":10}}'\n",
-      " u'{\"_cls\":\"Node\",\"_dict\":{\"value\":10}}'\n",
-      " u'{\"_cls\":\"Node\",\"_dict\":{\"value\":1}}'\n",
-      " u'{\"_cls\":\"Node\",\"_dict\":{\"value\":\"Second\"}}'\n",
-      " u'{\"_cls\":\"Node\",\"_dict\":{\"value\":\"Third\"}}']\n"
+      " [['' '']\n",
+      "  ['' '']]]\n",
+      "['{\"_cls\":\"Node\",\"_dict\":{\"value\":10}}'\n",
+      " '{\"_cls\":\"Node\",\"_dict\":{\"value\":10}}'\n",
+      " '{\"_cls\":\"Node\",\"_dict\":{\"value\":1}}'\n",
+      " '{\"_cls\":\"Node\",\"_dict\":{\"value\":\"Second\"}}'\n",
+      " '{\"_cls\":\"Node\",\"_dict\":{\"value\":\"Third\"}}']\n"
      ]
     }
    ],
@@ -1048,7 +1049,7 @@
      "text": [
       "Type:    <class 'openpathsampling.netcdfplus.proxy.LoaderProxy'>\n",
       "Class:   <class '__main__.Node'>\n",
-      "Content: {'__uuid__': 313358805600210293968778471146830954544L, 'value': 'First'}\n",
+      "Content: {'__uuid__': 302839522207420201522962921396994310194, 'value': 'First'}\n",
       "Access:  First\n"
      ]
     }
@@ -1099,6 +1100,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
     "obj = Node('BlaBla')\n",
     "st.nodes.save(obj);"
    ]
@@ -1272,7 +1274,10 @@
   {
    "cell_type": "markdown",
    "metadata": {
-    "collapsed": true
+    "collapsed": true,
+    "jupyter": {
+     "outputs_hidden": true
+    }
    },
    "source": [
     "### ObjectStores"
@@ -1387,6 +1392,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
     "st.nodesnamed.save(n);"
    ]
   },
@@ -1427,6 +1433,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
     "n2 = NamedNode(9)\n",
     "n2.name = 'MyNode'\n",
     "st.nodesnamed.save(n2);"
@@ -1448,7 +1455,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "{'MyNode': set([0, 1])}\n"
+      "{'MyNode': {0, 1}}\n"
      ]
     }
    ],
@@ -1477,6 +1484,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
     "st.nodesunique.save(n);"
    ]
   },
@@ -1527,7 +1535,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "{'MyNode': set([0])}\n"
+      "{'MyNode': {0}}\n"
      ]
     }
    ],
@@ -1549,6 +1557,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
     "n3 = NamedNode(10)\n",
     "n4 = NamedNode(12)\n",
     "st.nodesunique.save(n3);\n",
@@ -1602,6 +1611,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
     "st.nodesunique.save(n5, 'NextNode');"
    ]
   },
@@ -1807,7 +1817,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "[ {\"_store\":\"nodesunique\",\"_hex_uuid\":\"0xebbeb5f0b75311e89830000000000048L\"}, {\"_store\":\"nodesunique\",\"_hex_uuid\":\"0xebbeb5f0b75311e8983000000000004aL\"}, {\"_store\":\"nodesunique\",\"_hex_uuid\":\"0xebbeb5f0b75311e8983000000000004cL\"} ]\n"
+      "[ {\"_hex_uuid\":\"0xe3d4c53a010011ef9d1f00000000004a\",\"_store\":\"nodesunique\"}, {\"_hex_uuid\":\"0xe3d4c53a010011ef9d1f00000000004c\",\"_store\":\"nodesunique\"}, {\"_hex_uuid\":\"0xe3d4c53a010011ef9d1f00000000004e\",\"_store\":\"nodesunique\"} ]\n"
      ]
     }
    ],
@@ -1879,7 +1889,10 @@
   {
    "cell_type": "markdown",
    "metadata": {
-    "collapsed": true
+    "collapsed": true,
+    "jupyter": {
+     "outputs_hidden": true
+    }
    },
    "source": [
     "#### VariableStore"
@@ -1898,6 +1911,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
     "a = Node(30)\n",
     "st.varstore.save(a);"
    ]
@@ -2078,7 +2092,7 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "0xebbeb5f0b75311e89830000000000012L\n"
+      "0xe3d4c53a010011ef9d1f000000000014\n"
      ]
     }
    ],
@@ -2122,7 +2136,7 @@
  "metadata": {
   "anaconda-cloud": {},
   "kernelspec": {
-   "display_name": "Python 3",
+   "display_name": "Python 3 (ipykernel)",
    "language": "python",
    "name": "python3"
   },
@@ -2136,7 +2150,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.7.3"
+   "version": "3.11.7"
   },
   "toc": {
    "base_numbering": 1,
@@ -2182,5 +2196,5 @@
   }
  },
  "nbformat": 4,
- "nbformat_minor": 1
+ "nbformat_minor": 4
 }
diff --git a/examples/tests/test_openmm_integration.ipynb b/examples/tests/test_openmm_integration.ipynb
index ea29762f3..b6a43d34d 100644
--- a/examples/tests/test_openmm_integration.ipynb
+++ b/examples/tests/test_openmm_integration.ipynb
@@ -17,7 +17,9 @@
   {
    "cell_type": "code",
    "execution_count": 1,
-   "metadata": {},
+   "metadata": {
+    "scrolled": true
+   },
    "outputs": [],
    "source": [
     "from __future__ import print_function\n",
@@ -30,6 +32,7 @@
     "except ImportError: # OpenMM < 7.6\n",
     "    import simtk.openmm as omm\n",
     "    import simtk.unit as u\n",
+    "\n",
     "import mdtraj as md\n",
     "\n",
     "import openpathsampling.engines.openmm as eng"
@@ -42,8 +45,12 @@
    "outputs": [],
    "source": [
     "# NBVAL_IGNORE_OUTPUT\n",
-    "# Ignore until next OpenMMTools release (> 0.20.3), see openpathsampling/openpathsampling#1091\n",
-    "import openmmtools as omt"
+    "# hide stderr from openmmtools (apparently nbval still gets it)\n",
+    "from os import devnull\n",
+    "from contextlib import redirect_stderr\n",
+    "\n",
+    "with open(devnull, 'w') as dvnl, redirect_stderr(dvnl):\n",
+    "    import openmmtools as omt"
    ]
   },
   {
@@ -229,28 +236,28 @@
      "name": "stdout",
      "output_type": "stream",
      "text": [
-      "[[ 0.18766724  0.11599682  0.01245003]\n",
-      " [ 0.19458355  0.22444932  0.00401419]\n",
-      " [ 0.1443672   0.27122667  0.08869705]\n",
-      " [ 0.14886743  0.2574746  -0.08926158]\n",
-      " [ 0.33994532  0.26638272  0.0060984 ]\n",
-      " [ 0.42709577  0.18970107  0.04380053]\n",
-      " [ 0.36886498  0.38998336 -0.03181566]\n",
-      " [ 0.29336688  0.45243084 -0.05633819]\n",
-      " [ 0.49859306  0.45657611 -0.02400118]\n",
-      " [ 0.55814379  0.41515264  0.0573551 ]\n",
-      " [ 0.57325351  0.43391329 -0.15561192]\n",
-      " [ 0.51911271  0.48312774 -0.23640621]\n",
-      " [ 0.67427629  0.47320485 -0.14414111]\n",
-      " [ 0.58423072  0.32728142 -0.17536469]\n",
-      " [ 0.47495225  0.60669041 -0.00127666]\n",
-      " [ 0.359584    0.64924204  0.00474686]\n",
-      " [ 0.58359265  0.68379164  0.00902625]\n",
-      " [ 0.67474234  0.64066118  0.00332434]\n",
-      " [ 0.57127434  0.82897234  0.01853087]\n",
-      " [ 0.46718949  0.86095488  0.02347777]\n",
-      " [ 0.62214309  0.86301541  0.108722  ]\n",
-      " [ 0.61715913  0.87434274 -0.06931632]] nm\n"
+      "[[ 0.18766734  0.11599676  0.01245005]\n",
+      " [ 0.19458354  0.22444928  0.00401417]\n",
+      " [ 0.14436737  0.27122667  0.08869709]\n",
+      " [ 0.14886761  0.25747457 -0.08926168]\n",
+      " [ 0.33994532  0.26638275  0.00609838]\n",
+      " [ 0.42709583  0.18970108  0.04380053]\n",
+      " [ 0.36886492  0.38998333 -0.03181563]\n",
+      " [ 0.29336691  0.4524309  -0.05633818]\n",
+      " [ 0.49859303  0.45657605 -0.02400119]\n",
+      " [ 0.55814379  0.41515264  0.05735509]\n",
+      " [ 0.57325351  0.43391326 -0.1556119 ]\n",
+      " [ 0.51911277  0.48312786 -0.23640618]\n",
+      " [ 0.67427617  0.47320503 -0.14414097]\n",
+      " [ 0.58423084  0.32728145 -0.17536484]\n",
+      " [ 0.47495231  0.60669047 -0.00127665]\n",
+      " [ 0.35958403  0.64924204  0.00474683]\n",
+      " [ 0.58359265  0.6837917   0.00902626]\n",
+      " [ 0.67474234  0.64066118  0.00332438]\n",
+      " [ 0.57127434  0.82897246  0.01853088]\n",
+      " [ 0.46718928  0.86095428  0.02347768]\n",
+      " [ 0.62214309  0.86301523  0.1087221 ]\n",
+      " [ 0.61715913  0.8743425  -0.06931649]] nm\n"
      ]
     }
    ],
@@ -268,7 +275,7 @@
      "name": "stderr",
      "output_type": "stream",
      "text": [
-      "/home/sander/miniconda3/envs/python3/lib/python3.9/site-packages/mdtraj/utils/validation.py:115: TypeCastPerformanceWarning: Casting unitcell_vectors dtype=float64 to <class 'numpy.float32'> \n",
+      "/Users/dwhs/mambaforge/envs/dev/lib/python3.9/site-packages/mdtraj/utils/validation.py:115: TypeCastPerformanceWarning: Casting unitcell_vectors dtype=float64 to <class 'numpy.float32'> \n",
       "  warnings.warn(\"Casting %s dtype=%s to %s \" % (name, val.dtype, dtype),\n"
      ]
     }
@@ -287,11 +294,11 @@
     {
      "data": {
       "text/plain": [
-       "array([[ 3.1061459],\n",
-       "       [-3.0958638],\n",
-       "       [ 3.1367562],\n",
-       "       [ 3.1283274],\n",
-       "       [ 3.0931776]], dtype=float32)"
+       "array([[ 3.1061456],\n",
+       "       [-3.0958636],\n",
+       "       [ 3.136756 ],\n",
+       "       [ 3.128327 ],\n",
+       "       [ 3.0931773]], dtype=float32)"
       ]
      },
      "execution_count": 13,
@@ -338,7 +345,7 @@
     {
      "data": {
       "text/plain": [
-       "'{\"_cls\":\"OpenMMEngine\",\"_dict\":{\"system_xml\":\"<?xml version=\\\\\"1.0\\\\\" ?>\\\\n<System openmmVersion=\\\\\"7.6\\\\\" type=\\\\\"System\\\\\" version=\\\\\"1\\\\\">\\\\n\\\\t<PeriodicBoxVectors>\\\\n\\\\t\\\\t<A x=\\\\\"2\\\\\" y=\\\\\"0\\\\\" z=\\\\\"0\\\\\"\\\\/>\\\\n\\\\t\\\\t<B x=\\\\\"0\\\\\" y=\\\\\"2\\\\\" z=\\\\\"0\\\\\"\\\\/>\\\\n\\\\t\\\\t<C x=\\\\\"0\\\\\" y=\\\\\"0\\\\\" z=\\\\\"2...'"
+       "'{\"_cls\":\"OpenMMEngine\",\"_dict\":{\"system_xml\":\"<?xml version=\\\\\"1.0\\\\\" ?>\\\\n<System openmmVersion=\\\\\"7.7\\\\\" type=\\\\\"System\\\\\" version=\\\\\"1\\\\\">\\\\n\\\\t<PeriodicBoxVectors>\\\\n\\\\t\\\\t<A x=\\\\\"2\\\\\" y=\\\\\"0\\\\\" z=\\\\\"0\\\\\"\\\\/>\\\\n\\\\t\\\\t<B x=\\\\\"0\\\\\" y=\\\\\"2\\\\\" z=\\\\\"0\\\\\"\\\\/>\\\\n\\\\t\\\\t<C x=\\\\\"0\\\\\" y=\\\\\"0\\\\\" z=\\\\\"2...'"
       ]
      },
      "execution_count": 16,
@@ -357,6 +364,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
+    "# NBVAL_IGNORE_OUTPUT\n",
     "st.save(traj);"
    ]
   },
@@ -462,21 +470,14 @@
     "st.save(traj)\n",
     "st.close()"
    ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {},
-   "outputs": [],
-   "source": []
   }
  ],
  "metadata": {
   "anaconda-cloud": {},
   "kernelspec": {
-   "display_name": "Python 3 (ipykernel)",
+   "display_name": "dev",
    "language": "python",
-   "name": "python3"
+   "name": "dev"
   },
   "language_info": {
    "codemirror_mode": {
@@ -488,7 +489,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.7"
+   "version": "3.9.16"
   },
   "toc": {
    "base_numbering": 1,
@@ -534,5 +535,5 @@
   }
  },
  "nbformat": 4,
- "nbformat_minor": 1
+ "nbformat_minor": 4
 }
diff --git a/openpathsampling/experimental/simstore/test_attribute_handlers.py b/openpathsampling/experimental/simstore/test_attribute_handlers.py
index 244d720a4..48bd265aa 100644
--- a/openpathsampling/experimental/simstore/test_attribute_handlers.py
+++ b/openpathsampling/experimental/simstore/test_attribute_handlers.py
@@ -5,7 +5,7 @@
 from .attribute_handlers import *
 
 class TestStandardHandler(object):
-    def setup(self):
+    def setup_method(self):
         self.obj = {'str': 'foo',
                     'int': 42,
                     'float': 3.14159,
@@ -30,7 +30,7 @@ def test_serialization_cycle(self, type_str):
 
 
 class TestNDArrayHandler(object):
-    def setup(self):
+    def setup_method(self):
         self.data = np.array([[0.0, 1.0, 2.0], [3.0, 4.0, 5.0]])
         self.ndarray_handler = NDArrayHandler(('float32', (2,3)))
 
diff --git a/openpathsampling/experimental/simstore/test_callable_codec.py b/openpathsampling/experimental/simstore/test_callable_codec.py
index 618304906..a06a90cac 100644
--- a/openpathsampling/experimental/simstore/test_callable_codec.py
+++ b/openpathsampling/experimental/simstore/test_callable_codec.py
@@ -1,6 +1,8 @@
 import pytest
+from unittest.mock import Mock
 from .callable_codec import *
 import dill
+import codecs
 
 import numpy as np
 
@@ -14,7 +16,7 @@ def _globals_using_function(self):
     return _global_var
 
 class TestCallableCodec(object):
-    def setup(self):
+    def setup_method(self):
         self.codec = CallableCodec()
         self.functions = {
             'generic': lambda x: x.xyz[0][0],
@@ -26,8 +28,10 @@ def setup(self):
         for func in ['generic', 'use_known_module', 'use_globals']:
             self.functions[func].__module__ = "__main__"
 
-        dilled = {key: dill.dumps(func, recurse=True)
-                  for key, func in self.functions.items()}
+        dilledbytes = {key: dill.dumps(func, recurse=True)
+                       for key, func in self.functions.items()}
+        dilled = {key: str(codecs.encode(val, 'base64'), 'utf-8')
+                  for key, val in dilledbytes.items()}
         self.dcts = {
             'generic': {
                 '__callable_name__': '<lambda>',
@@ -47,6 +51,16 @@ def setup(self):
             }
             # use_globals never gets serialized
         }
+        self.old_dicts = {
+            'generic': {
+                '__callable_name__': '<lambda>',
+                '_dilled': dilledbytes['generic']
+            },
+            'use_known_module': {
+                '__callable_name__': '_known_module_func',
+                '_dilled': dilledbytes['use_known_module']
+            }
+        }
 
     @pytest.mark.parametrize(
         'func,known_modules,allow_unknown',
@@ -109,6 +123,22 @@ def test_object_hook(self, func, safe):
         if not safemode and func not in untestable:
             assert result == expected
 
+    def test_object_hook_old(self):
+        # TODO: OPS 2 will drop support for the functionality this tests
+        func = 'generic'  # others won't hit this part of the code
+        self.codec.known_modules = ['openpathsampling', 'numpy']
+        self.codec.safemode = False
+        warntext = "representation of callables"
+        with pytest.warns(FutureWarning, match=warntext):
+            result = self.codec.object_hook(self.old_dicts[func])
+
+        # test the function gives a few values the same
+        mock1 = Mock(xyz=[[1.0]])
+        mock2 = Mock(xyz=[[2.0], [3.0]])
+        expected = self.functions[func]
+        assert result(mock1) == expected(mock1) == 1.0
+        assert result(mock2) == expected(mock2) == 2.0
+
     def test_object_hook_not_mine(self):
         dct = {'foo': 'bar'}
         assert self.codec.object_hook(dct) is dct
diff --git a/openpathsampling/experimental/simstore/test_class_info.py b/openpathsampling/experimental/simstore/test_class_info.py
index b2c51d403..ed0de202f 100644
--- a/openpathsampling/experimental/simstore/test_class_info.py
+++ b/openpathsampling/experimental/simstore/test_class_info.py
@@ -42,7 +42,7 @@ def test_init_default_setup(self):
         assert class_info.lookup_result == MockUUIDObject
 
 class SerializationSchemeTester(object):
-    def setup(self):
+    def setup_method(self):
         self.data_obj = all_objects['int']  # doesn't really matter which
         # technically, the next two should behave identically, except
         # name/UUID; but as different types they can be serailized
diff --git a/openpathsampling/experimental/simstore/test_custom_json.py b/openpathsampling/experimental/simstore/test_custom_json.py
index 95192257d..84d5858ed 100644
--- a/openpathsampling/experimental/simstore/test_custom_json.py
+++ b/openpathsampling/experimental/simstore/test_custom_json.py
@@ -54,7 +54,7 @@ def test_not_mine(self):
 
 
 class TestNumpyCoding(CustomJSONCodingTest):
-    def setup(self):
+    def setup_method(self):
         self.codec = numpy_codec
         self.objs = [np.array([[1.0, 0.0], [2.0, 3.2]]),
                      np.array([1, 0])]
@@ -89,7 +89,7 @@ def test_round_trip(self):
 
 
 class TestUUIDCoding(object):
-    def setup(self):
+    def setup_method(self):
         self.codec = uuid_object_codec
         all_objs = test_utils.all_objects
         self.objs = [all_objs['int'], all_objs['str']]
diff --git a/openpathsampling/experimental/simstore/test_dict_serialization_helpers.py b/openpathsampling/experimental/simstore/test_dict_serialization_helpers.py
index 5c4a14d22..9281cc7c5 100644
--- a/openpathsampling/experimental/simstore/test_dict_serialization_helpers.py
+++ b/openpathsampling/experimental/simstore/test_dict_serialization_helpers.py
@@ -18,7 +18,7 @@ def to_dict(self):
         def from_dict(cls, dct):
             return cls(**dct)
 
-    def setup(self):
+    def setup_method(self):
         self.foo = {('a', 'b'): [1, 2], ('c', 'd'): [3, 4]}
         self.bar = 3
         self.obj = self.ExampleObj(self.foo, self.bar)
diff --git a/openpathsampling/experimental/simstore/test_serialization.py b/openpathsampling/experimental/simstore/test_serialization.py
index 902ee0000..3c2a5d4a5 100644
--- a/openpathsampling/experimental/simstore/test_serialization.py
+++ b/openpathsampling/experimental/simstore/test_serialization.py
@@ -12,7 +12,7 @@
 
 
 class TestGenericLazyLoader(object):
-    def setup(self):
+    def setup_method(self):
         original_and_class = {
             'normal': (all_objects['int'], MockUUIDObject)
             # TODO: add iterable and mappable classes
@@ -97,7 +97,7 @@ def test_save_proxy(self):
 
 
 class TestProxyObjectFactory(object):
-    def setup(self):
+    def setup_method(self):
         self.storage = LoadingStorageMock({get_uuid(obj): obj
                                            for obj in all_objects.values()})
 
diff --git a/openpathsampling/experimental/simstore/test_sql_backend.py b/openpathsampling/experimental/simstore/test_sql_backend.py
index 52930817a..d81e1579d 100644
--- a/openpathsampling/experimental/simstore/test_sql_backend.py
+++ b/openpathsampling/experimental/simstore/test_sql_backend.py
@@ -2,7 +2,7 @@
 import pytest
 
 class TestSQLStorageBackend(object):
-    def setup(self):
+    def setup_method(self):
         self._delete_tmp_files()
         self.database = self._default_database
         self.database.debug = True
@@ -49,7 +49,7 @@ def _add_snapshot_data(self):
         return snap_dicts
 
 
-    def teardown(self):
+    def teardown_method(self):
         self._delete_tmp_files()
 
     @property
diff --git a/openpathsampling/experimental/simstore/test_storable_function.py b/openpathsampling/experimental/simstore/test_storable_function.py
index a0ec911f0..78fccb97a 100644
--- a/openpathsampling/experimental/simstore/test_storable_function.py
+++ b/openpathsampling/experimental/simstore/test_storable_function.py
@@ -71,7 +71,7 @@ def test_wrap_numpy():
         assert isinstance(wrap_numpy(inp), np.ndarray)
 
 class TestStorableFunctionConfig(object):
-    def setup(self):
+    def setup_method(self):
         self.config = StorableFunctionConfig(processors=[
             scalarize_singletons,
             wrap_numpy,
@@ -160,7 +160,7 @@ def test_storable_function_integration(self):
 
 
 class TestStorableFunctionResults(object):
-    def setup(self):
+    def setup_method(self):
         self.cv = StorableFunction(lambda x: x)
         self.cv.__uuid__ = "funcUUID"
         self.mapping = {'UUID1': "foo",
@@ -219,7 +219,7 @@ def test_to_dict_from_dict_cycle(self):
 @mock.patch(_MODULE + '.get_uuid', lambda x: x)
 @mock.patch(_MODULE + '.has_uuid', lambda x: isinstance(x, str))
 class TestStorableFunction(object):
-    def setup(self):
+    def setup_method(self):
         def get_expected(uuid):
             expected = {'uuid': 'eval', 'uuid1': 'other'}
             return expected[uuid]
@@ -394,7 +394,7 @@ def test_analysis_mode_integration(self, found_in):
 
 
 class TestStorageFunctionHandler(object):
-    def setup(self):
+    def setup_method(self):
         self.backend = MockBackend()
         self.storage = mock.NonCallableMock(backend=self.backend)
         self.sf_handler = StorageFunctionHandler(self.storage)
diff --git a/openpathsampling/experimental/simstore/test_storable_function_integration.py b/openpathsampling/experimental/simstore/test_storable_function_integration.py
index 44dc75a0e..83fc42871 100644
--- a/openpathsampling/experimental/simstore/test_storable_function_integration.py
+++ b/openpathsampling/experimental/simstore/test_storable_function_integration.py
@@ -28,6 +28,8 @@
 
 from openpathsampling.netcdfplus import StorableNamedObject
 
+import sqlalchemy.pool
+
 class InputObj(StorableNamedObject):
     pass  # just a thing that has a UUID
 
@@ -285,7 +287,7 @@ def test_multiple_storage_inside_other_object(tmpdir, inputs_and_func):
     container.cv.local_cache.clear()
     assert container.cv.func.call_count == 2
     assert container(inp2) == 'f'
-    assert container.cv.func.call_count == 2 
+    assert container.cv.func.call_count == 2
 
 def test_closing(tmpdir, inputs_and_func):
     inp1, inp2, func, sf, container = inputs_and_func
@@ -300,16 +302,21 @@ def test_closing(tmpdir, inputs_and_func):
     assert not sf.has_handler
 
 def test_storage_file_problem(tmpdir, inputs_and_func):
+    # need to switch back to NullPool in SQLAlchemy 2.0+, otherwise it
+    # appears to cache the old results. See discussion here:
+    # https://github.com/sqlalchemy/sqlalchemy/discussions/9167
     inp1, inp2, func, sf, container = inputs_and_func
     sf_uuid = str(get_uuid(sf))
     st1_fname = tmpdir.join("st1.db")
-    be_1w = SQLStorageBackend(st1_fname, mode='w')
+    be_1w = SQLStorageBackend(st1_fname, mode='w',
+                              poolclass=sqlalchemy.pool.NullPool)
     _store_via_container(be_1w, container, inp1, call_count=1,
                          backend_count=1)
 
     GeneralStorage._known_storages = {}
     storage = GeneralStorage(
-        backend=SQLStorageBackend(st1_fname, mode='r'),
+        backend=SQLStorageBackend(st1_fname, mode='r',
+                                  poolclass=sqlalchemy.pool.NullPool),
         class_info=_serialization,
         schema=_schema
     )
@@ -318,6 +325,7 @@ def test_storage_file_problem(tmpdir, inputs_and_func):
     assert container.cv.func.call_count == 1
     os.remove(st1_fname)  # naughty user behavior
 
+    # this requires using the NullPool, apparently
     with pytest.warns(UserWarning):
         assert container(inp1) == 'f'
     assert container.cv.func.call_count == 2
diff --git a/openpathsampling/experimental/simstore/test_storage.py b/openpathsampling/experimental/simstore/test_storage.py
index 5ea68462b..623cf0540 100644
--- a/openpathsampling/experimental/simstore/test_storage.py
+++ b/openpathsampling/experimental/simstore/test_storage.py
@@ -7,7 +7,7 @@
 
 
 class TestStorageTable(object):
-    def setup(self):
+    def setup_method(self):
         self.storage = MockStorage()
         # Override default tables/uuids
         self.storage.backend.table_names = ["all"]
@@ -52,7 +52,7 @@ def test_bogus_access(self):
 
 
 class TestPseudoTable(object):
-    def setup(self):
+    def setup_method(self):
         self.objs = {None: UnnamedUUID(normal_attr=10)}
         self.objs.update(all_objects)
         self.obj_list = list(self.objs.values())
diff --git a/openpathsampling/experimental/simstore/test_tags_table.py b/openpathsampling/experimental/simstore/test_tags_table.py
index 020145eb2..d81f28e96 100644
--- a/openpathsampling/experimental/simstore/test_tags_table.py
+++ b/openpathsampling/experimental/simstore/test_tags_table.py
@@ -17,7 +17,7 @@ def __init__(self, value):
         self.value = value
 
 class TestTagsTable(object):
-    def setup(self):
+    def setup_method(self):
         json_ser = JSONSerializerDeserializer([uuid_object_codec])
         # TODO: add tags to serialization schema
         serialization_schema = SerializationSchema(
diff --git a/openpathsampling/experimental/simstore/test_tools.py b/openpathsampling/experimental/simstore/test_tools.py
index 7c0df9a24..c467532cd 100644
--- a/openpathsampling/experimental/simstore/test_tools.py
+++ b/openpathsampling/experimental/simstore/test_tools.py
@@ -10,7 +10,7 @@ def test_compare_sets():
     pytest.skip()
 
 class TestGroupBy(object):
-    def setup(self):
+    def setup_method(self):
         pass
 
 class TestListify(object):
@@ -25,7 +25,7 @@ def test_should_not_wrap(self, obj):
 
 
 class TestFlatten(object):
-    def setup(self):
+    def setup_method(self):
         self.result = ['a', 'b', 'c', 'd', 'e', 'f']
         self.list = ['a', 'b', [['c'], ['d'], 'e'], [['f']]]
         self.dict = {11: 'a', 12: 'b',
diff --git a/openpathsampling/experimental/simstore/test_type_ident.py b/openpathsampling/experimental/simstore/test_type_ident.py
index 02b9a8815..02dc2b889 100644
--- a/openpathsampling/experimental/simstore/test_type_ident.py
+++ b/openpathsampling/experimental/simstore/test_type_ident.py
@@ -9,7 +9,7 @@ class TestStandardTyping(object):
     # this tests everything in STANDARD_TYPING; it's a little more
     # integration test than unit, but the individual tests ensure that each
     # unit is tested
-    def setup(self):
+    def setup_method(self):
         self.objects = {
             'int': ('int', 5),
             'float': ('float', 2.3),
diff --git a/openpathsampling/experimental/simstore/test_utils.py b/openpathsampling/experimental/simstore/test_utils.py
index ce856c577..4868b7545 100644
--- a/openpathsampling/experimental/simstore/test_utils.py
+++ b/openpathsampling/experimental/simstore/test_utils.py
@@ -198,7 +198,7 @@ def create_test_objects():
 
 
 class TestMockBackend(object):
-    def setup(self):
+    def setup_method(self):
         self.backend = MockBackend()
 
     @pytest.mark.parametrize(('obj_name', 'table_idx', 'idx'),
diff --git a/openpathsampling/experimental/storage/test_collective_variables.py b/openpathsampling/experimental/storage/test_collective_variables.py
index 0da675fb5..872d9b585 100644
--- a/openpathsampling/experimental/storage/test_collective_variables.py
+++ b/openpathsampling/experimental/storage/test_collective_variables.py
@@ -26,7 +26,7 @@
 
 
 class TestCollectiveVariable(object):
-    def setup(self):
+    def setup_method(self):
         self.cv = CollectiveVariable(lambda x: x.xyz[0][0])
         self.traj = make_1d_traj([1.0, 2.0, 3.0])
         ensemble = paths.LengthEnsemble(3)
@@ -47,7 +47,7 @@ def test_is_scalar_false(self, inp_type):
 # TODO: It turns out that trajectories don't currently store their reversed
 # partners. This test won't work until that idea is implemented.
 # class TestReversibleStorableFunction(object):
-    # def setup(self):
+    # def setup_method(self):
         # self.func = ReversibleStorableFunction(
             # func=mock.MagicMock(return_value=3.0),
             # result_type='float'
@@ -72,7 +72,7 @@ def test_is_scalar_false(self, inp_type):
 
 
 class TestCoordinateFunctionCV(object):
-    def setup(self):
+    def setup_method(self):
         # On using side_effect: https://stackoverflow.com/a/16162114
         mock_func = mock.MagicMock(side_effect=lambda s: s.xyz[0][0])
         self.func = CoordinateFunctionCV(mock_func)
@@ -117,7 +117,7 @@ def test_get_storage(self, inp_type):
 
 
 class TestMDTrajFunctionCV(object):
-    def setup(self):
+    def setup_method(self):
         if not HAS_MDTRAJ:
             pytest.skip("Unable to import MDTraj")
         pytest.importorskip('simtk.unit')
diff --git a/openpathsampling/experimental/storage/test_mdtraj_json.py b/openpathsampling/experimental/storage/test_mdtraj_json.py
index f3c57c4ad..fdda62f4b 100644
--- a/openpathsampling/experimental/storage/test_mdtraj_json.py
+++ b/openpathsampling/experimental/storage/test_mdtraj_json.py
@@ -10,7 +10,7 @@
 from openpathsampling.tests.test_helpers import data_filename
 
 class MDTrajCodingTest(CustomJSONCodingTest):
-    def setup(self):
+    def setup_method(self):
         if not HAS_MDTRAJ:
             pytest.skip()
 
@@ -36,8 +36,8 @@ def test_round_trip(self):
 
 
 class TestTopologyCoding(MDTrajCodingTest):
-    def setup(self):
-        super(TestTopologyCoding, self).setup()
+    def setup_method(self):
+        super(TestTopologyCoding, self).setup_method()
         self.codec = top_codec
         top = md.load(self.filename).topology
         dataframe, bonds = top.to_dataframe()
@@ -51,8 +51,8 @@ def setup(self):
 
 
 class TestTrajectoryCoding(MDTrajCodingTest):
-    def setup(self):
-        super(TestTrajectoryCoding, self).setup()
+    def setup_method(self):
+        super(TestTrajectoryCoding, self).setup_method()
         self.codec = traj_codec
         traj = md.load(self.filename)
         self.objs = [traj]
diff --git a/openpathsampling/experimental/storage/test_monkey_patches.py b/openpathsampling/experimental/storage/test_monkey_patches.py
new file mode 100644
index 000000000..60942aab1
--- /dev/null
+++ b/openpathsampling/experimental/storage/test_monkey_patches.py
@@ -0,0 +1,43 @@
+import openpathsampling as paths
+import pytest
+
+from openpathsampling.experimental.storage.monkey_patches import *
+from openpathsampling.experimental.storage.collective_variables import \
+    CollectiveVariable
+
+@pytest.fixture
+def patching_tps_network():
+    cv = CollectiveVariable(lambda x: x.xyz[0][0]).named('cv')
+    state_A = paths.CVDefinedVolume(cv, 0, 1)
+    state_B = paths.CVDefinedVolume(cv, 9, 10)
+    tps_network = paths.TPSNetwork(state_A, state_B)
+    yield tps_network
+
+
+def test_double_patch(patching_tps_network):
+    orig_dict = patching_tps_network.to_dict()
+    global paths
+    paths = monkey_patch_all(paths)
+    try:
+        patched_dict = patching_tps_network.to_dict()
+        paths = monkey_patch_all(paths)
+        double_patched_dict = patching_tps_network.to_dict()
+    finally:
+        unpatch(paths)
+
+    assert orig_dict != patched_dict
+    assert patched_dict == double_patched_dict
+
+
+def test_unpatch(patching_tps_network):
+    orig_dict = patching_tps_network.to_dict()
+    global paths
+    paths = monkey_patch_all(paths)
+    try:
+        patched_dict = patching_tps_network.to_dict()
+    finally:
+        paths = unpatch(paths)
+
+    unpatched_dict = patching_tps_network.to_dict()
+    assert unpatched_dict == orig_dict
+    assert unpatched_dict != patched_dict
diff --git a/openpathsampling/experimental/storage/test_regressions.py b/openpathsampling/experimental/storage/test_regressions.py
new file mode 100644
index 000000000..501b71af6
--- /dev/null
+++ b/openpathsampling/experimental/storage/test_regressions.py
@@ -0,0 +1,18 @@
+# miscellaneous regression testing
+import pytest
+from openpathsampling.tests.test_helpers import data_filename
+from openpathsampling.netcdfplus import StorableNamedObject
+
+from openpathsampling.experimental.storage.ops_storage import ops_class_info
+
+class TrajWrapper(StorableNamedObject):
+    def __init__(self, traj):
+        super(TrajWrapper, self).__init__()
+        self.traj = traj
+
+
+def test_use_mdtraj_codec():
+    # smoke test to ensure we *can* use the MDTraj codecs
+    md = pytest.importorskip("mdtraj")
+    traj = md.load(data_filename("ala_small_traj.pdb"))
+    serialized = ops_class_info.default_info.serializer(TrajWrapper(traj))
diff --git a/openpathsampling/experimental/storage/test_simtk_unit.py b/openpathsampling/experimental/storage/test_simtk_unit.py
index 11f941b4f..431e2b3a7 100644
--- a/openpathsampling/experimental/storage/test_simtk_unit.py
+++ b/openpathsampling/experimental/storage/test_simtk_unit.py
@@ -11,7 +11,7 @@
 from openpathsampling.integration_tools import HAS_SIMTK_UNIT, unit
 
 class TestSimtkUnitCodec(object):
-    def setup(self):
+    def setup_method(self):
         if not HAS_SIMTK_UNIT:
             pytest.skip("openmm.unit not installed")
         my_unit = unit.nanometer / unit.picosecond**2
@@ -37,7 +37,7 @@ def test_serialization_cycle(self, obj_type):
 
 
 class TestSimtkQuantityHandler(object):
-    def setup(self):
+    def setup_method(self):
         pytest.importorskip('simtk.unit')
         self.handlers = {
             'float': SimtkQuantityHandler(
diff --git a/openpathsampling/experimental/storage/test_snapshots_table.py b/openpathsampling/experimental/storage/test_snapshots_table.py
index 9148fd750..45f4d6f2b 100644
--- a/openpathsampling/experimental/storage/test_snapshots_table.py
+++ b/openpathsampling/experimental/storage/test_snapshots_table.py
@@ -58,7 +58,7 @@ def save(self, snapshot):
 
 
 class TestSnapshotsTable(object):
-    def setup(self):
+    def setup_method(self):
         self.engine_1 = make_engine()
         self.engine_2 = make_engine()
         snap_1_0 = toys.Snapshot(coordinates=np.array([0.0, 0.0]),
diff --git a/openpathsampling/tests/pathsimulators/test_hooks.py b/openpathsampling/tests/pathsimulators/test_hooks.py
index 4ba5c01f3..0cd3acc75 100644
--- a/openpathsampling/tests/pathsimulators/test_hooks.py
+++ b/openpathsampling/tests/pathsimulators/test_hooks.py
@@ -1,7 +1,4 @@
 from __future__ import absolute_import
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises)
-from nose.plugins.skip import Skip, SkipTest
 
 import io
 import sys
@@ -105,15 +102,15 @@ def test_attach_hooks_class(self):
         sim = TrivialPathSimulator(storage=None)
         sim.attach_hook(hook)
         for k in sim.hook_names:
-            assert_equal(len(sim.hooks[k]), 1)
+            assert len(sim.hooks[k]) == 1
 
     def test_attach_hooks_method(self):
         hook_instance = StupiderHook()
         sim = NoStepHookPathSimulator(storage=None)
         sim.attach_hook(hook_instance.hook_method, hook_for='extra_hook')
-        assert_not_equal(hook_instance.foo, "foo")
+        assert hook_instance.foo != "foo"
         sim.run(1)
-        assert_equal(hook_instance.foo, "foo")
+        assert hook_instance.foo == "foo"
 
     def test_trivial_simulation_hooks(self):
         sim = TrivialPathSimulator(storage=None)
@@ -125,31 +122,31 @@ def test_trivial_simulation_hooks(self):
                                                              "foo")
         sim.attach_hook(extra, hook_for='after_simulation')
         # before running, check that state is as expected
-        assert_equal(stupid.began_simulation, False)
-        assert_equal(stupid.finished_simulation, False)
-        assert_equal(stupid.began_steps, 0)
-        assert_equal(stupid.finished_steps, 0)
-        assert_not_equal(stupider.foo, "foo")
+        assert stupid.began_simulation is False
+        assert stupid.finished_simulation is False
+        assert stupid.began_steps == 0
+        assert stupid.finished_steps == 0
+        assert stupider.foo != "foo"
         # run
         sim.run(3)
         # check results
-        assert_equal(stupid.began_simulation, True)
-        assert_equal(stupid.finished_simulation, True)
-        assert_equal(stupid.began_steps, 3)
-        assert_equal(stupid.finished_steps, 3)
-        assert_equal(stupider.foo, "foo")
+        assert stupid.began_simulation is True
+        assert stupid.finished_simulation is True
+        assert stupid.began_steps == 3
+        assert stupid.finished_steps == 3
+        assert stupider.foo == "foo"
 
-    @raises(TypeError)
     def test_bad_extra_hook_method(self):
         sim = NoStepHookPathSimulator(storage=None)
         stupid = StupidHook()
-        sim.attach_hook(stupid)
+        with pytest.raises(TypeError):
+            sim.attach_hook(stupid)
 
-    @raises(TypeError)
     def test_bad_hook_name(self):
         sim = NoStepHookPathSimulator(storage=None)
         stupider = StupiderHook()
-        sim.attach_hook(stupider.hook_method, "blah")
+        with pytest.raises(TypeError):
+            sim.attach_hook(stupider.hook_method, "blah")
 
     def test_no_step_hook_simulation_hooks(self):
         sim = NoStepHookPathSimulator(storage=None)
@@ -158,19 +155,19 @@ def test_no_step_hook_simulation_hooks(self):
         sim.attach_hook(stupid.before_simulation,
                         hook_for='before_simulation')
         sim.attach_hook(stupider.hook_method, hook_for='extra_hook')
-        assert_equal(len(sim.hooks), len(sim.hook_names))
+        assert len(sim.hooks) == len(sim.hook_names)
         for hook in sim.hooks:
-            assert_equal(len(sim.hooks[hook]), 1)
+            assert len(sim.hooks[hook]) == 1
 
-        assert_equal(stupid.began_simulation, False)
-        assert_not_equal(stupider.foo, "foo")
+        assert stupid.began_simulation is False
+        assert stupider.foo != "foo"
         sim.run(1)
-        assert_equal(stupid.began_simulation, True)
-        assert_equal(stupider.foo, "foo")
+        assert stupid.began_simulation is True
+        assert stupider.foo == "foo"
 
 
 class TestSelfOrSimProperty(object):
-    def setup(self):
+    def setup_method(self):
         self.attr_name = "test_attr"
         self.simulation = MagicMock(test_attr="sim_attr")
         self.sans_attr_sans_sim = MagicMock(_test_attr=None, _simulation=None)
@@ -226,7 +223,7 @@ def test_self_or_sim_property_helper_func(self, hook_name):
 
 
 class TestStorageHook(object):
-    def setup(self):
+    def setup_method(self):
         self.storage = MagicMock(spec=["stash", "sync_all"])
         self.old_storage = MagicMock(spec=["save", "sync_all"])
         self.empty_hook = StorageHook()
@@ -267,7 +264,7 @@ def test_after_simulation(self):
 
 
 class TestShootFromSnapshotsOutputHook(object):
-    def setup(self):
+    def setup_method(self):
         if sys.version_info < (3,):
             self.stream = io.BytesIO()
         else:
@@ -295,7 +292,7 @@ def test_before_step(self):
 
 
 class TestSampleSetSanityCheckHook(object):
-    def setup(self):
+    def setup_method(self):
         self.simulation = MagicMock(save_frequency=10)
         self.empty_hook = SampleSetSanityCheckHook()
         self.hook = SampleSetSanityCheckHook(frequency=10)
@@ -319,7 +316,7 @@ def test_after_step(self, step_num):
 
 
 class TestLiveVisualizerHook(object):
-    def setup(self):
+    def setup_method(self):
         self.live_visualizer = MagicMock()
         self.simulation = MagicMock(live_visualizer=self.live_visualizer,
                                     status_update_frequency=2)
@@ -363,7 +360,7 @@ def test_integration(self):
 
 
 class TestPathSamplingOutputHook(object):
-    def setup(self):
+    def setup_method(self):
         if sys.version_info < (3,):
             self.stream = io.BytesIO()
         else:
@@ -434,7 +431,7 @@ def test_after_simulation(self):
 
 
 class TestGraciousKillHook(object):
-    def setup(self):
+    def setup_method(self):
         self.final_call = MagicMock()
         self.nocall_final_call = MagicMock()
         self.simulation = MagicMock(storage=MagicMock(close=MagicMock(),
diff --git a/openpathsampling/tests/test_attribute.py b/openpathsampling/tests/test_attribute.py
index e03d5e96b..dd163a066 100644
--- a/openpathsampling/tests/test_attribute.py
+++ b/openpathsampling/tests/test_attribute.py
@@ -5,8 +5,6 @@
 from .test_helpers import data_filename, assert_close_unit, make_1d_traj, md
 import pytest
 
-from nose.plugins.skip import SkipTest
-
 import openpathsampling.engines.openmm as peng
 from openpathsampling.netcdfplus import FunctionPseudoAttribute
 
@@ -15,9 +13,9 @@
 
 
 class TestFunctionPseudoAttribute(object):
-    def setup(self):
+    def setup_method(self):
         if not md:
-            raise SkipTest("mdtraj not installed")
+            pytest.skip("mdtraj not installed")
         self.mdtraj = md.load(data_filename("ala_small_traj.pdb"))
         pytest.importorskip("simtk.unit")
         self.traj_topology = peng.trajectory_from_mdtraj(self.mdtraj)
@@ -30,7 +28,7 @@ def setup(self):
         if os.path.isfile("myfile.nc"):
             os.remove("myfile.nc")
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile("myfile.nc"):
             os.remove("myfile.nc")
 
diff --git a/openpathsampling/tests/test_bias_function.py b/openpathsampling/tests/test_bias_function.py
index 00db02909..faea98bb0 100644
--- a/openpathsampling/tests/test_bias_function.py
+++ b/openpathsampling/tests/test_bias_function.py
@@ -4,10 +4,10 @@
 from builtins import range
 from builtins import object
 from past.utils import old_div
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises, assert_in)
 
-from nose.plugins.skip import Skip, SkipTest
+import pytest
+import numpy.testing as npt
+
 from .test_helpers import true_func, assert_equal_array_array, make_1d_traj
 
 import numpy as np
@@ -24,13 +24,13 @@
 
 
 class TestBiasEnsembleTable(object):
-    def setup(self):
+    def setup_method(self):
         # create the network
         paths.InterfaceSet._reset()
         xval = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(xval, -1.0, -0.5).named("A")
         self.stateB = paths.CVDefinedVolume(xval, 0.5, float("inf")).named("B")
-        ifacesA = paths.VolumeInterfaceSet(xval, float(-1.0), 
+        ifacesA = paths.VolumeInterfaceSet(xval, float(-1.0),
                                            [-0.5, -0.4, -0.3, -0.2])
         self.network = paths.MISTISNetwork([
             (self.stateA, ifacesA, self.stateB)
@@ -76,7 +76,7 @@ def test_bias_ensemble_new_to_old(self):
 
         # for old_to_new, the probability of moving outerward depends on the
         # ratio of the probabilities of the two ensembles
-        change_vals = { 
+        change_vals = {
             self.change_01 : 0.5,
             self.change_02 : 0.2,
             self.change_12 : old_div(0.2, 0.5),
@@ -86,7 +86,7 @@ def test_bias_ensemble_new_to_old(self):
         }
         for change in list(change_vals.keys()):
             test_val = min(1.0, change_vals[change])
-            assert_almost_equal(
+            npt.assert_almost_equal(
                 self.bias.probability_new_to_old(self.sample_set, change),
                 test_val
             )
@@ -106,7 +106,7 @@ def test_bias_ensemble_old_to_new(self):
         }
         for change in list(change_vals.keys()):
             test_val = min(1.0, change_vals[change])
-            assert_almost_equal(
+            npt.assert_almost_equal(
                 self.bias.probability_old_to_new(self.sample_set, change),
                 test_val
             )
@@ -134,13 +134,13 @@ def test_combo_bias(self):
         ])
         change_210 = move_210.move(self.sample_set)
 
-        # assert_almost_equal(
+        # npt.assert_almost_equal(
             # self.bias.probability_old_to_new(change_210, self.sample_set), 1.0
         # )
-        # assert_almost_equal(
+        # npt.assert_almost_equal(
             # self.bias.probability_new_to_old(change_210, self.sample_set), 0.2
         # )
-        raise SkipTest
+        pytest.skip()
 
     def test_add_biases(self):
         # this is where we combine multiple biases into one
@@ -180,12 +180,12 @@ def test_add_biases(self):
         bias_C = BiasEnsembleTable.ratios_from_dictionary(dict_C)
         bias_AB = bias_A + bias_B
         # check the ensembles_to_ids
-        assert_equal(len(bias_AB.ensembles_to_ids), 7)
+        assert len(bias_AB.ensembles_to_ids) == 7
         for ens in ens_A:
-            assert_in(bias_AB.ensembles_to_ids[ens], [0, 1, 2])
+            assert bias_AB.ensembles_to_ids[ens] in [0, 1, 2]
         for ens in ens_B:
-            assert_in(bias_AB.ensembles_to_ids[ens], [3, 4, 5])
-        assert_equal(bias_AB.ensembles_to_ids[ms_outer], 6)
+            assert bias_AB.ensembles_to_ids[ens] in [3, 4, 5]
+        assert bias_AB.ensembles_to_ids[ms_outer] == 6
 
         # check values
         df_A = bias_A.dataframe
@@ -203,14 +203,14 @@ def test_add_biases(self):
                 col_AB = bias_AB.ensembles_to_ids[ens2]
                 val_A = df_A.loc[idx_A, col_A]
                 val_AB = df_AB.loc[idx_AB, col_AB]
-                assert_equal(val_A, val_AB)
+                assert val_A == val_AB
             for ens2 in ens_B:
                 col_AB = bias_AB.ensembles_to_ids[ens2]
-                assert_equal(np.isnan(df_AB.loc[idx_AB, col_AB]), True)
-            assert_equal(df_A.loc[idx_A, col_A_msouter],
-                         df_AB.loc[idx_AB, col_AB_msouter])
-            assert_equal(df_A.loc[col_A_msouter, idx_A],
-                         df_AB.loc[col_AB_msouter, idx_AB])
+                assert np.isnan(df_AB.loc[idx_AB, col_AB])
+            assert (df_A.loc[idx_A, col_A_msouter]
+                    == df_AB.loc[idx_AB, col_AB_msouter])
+            assert (df_A.loc[col_A_msouter, idx_A]
+                    == df_AB.loc[col_AB_msouter, idx_AB])
 
         for ens1 in ens_B:
             idx_B = bias_B.ensembles_to_ids[ens1]
@@ -220,26 +220,26 @@ def test_add_biases(self):
                 col_AB = bias_AB.ensembles_to_ids[ens2]
                 val_B = df_B.loc[idx_B, col_B]
                 val_AB = df_AB.loc[idx_AB, col_AB]
-                assert_equal(val_B, val_AB)
+                assert val_B == val_AB
             for ens2 in ens_A:
                 col_AB = bias_AB.ensembles_to_ids[ens2]
-                assert_equal(np.isnan(df_AB.loc[idx_AB, col_AB]), True)
-            assert_equal(df_B.loc[idx_B, col_B_msouter],
-                         df_AB.loc[idx_AB, col_AB_msouter])
-            assert_equal(df_B.loc[col_B_msouter, idx_B],
-                         df_AB.loc[col_AB_msouter, idx_AB])
+                assert np.isnan(df_AB.loc[idx_AB, col_AB])
+            assert (df_B.loc[idx_B, col_B_msouter]
+                    == df_AB.loc[idx_AB, col_AB_msouter])
+            assert (df_B.loc[col_B_msouter, idx_B]
+                    == df_AB.loc[col_AB_msouter, idx_AB])
 
         # just to make sure no errors raise when there are NaNs in table
         bias_ABC = bias_A + bias_B + bias_C
 
 class TestSRTISBiasFromNetwork(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         xval = paths.CoordinateFunctionCV(name="xA",
                                           f=lambda s : s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(xval, -1.0, -0.5).named("A")
         self.stateB = paths.CVDefinedVolume(xval, 0.5, float("inf")).named("B")
-        self.ifacesA = paths.VolumeInterfaceSet(xval, -1.0, 
+        self.ifacesA = paths.VolumeInterfaceSet(xval, -1.0,
                                                 [-0.5, -0.4, -0.3, -0.2])
         self.ifacesB = paths.VolumeInterfaceSet(xval, [0.5, 0.4, 0.3, 0.2],
                                                 1.0)
@@ -265,27 +265,26 @@ def test_bias_from_network(self):
         # check reciprocal of symmetric partners
         for i in range(4):
             for j in range(i, 4):
-                assert_equal(bias.dataframe.loc[i, j],
-                             old_div(1.0, bias.dataframe.loc[j, i]))
+                assert (bias.dataframe.loc[i, j]
+                        == old_div(1.0, bias.dataframe.loc[j, i]))
 
         for i in range(len(transition.ensembles) - 1):
             ens_to = transition.ensembles[i]
             ens_from = transition.ensembles[i + 1]
-            assert_equal(bias.bias_value(ens_from, ens_to), 0.5)
+            assert bias.bias_value(ens_from, ens_to) == 0.5
 
         for i in range(len(transition.ensembles) - 2):
             ens_to = transition.ensembles[i]
             ens_from = transition.ensembles[i + 2]
-            assert_equal(bias.bias_value(ens_from, ens_to), 0.25)
+            assert bias.bias_value(ens_from, ens_to) == 0.25
 
-    @raises(RuntimeError)
     def test_fail_without_tcp(self):
         network = paths.MISTISNetwork([
             (self.stateA, self.ifacesA, self.stateB)
         ])
-        bias = paths.SRTISBiasFromNetwork(network)
+        with pytest.raises(RuntimeError):
+            bias = paths.SRTISBiasFromNetwork(network)
 
-    @raises(RuntimeError)
     def test_fail_without_lambdas(self):
         fake_ifaceA = paths.InterfaceSet(cv=self.ifacesA.cv,
                                          volumes=self.ifacesA.volumes,
@@ -294,7 +293,8 @@ def test_fail_without_lambdas(self):
             (self.stateA, fake_ifaceA, self.stateB)
         ])
         network.sampling_transitions[0].tcp = self.tcp_A
-        bias = paths.SRTISBiasFromNetwork(network)
+        with pytest.raises(RuntimeError):
+            bias = paths.SRTISBiasFromNetwork(network)
 
     def test_bias_from_ms_network(self):
         ms_outer = paths.MSOuterTISInterface.from_lambdas(
@@ -333,27 +333,25 @@ def test_bias_from_ms_network(self):
         for i in range(len(transition_AB.ensembles) - 1):
             ens_to = transition_AB.ensembles[i]
             ens_from = transition_AB.ensembles[i + 1]
-            assert_almost_equal(bias.bias_value(ens_from, ens_to), 0.5)
+            npt.assert_almost_equal(bias.bias_value(ens_from, ens_to), 0.5)
 
         for i in range(len(transition_BA.ensembles) - 1):
             ens_to = transition_BA.ensembles[i]
             ens_from = transition_BA.ensembles[i + 1]
-            assert_almost_equal(bias.bias_value(ens_from, ens_to), 0.2)
+            npt.assert_almost_equal(bias.bias_value(ens_from, ens_to), 0.2)
 
         for ensA in transition_AB.ensembles:
             for ensB in transition_BA.ensembles:
-                assert_equal(np.isnan(bias.bias_value(ensA, ensB)), True)
-                assert_equal(np.isnan(bias.bias_value(ensB, ensA)), True)
+                assert np.isnan(bias.bias_value(ensA, ensB))
+                assert np.isnan(bias.bias_value(ensB, ensA))
 
-        assert_almost_equal(bias.bias_value(transition_BA.ensembles[-1],
-                                            network.ms_outers[0]),
+        npt.assert_almost_equal(bias.bias_value(transition_BA.ensembles[-1],
+                                                network.ms_outers[0]),
                             old_div(5.0, 2))
-        assert_almost_equal(bias.bias_value(transition_AB.ensembles[-1],
-                                            network.ms_outers[0]),
+        npt.assert_almost_equal(bias.bias_value(transition_AB.ensembles[-1],
+                                                network.ms_outers[0]),
                             old_div(2.0, 2))
 
 
 
 
-
-        raise SkipTest
diff --git a/openpathsampling/tests/test_channel_analysis.py b/openpathsampling/tests/test_channel_analysis.py
index df9b8915c..2cfb16686 100644
--- a/openpathsampling/tests/test_channel_analysis.py
+++ b/openpathsampling/tests/test_channel_analysis.py
@@ -1,8 +1,7 @@
 from __future__ import absolute_import
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, raises,
-                        assert_almost_equal)
-from nose.plugins.skip import SkipTest
+import pytest
+
 from numpy.testing import assert_array_almost_equal
 from .test_helpers import make_1d_traj, data_filename, assert_items_equal
 
@@ -18,7 +17,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestChannelAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         cv = paths.FunctionCV("Id", lambda snap: snap.xyz[0][0])
         self.state_A = paths.CVDefinedVolume(cv, -0.1, 0.1)
         self.state_B = ~paths.CVDefinedVolume(cv, -1.0, 1.0)
@@ -85,7 +84,7 @@ def test_storage(self):
 
         new_store = paths.Storage(data_filename('test.nc'), 'r')
         reloaded = storage.tag['analyzer']
-        assert_equal(reloaded._results, self.toy_results)
+        assert reloaded._results == self.toy_results
 
         if os.path.isfile(data_filename('test.nc')):
             os.remove(data_filename('test.nc'))
@@ -96,8 +95,8 @@ def test_analyze_incr_decr(self):
             paths.MCStep(mccycle=1, active=self.decr_1)
         ]
         results = paths.ChannelAnalysis(steps, self.channels)
-        assert_equal(results._results,
-                     {'incr': [(0,1)], 'decr': [(1,2)], None: []})
+        expected = {'incr': [(0,1)], 'decr': [(1,2)], None: []}
+        assert results._results == expected
 
     def test_analyze_incr_incr(self):
         steps = [
@@ -105,8 +104,7 @@ def test_analyze_incr_incr(self):
             paths.MCStep(mccycle=1, active=self.incr_2)
         ]
         results = paths.ChannelAnalysis(steps, self.channels)
-        assert_equal(results._results,
-                     {'incr': [(0,2)], 'decr': [], None: []})
+        assert results._results == {'incr': [(0,2)], 'decr': [], None: []}
 
     def test_analyze_incr_both_decr(self):
         steps = [
@@ -116,8 +114,8 @@ def test_analyze_incr_both_decr(self):
             paths.MCStep(mccycle=3, active=self.decr_1)
         ]
         results = paths.ChannelAnalysis(steps, self.channels)
-        assert_equal(results._results,
-                     {'incr': [(0,3)], 'decr': [(1,4)], None: []})
+        expected = {'incr': [(0,3)], 'decr': [(1,4)], None: []}
+        assert results._results == expected
 
     def test_analyze_incr_both_incr(self):
         steps = [
@@ -126,8 +124,8 @@ def test_analyze_incr_both_incr(self):
             paths.MCStep(mccycle=2, active=self.incr_2)
         ]
         results = paths.ChannelAnalysis(steps, self.channels)
-        assert_equal(results._results,
-                     {'incr': [(0,3)], 'decr': [(1,2)], None: []})
+        expected = {'incr': [(0,3)], 'decr': [(1,2)], None: []}
+        assert results._results == expected
 
     def test_analyze_incr_same_decr(self):
         steps = [
@@ -136,8 +134,8 @@ def test_analyze_incr_same_decr(self):
             paths.MCStep(mccycle=2, active=self.incr_2)
         ]
         results = paths.ChannelAnalysis(steps, self.channels)
-        assert_equal(results._results,
-                     {'incr': [(0,3)], 'decr': [], None: []})
+        expected = {'incr': [(0,3)], 'decr': [], None: []}
+        assert results._results == expected
 
     def test_analyze_incr_none_incr(self):
         steps = [
@@ -146,8 +144,8 @@ def test_analyze_incr_none_incr(self):
             paths.MCStep(mccycle=2, active=self.incr_2)
         ]
         results = paths.ChannelAnalysis(steps, self.channels)
-        assert_equal(results._results,
-                     {'incr': [(0,1), (2,3)], 'decr': [], None: [(1,2)]})
+        expected = {'incr': [(0,1), (2,3)], 'decr': [], None: [(1,2)]}
+        assert results._results == expected
 
     def test_analyze_incr_none_decr(self):
         steps = [
@@ -156,74 +154,79 @@ def test_analyze_incr_none_decr(self):
             paths.MCStep(mccycle=2, active=self.decr_1)
         ]
         results = paths.ChannelAnalysis(steps, self.channels)
-        assert_equal(results._results,
-                     {'incr': [(0,1)], 'decr': [(2,3)], None: [(1,2)]})
+        expected = {'incr': [(0,1)], 'decr': [(2,3)], None: [(1,2)]}
+        assert results._results == expected
 
     def test_expand_results(self):
         expanded = paths.ChannelAnalysis._expand_results(self.toy_results)
-        assert_equal(expanded, self.toy_expanded_results)
+        assert expanded == self.toy_expanded_results
 
     def test_labels_by_step_newest(self):
         test_set = self.toy_expanded_results
         relabeled = paths.ChannelAnalysis._labels_by_step_newest(test_set)
-        assert_equal(relabeled, [(0, 3, self.set_a), (3, 7, self.set_b),
-                                 (7, 8, self.set_c), (8, 10, self.set_a)])
+        expected = [(0, 3, self.set_a), (3, 7, self.set_b),
+                    (7, 8, self.set_c), (8, 10, self.set_a)]
+        assert relabeled == expected
 
         test_set = self.expanded_results_simultaneous_ending
         relabeled = paths.ChannelAnalysis._labels_by_step_newest(test_set)
-        assert_equal(relabeled, [(0, 3, self.set_a), (3, 7, self.set_b),
-                                 (7, 8, self.set_c), (8, 10, self.set_a)])
+        expected =  [(0, 3, self.set_a), (3, 7, self.set_b),
+                     (7, 8, self.set_c), (8, 10, self.set_a)]
+        assert relabeled == expected
 
     def test_labels_by_step_oldest(self):
         test_set = self.toy_expanded_results
         relabeled = paths.ChannelAnalysis._labels_by_step_oldest(test_set)
-        assert_equal(relabeled, [(0, 5, self.set_a), (5, 9, self.set_b),
-                                 (9, 10, self.set_a)])
+        expected = [(0, 5, self.set_a), (5, 9, self.set_b),
+                    (9, 10, self.set_a)]
+        assert relabeled == expected
 
         test_set = self.expanded_results_simultaneous_ending
         relabeled = paths.ChannelAnalysis._labels_by_step_oldest(test_set)
-        assert_equal(relabeled, [(0, 5, self.set_a), (5, 9, self.set_b),
-                                 (9, 10, self.set_c)])
+        expected = [(0, 5, self.set_a), (5, 9, self.set_b),
+                    (9, 10, self.set_c)]
+        assert relabeled == expected
 
         test_set = self.expanded_oldest_skips_internal
         relabeled = paths.ChannelAnalysis._labels_by_step_oldest(test_set)
-        assert_equal(relabeled, [(0, 5, self.set_a), (5, 9, self.set_b),
-                                 (9, 10, self.set_a), (10, 11, self.set_b)])
+        expected = [(0, 5, self.set_a), (5, 9, self.set_b),
+                    (9, 10, self.set_a), (10, 11, self.set_b)]
+        assert relabeled == expected
 
 
     def test_labels_by_step_multiple(self):
         test_set = self.toy_expanded_results
         relabeled = paths.ChannelAnalysis._labels_by_step_multiple(test_set)
-        assert_equal(relabeled,
-                     [(0, 3, self.set_a),
-                      (3, 5, self.set_a | self.set_b),
-                      (5, 7, self.set_b),
-                      (7, 8, self.set_b | self.set_c),
-                      (8, 9, self.set_b | self.set_c | self.set_a),
-                      (9, 10, self.set_a)])
+        expected = [(0, 3, self.set_a),
+                    (3, 5, self.set_a | self.set_b),
+                    (5, 7, self.set_b),
+                    (7, 8, self.set_b | self.set_c),
+                    (8, 9, self.set_b | self.set_c | self.set_a),
+                    (9, 10, self.set_a)]
+        assert relabeled == expected
 
     def test_empty(self):
         obj = paths.ChannelAnalysis(steps=None, channels=self.channels)
         empty_results = {c: [] for c in list(self.channels.keys()) + [None]}
-        assert_equal(obj._results, empty_results)
+        assert obj._results == empty_results
 
     def test_treat_multiples(self):
         obj = paths.ChannelAnalysis(steps=None, channels=self.channels)
-        assert_equal(obj.treat_multiples, 'all')
+        assert obj.treat_multiples == 'all'
         obj.treat_multiples = 'oldest'
-        assert_equal(obj.treat_multiples, 'oldest')
+        assert obj.treat_multiples == 'oldest'
         obj.treat_multiples = 'newest'
-        assert_equal(obj.treat_multiples, 'newest')
+        assert obj.treat_multiples == 'newest'
         obj.treat_multiples = 'multiple'
-        assert_equal(obj.treat_multiples, 'multiple')
+        assert obj.treat_multiples == 'multiple'
         obj.treat_multiples = 'all'
-        assert_equal(obj.treat_multiples, 'all')
+        assert obj.treat_multiples == 'all'
 
-    @raises(ValueError)
     def test_bad_treat_multiples(self):
         obj = paths.ChannelAnalysis(steps=None, channels=self.channels)
-        assert_equal(obj.treat_multiples, 'all')
-        obj.treat_multiples = 'bad_string'
+        assert obj.treat_multiples == 'all'
+        with pytest.raises(ValueError):
+            obj.treat_multiples = 'bad_string'
 
     def test_labels_as_sets_sort_function(self):
         sort_key = paths.ChannelAnalysis._labels_as_sets_sort_function
@@ -232,7 +235,7 @@ def test_labels_as_sets_sort_function(self):
         sorted_set_list = sorted(inp_list, key=sort_key)
         sorted_labels = [paths.ChannelAnalysis.label_to_string(e)
                          for e in sorted_set_list]
-        assert_equal(sorted_labels, ['a', 'b', 'c'])
+        assert sorted_labels == ['a', 'b', 'c']
 
         inp_list = [self.set_a, self.set_a | self.set_b, self.set_b,
                     self.set_c | self.set_b,
@@ -240,7 +243,7 @@ def test_labels_as_sets_sort_function(self):
         sorted_set_list = sorted(inp_list, key=sort_key)
         sorted_labels = [paths.ChannelAnalysis.label_to_string(e)
                          for e in sorted_set_list]
-        assert_equal(sorted_labels, ['a', 'b', 'a,b', 'b,c', 'a,b,c'])
+        assert sorted_labels == ['a', 'b', 'a,b', 'b,c', 'a,b,c']
 
     def test_labels_sort_function_with_none(self):
         sort_key = paths.ChannelAnalysis._labels_as_sets_sort_function
@@ -248,7 +251,7 @@ def test_labels_sort_function_with_none(self):
         sorted_set_list = sorted(inp_list, key=sort_key)
         sorted_labels = [paths.ChannelAnalysis.label_to_string(e)
                          for e in sorted_set_list]
-        assert_equal(sorted_labels, ['None', 'a', 'b'])
+        assert sorted_labels == ['None', 'a', 'b']
 
     def test_switching(self):
         analysis = paths.ChannelAnalysis(steps=None, channels=self.channels)
@@ -299,28 +302,28 @@ def test_residence_times(self):
 
         analysis.treat_multiples = 'newest'
         residence_times = analysis.residence_times
-        assert_equal(residence_times, {'a': [3, 2], 'b': [4], 'c': [1]})
+        assert residence_times == {'a': [3, 2], 'b': [4], 'c': [1]}
 
         analysis.treat_multiples = 'oldest'
         residence_times = analysis.residence_times
-        assert_equal(residence_times, {'a': [5, 1], 'b': [4]})
+        assert residence_times == {'a': [5, 1], 'b': [4]}
 
         analysis.treat_multiples = 'all'
         residence_times = analysis.residence_times
-        assert_equal(residence_times, {'a': [5, 2], 'b': [6], 'c': [2]})
+        assert residence_times == {'a': [5, 2], 'b': [6], 'c': [2]}
 
         analysis.treat_multiples = 'multiple'
         residence_times = analysis.residence_times
-        assert_equal(residence_times, {'a': [3, 1], 'b': [2], 'a,b': [2],
-                                       'b,c': [1], 'a,b,c': [1]})
+        assert residence_times == {
+            'a': [3, 1], 'b': [2], 'a,b': [2], 'b,c': [1], 'a,b,c': [1]
+        }
 
     def test_residence_times_with_none(self):
         analysis = paths.ChannelAnalysis(steps=None, channels=self.channels)
         analysis._results = self.results_with_none
 
         residence_times = analysis.residence_times
-        assert_equal(residence_times,
-                     {'a': [2, 3], 'b': [2, 1], 'None': [3]})
+        assert residence_times == {'a': [2, 3], 'b': [2, 1], 'None': [3]}
 
     def test_total_time(self):
         analysis = paths.ChannelAnalysis(steps=None, channels=self.channels)
@@ -328,28 +331,27 @@ def test_total_time(self):
 
         analysis.treat_multiples = 'newest'
         total_time = analysis.total_time
-        assert_equal(total_time, {'a': 5, 'b': 4, 'c': 1})
+        assert total_time == {'a': 5, 'b': 4, 'c': 1}
 
         analysis.treat_multiples = 'oldest'
         total_time = analysis.total_time
-        assert_equal(total_time, {'a': 6, 'b': 4})
-        assert_equal(total_time['c'], 0)
+        assert total_time == {'a': 6, 'b': 4}
+        assert total_time['c'] == 0
 
         analysis.treat_multiples = 'all'
         total_time = analysis.total_time
-        assert_equal(total_time, {'a': 7, 'b': 6, 'c': 2})
+        assert total_time == {'a': 7, 'b': 6, 'c': 2}
 
         analysis.treat_multiples = 'multiple'
         total_time = analysis.total_time
-        assert_equal(total_time, {'a': 4, 'b': 2, 'a,b': 2, 'b,c': 1,
-                                  'a,b,c': 1})
+        assert total_time == {'a': 4, 'b': 2, 'a,b': 2, 'b,c': 1, 'a,b,c': 1}
 
     def test_total_time_with_none(self):
         analysis = paths.ChannelAnalysis(steps=None, channels=self.channels)
         analysis._results = self.results_with_none
 
         total_time = analysis.total_time
-        assert_equal(total_time, {'a': 5, 'b': 3, 'None': 3})
+        assert total_time == {'a': 5, 'b': 3, 'None': 3}
 
 
     def test_status(self):
@@ -357,29 +359,29 @@ def test_status(self):
         analysis._results = self.toy_results
 
         analysis.treat_multiples = 'newest'
-        assert_equal(analysis.status(4), 'b')
-        assert_equal(analysis.status(8), 'a')
+        assert analysis.status(4) == 'b'
+        assert analysis.status(8) == 'a'
 
         analysis.treat_multiples = 'oldest'
-        assert_equal(analysis.status(4), 'a')
-        assert_equal(analysis.status(8), 'b')
+        assert analysis.status(4) == 'a'
+        assert analysis.status(8) == 'b'
 
         analysis.treat_multiples = 'multiple'
-        assert_equal(analysis.status(4), 'a,b')
-        assert_equal(analysis.status(8), 'a,b,c')
+        assert analysis.status(4) == 'a,b'
+        assert analysis.status(8) == 'a,b,c'
 
         analysis.treat_multiples = 'all'
-        assert_equal(analysis.status(4), 'a,b')
-        assert_equal(analysis.status(8), 'a,b,c')
+        assert analysis.status(4) == 'a,b'
+        assert analysis.status(8) == 'a,b,c'
 
     def test_status_with_none(self):
         analysis = paths.ChannelAnalysis(steps=None, channels=self.channels)
         analysis._results = self.results_with_none
-        assert_equal(analysis.status(4), 'None')
+        assert analysis.status(4) == 'None'
 
-    @raises(RuntimeError)
     def test_bad_status_number(self):
         analysis = paths.ChannelAnalysis(steps=None, channels=self.channels)
         analysis._results = self.toy_results
 
-        analysis.status(10)
+        with pytest.raises(RuntimeError):
+            analysis.status(10)
diff --git a/openpathsampling/tests/test_collectivevariable.py b/openpathsampling/tests/test_collectivevariable.py
index 7f47b9e68..4c199957e 100644
--- a/openpathsampling/tests/test_collectivevariable.py
+++ b/openpathsampling/tests/test_collectivevariable.py
@@ -8,7 +8,6 @@
 from .test_helpers import data_filename, assert_close_unit, md
 
 import pytest
-from nose.plugins.skip import SkipTest
 
 import numpy as np
 
@@ -29,9 +28,9 @@
 
 
 class TestFunctionCV(object):
-    def setup(self):
+    def setup_method(self):
         if not md:
-            raise SkipTest("mdtraj not installed")
+            pytest.skip("mdtraj not installed")
         self.mdtraj = md.load(data_filename("ala_small_traj.pdb"))
         _ = pytest.importorskip('simtk.unit')
         self.traj_topology = peng.trajectory_from_mdtraj(self.mdtraj)
@@ -44,7 +43,7 @@ def setup(self):
         if os.path.isfile("myfile.nc"):
             os.remove("myfile.nc")
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile("myfile.nc"):
             os.remove("myfile.nc")
 
@@ -87,7 +86,7 @@ def test_atom_pair_featurizer(self):
         """ Create an atom pair collectivevariable using MSMSBuilder3 """
 
         if not has_msmbuilder:
-            raise SkipTest("MSMBuilder not installed")
+            pytest.skip("MSMBuilder not installed")
         atom_pairs = [[0, 1], [10, 14]]
         atom_pair_op = op.MSMBFeaturizerCV(
             "atom_pairs",
@@ -105,7 +104,7 @@ def test_atom_pair_featurizer(self):
     def test_return_parameters_from_template(self):
 
         if not has_msmbuilder:
-            raise SkipTest("MSMBuilder not installed")
+            pytest.skip("MSMBuilder not installed")
         atom_pairs = [[0, 1], [10, 14]]
         atom_pair_op = op.MSMBFeaturizerCV(
             "atom_pairs",
diff --git a/openpathsampling/tests/test_deprecations.py b/openpathsampling/tests/test_deprecations.py
index b317474a5..9f21c4bfd 100644
--- a/openpathsampling/tests/test_deprecations.py
+++ b/openpathsampling/tests/test_deprecations.py
@@ -51,7 +51,7 @@ def test_version_tuple_to_string(version_tup, version_str):
     assert version_tuple_to_string(version_tup) == version_str
 
 class TestDeprecation(object):
-    def setup(self):
+    def setup_method(self):
         self.foo, self.bar = make_foo_bar_baz()[0:2]
 
     def test_message(self):
@@ -78,7 +78,7 @@ def test_warn_multiple(self):
 
 
 class TestDeprecationDecorators(object):
-    def setup(self):
+    def setup_method(self):
         self.foo, self.bar, self.baz = make_foo_bar_baz()
 
         @deprecate(self.foo)
diff --git a/openpathsampling/tests/test_dynamicsengine.py b/openpathsampling/tests/test_dynamicsengine.py
index 9bd57cbbb..cf762ab21 100644
--- a/openpathsampling/tests/test_dynamicsengine.py
+++ b/openpathsampling/tests/test_dynamicsengine.py
@@ -3,8 +3,6 @@
 import pytest
 import openpathsampling as paths
 
-from nose.tools import (assert_equal, assert_not_equal, raises, assert_true)
-from nose.plugins.skip import SkipTest
 from .test_helpers import make_1d_traj, raises_with_message_like
 
 try:
@@ -41,7 +39,7 @@ def current_snapshot(self, snap):
 
 
 class TestDynamicsEngine(object):
-    def setup(self):
+    def setup_method(self):
         options = {'n_frames_max' : 100, 'random_option' : True}
 
         snapshot_dimensions = {
@@ -58,7 +56,7 @@ def setup(self):
         self.stupid = StupidEngine(options, descriptor)
 
     def test_getattr_from_options(self):
-        assert_equal(self.stupid.random_option, True)
+        assert self.stupid.random_option is True
 
     def test_getattr_property_fails(self):
         # py2: "'newobject' object has no attribute 'b'"
@@ -67,7 +65,7 @@ def test_getattr_property_fails(self):
         try:
             self.stupid.bad_property
         except AttributeError as e:
-            assert_true(str(e).endswith(error_string_end))
+            assert str(e).endswith(error_string_end)
 
         else:
             raise AssertionError("Did not raise appropriate AttributeError")
diff --git a/openpathsampling/tests/test_ensemble.py b/openpathsampling/tests/test_ensemble.py
index f1bfd945b..3ab96a428 100644
--- a/openpathsampling/tests/test_ensemble.py
+++ b/openpathsampling/tests/test_ensemble.py
@@ -4,9 +4,7 @@
 from builtins import str
 from builtins import range
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, raises, assert_true,
-                        assert_false)
-from nose.plugins.skip import SkipTest
+import pytest
 from .test_helpers import (CallIdentity, prepend_exception_message,
                           make_1d_traj, raises_with_message_like,
                           CalvinistDynamics)
@@ -45,9 +43,9 @@ def wrap_traj(traj, start, length):
 def test_wrap_traj():
     """Testing wrap_traj (oh gods, the meta! a test for a test function!)"""
     intraj = [1, 2, 3]
-    assert_equal(wrap_traj(intraj, 3, 6), [1, 1, 1, 1, 2, 3])
-    assert_equal(wrap_traj(intraj, 3, 8), [1, 1, 1, 1, 2, 3, 3, 3])
-    assert_equal(wrap_traj(intraj, 3, 8)[slice(3, 6)], intraj)
+    assert wrap_traj(intraj, 3, 6) == [1, 1, 1, 1, 2, 3]
+    assert wrap_traj(intraj, 3, 8) == [1, 1, 1, 1, 2, 3, 3, 3]
+    assert wrap_traj(intraj, 3, 8)[slice(3, 6)] == intraj
 
 def build_trajdict(trajtypes, lower, upper):
     upperadddict = {'a' : 'in', 'b' : 'out', 'c' : 'cross', 'o' : 'hit'}
@@ -143,7 +141,7 @@ def in_out_parser(testname):
 class EnsembleTest(object):
     def _single_test(self, ensemble_fcn, traj, res, failmsg):
         try:
-            assert_equal(ensemble_fcn(traj), res)
+            assert ensemble_fcn(traj) == res
         except AssertionError as e:
             prepend_exception_message(e, failmsg)
             raise
@@ -195,7 +193,7 @@ def _run(self, results):
 
 
 class TestPartOutXEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.leaveX = PartOutXEnsemble(vol1)
 
     def test_leaveX(self):
@@ -232,17 +230,17 @@ def test_strict_can_prepend(self):
 
     def test_leaveX_0(self):
         """PartOutXEnsemble treatment of zero-length trajectory"""
-        assert_equal(self.leaveX(paths.Trajectory([])), False)
-        assert_equal(self.leaveX.can_append(paths.Trajectory([])), True)
-        assert_equal(self.leaveX.can_prepend(paths.Trajectory([])), True)
+        assert self.leaveX(paths.Trajectory([])) is False
+        assert self.leaveX.can_append(paths.Trajectory([])) is True
+        assert self.leaveX.can_prepend(paths.Trajectory([])) is True
 
     def test_leaveX_str(self):
         volstr = "{x|Id(x) in [0.1, 0.5]}"
-        assert_equal(self.leaveX.__str__(), 
-                     "exists t such that x[t] in (not "+volstr+")")
+        expected_str = "exists t such that x[t] in (not "+volstr+")"
+        assert self.leaveX.__str__() == expected_str
 
 class TestAllInXEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.inX = AllInXEnsemble(vol1)
 
     def test_inX(self):
@@ -296,17 +294,16 @@ def test_strict_can_prepend(self):
 
     def test_inX_0(self):
         """AllInXEnsemble treatment of zero-length trajectory"""
-        assert_equal(self.inX(paths.Trajectory([])), False)
-        assert_equal(self.inX.can_append(paths.Trajectory([])), True)
-        assert_equal(self.inX.can_prepend(paths.Trajectory([])), True)
+        assert self.inX(paths.Trajectory([])) is False
+        assert self.inX.can_append(paths.Trajectory([])) is True
+        assert self.inX.can_prepend(paths.Trajectory([])) is True
 
     def test_inX_str(self):
         volstr = "{x|Id(x) in [0.1, 0.5]}"
-        assert_equal(self.inX.__str__(),
-                     "x[t] in "+volstr+" for all t")
+        assert self.inX.__str__() == "x[t] in "+volstr+" for all t"
 
 class TestAllOutXEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.outX = AllOutXEnsemble(vol1)
 
     def test_outX(self):
@@ -359,17 +356,16 @@ def test_strict_can_prepend(self):
 
     def test_outX_0(self):
         """AllOutXEnsemble treatment of zero-length trajectory"""
-        assert_equal(self.outX(paths.Trajectory([])), False)
-        assert_equal(self.outX.can_append(paths.Trajectory([])), True)
-        assert_equal(self.outX.can_prepend(paths.Trajectory([])), True)
+        assert self.outX(paths.Trajectory([])) is False
+        assert self.outX.can_append(paths.Trajectory([])) is True
+        assert self.outX.can_prepend(paths.Trajectory([])) is True
 
     def test_outX_str(self):
         volstr = "{x|Id(x) in [0.1, 0.5]}"
-        assert_equal(self.outX.__str__(),
-                     "x[t] in (not "+volstr+") for all t")
+        assert self.outX.__str__() == "x[t] in (not "+volstr+") for all t"
 
 class TestPartInXEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.hitX = PartInXEnsemble(vol1)
 
     def test_hitX(self):
@@ -396,18 +392,17 @@ def test_strict_can_prepend(self):
 
     def test_hitX_0(self):
         """PartInXEnsemble treatment of zero-length trajectory"""
-        assert_equal(self.hitX(paths.Trajectory([])), False)
-        assert_equal(self.hitX.can_append(paths.Trajectory([])), True)
-        assert_equal(self.hitX.can_prepend(paths.Trajectory([])), True)
+        assert self.hitX(paths.Trajectory([])) is False
+        assert self.hitX.can_append(paths.Trajectory([])) is True
+        assert self.hitX.can_prepend(paths.Trajectory([])) is True
 
     def test_hitX_str(self):
         volstr = "{x|Id(x) in [0.1, 0.5]}"
-        assert_equal(self.hitX.__str__(),
-                     "exists t such that x[t] in "+volstr)
+        assert self.hitX.__str__() == "exists t such that x[t] in " + volstr
 
 
 class TestSequentialEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.inX = AllInXEnsemble(vol1)
         self.outX = AllOutXEnsemble(vol1)
         self.hitX = PartInXEnsemble(vol1)
@@ -436,50 +431,52 @@ def setup(self):
             self.inX & self.length1,
         ])
 
-    @raises(ValueError)
     def test_maxminoverlap_size(self):
         """SequentialEnsemble errors if max/min overlap sizes different"""
-        SequentialEnsemble([self.inX, self.outX, self.inX], (0,0), (0,0,0))
+        with pytest.raises(ValueError):
+            SequentialEnsemble([self.inX, self.outX, self.inX],
+                               (0,0), (0,0,0))
 
-    @raises(ValueError)
     def test_maxoverlap_ensemble_size(self):
         """SequentialEnsemble errors if overlap sizes don't match ensemble size"""
-        SequentialEnsemble([self.inX, self.outX, self.inX], (0,0,0), (0,0,0))
+        with pytest.raises(ValueError):
+            SequentialEnsemble([self.inX, self.outX, self.inX],
+                               (0,0,0), (0,0,0))
 
-    @raises(ValueError)
     def test_minmax_order(self):
         """SequentialEnsemble errors if min_overlap > max_overlap"""
-        SequentialEnsemble([self.inX, self.outX, self.inX], (0,1), (0,0))
+        with pytest.raises(ValueError):
+            SequentialEnsemble([self.inX, self.outX, self.inX], (0,1), (0,0))
 
     def test_allowed_initializations(self):
         """SequentialEnsemble initializes correctly with defaults"""
         A = SequentialEnsemble([self.inX, self.outX, self.inX], (0,0), (0,0))
         B = SequentialEnsemble([self.inX, self.outX, self.inX],0,0)
         C = SequentialEnsemble([self.inX, self.outX, self.inX])
-        assert_equal(A.min_overlap,B.min_overlap)
-        assert_equal(A.min_overlap,C.min_overlap)
-        assert_equal(A.max_overlap,B.max_overlap)
-        assert_equal(A.max_overlap,C.max_overlap)
+        assert A.min_overlap == B.min_overlap
+        assert A.min_overlap == C.min_overlap
+        assert A.max_overlap == B.max_overlap
+        assert A.max_overlap == C.max_overlap
 
     def test_overlap_max(self):
         """SequentialEnsemble allows overlaps up to overlap max, no more"""
-        raise SkipTest
+        pytest.skip()
 
     def test_overlap_min(self):
         """SequentialEnsemble requires overlaps of at least overlap min"""
-        raise SkipTest
+        pytest.skip()
 
     def test_overlap_max_inf(self):
         """SequentialEnsemble works if max overlap in infinite"""
-        raise SkipTest
+        pytest.skip()
 
     def test_overlap_min_gap(self):
         """SequentialEnsemble works in mix overlap is negative (gap)"""
-        raise SkipTest
+        pytest.skip()
 
     def test_overlap_max_gap(self):
         """SequentialEnsemble works if max overlap is negative (gap)"""
-        raise SkipTest
+        pytest.skip()
 
     def test_seqens_order_combo(self):
         # regression test for #229
@@ -504,13 +501,13 @@ def test_seqens_order_combo(self):
         seq1 = SequentialEnsemble([combo1])
         seq2 = SequentialEnsemble([combo2])
         logger.debug("Checking combo1")
-        assert_equal(combo1.can_append(traj), True)
+        assert combo1.can_append(traj) is True
         logger.debug("Checking combo2")
-        assert_equal(combo2.can_append(traj), True)
+        assert combo2.can_append(traj) is True
         logger.debug("Checking seq1")
-        assert_equal(seq1.can_append(traj), True)
+        assert seq1.can_append(traj) is True
         logger.debug("Checking seq2")
-        assert_equal(seq2.can_append(traj), True)
+        assert seq2.can_append(traj) is True
 
 
     def test_can_append_tis(self):
@@ -533,10 +530,10 @@ def test_can_append_tis(self):
                         'lower_in_out_in_in' : False,
                         'upper_in_out_in_out_in' : False,
                         'lower_in_out_in_out_in' : False
-                    }   
+                    }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_tis.can_append, 
+            self._single_test(self.pseudo_tis.can_append,
                                 ttraj[test], results[test], failmsg)
 
     def test_strict_can_append_tis(self):
@@ -559,10 +556,10 @@ def test_strict_can_append_tis(self):
             'lower_in_out_in_in' : False,
             'upper_in_out_in_out_in' : False,
             'lower_in_out_in_out_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_tis.strict_can_append, 
+            self._single_test(self.pseudo_tis.strict_can_append,
                                 ttraj[test], results[test], failmsg)
 
     def test_can_append_pseudominus(self):
@@ -604,10 +601,10 @@ def test_can_append_pseudominus(self):
             'lower_cross_in_cross_in' : False,
             'upper_in_cross_in_cross_in' : False,
             'lower_in_cross_in_cross_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_minus.can_append, 
+            self._single_test(self.pseudo_minus.can_append,
                                 ttraj[test], results[test], failmsg)
 
     def test_strict_can_append_pseudominus(self):
@@ -648,10 +645,10 @@ def test_strict_can_append_pseudominus(self):
             'lower_cross_in_cross_in' : False,
             'upper_in_cross_in_cross_in' : False,
             'lower_in_cross_in_cross_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_minus.strict_can_append, 
+            self._single_test(self.pseudo_minus.strict_can_append,
                                 ttraj[test], results[test], failmsg)
 
     def test_can_prepend_pseudo_tis(self):
@@ -675,10 +672,10 @@ def test_can_prepend_pseudo_tis(self):
             'lower_in_out_in_in' : False,
             'upper_in_out_in_out_in' : False,
             'lower_in_out_in_out_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_tis.can_prepend, 
+            self._single_test(self.pseudo_tis.can_prepend,
                                 ttraj[test], results[test], failmsg)
 
     def test_strict_can_prepend_pseudo_tis(self):
@@ -701,10 +698,10 @@ def test_strict_can_prepend_pseudo_tis(self):
             'lower_in_out_in_in' : False,
             'upper_in_out_in_out_in' : False,
             'lower_in_out_in_out_in' : False
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_tis.strict_can_prepend, 
+            self._single_test(self.pseudo_tis.strict_can_prepend,
                                 ttraj[test], results[test], failmsg)
 
     def test_can_prepend_pseudo_minus(self):
@@ -738,10 +735,10 @@ def test_can_prepend_pseudo_minus(self):
             'lower_out_in_out_in': True,
             'upper_out_in_in_out_in' : True,
             'lower_out_in_in_out_in' : True
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_minus.can_prepend, 
+            self._single_test(self.pseudo_minus.can_prepend,
                                 ttraj[test], results[test], failmsg)
 
     def test_strict_can_prepend_pseudo_minus(self):
@@ -775,14 +772,14 @@ def test_strict_can_prepend_pseudo_minus(self):
             'lower_out_in_out_in': True,
             'upper_out_in_in_out_in' : True,
             'lower_out_in_in_out_in' : True
-        }   
+        }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(self.pseudo_minus.strict_can_prepend, 
+            self._single_test(self.pseudo_minus.strict_can_prepend,
                                 ttraj[test], results[test], failmsg)
 
 
-    
+
     def test_sequential_transition_frames(self):
         """SequentialEnsemble identifies transitions frames correctly"""
         ensemble = SequentialEnsemble([self.inX, self.outX])
@@ -793,7 +790,7 @@ def test_sequential_transition_frames(self):
                   }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(ensemble.transition_frames, 
+            self._single_test(ensemble.transition_frames,
                                 ttraj[test], results[test], failmsg)
 
     def test_sequential_simple_in_out_call(self):
@@ -806,7 +803,7 @@ def test_sequential_simple_in_out_call(self):
                   }
         for test in list(results.keys()):
             failmsg = "Failure in "+test+"("+str(ttraj[test])+"): "
-            self._single_test(ensemble, 
+            self._single_test(ensemble,
                                 ttraj[test], results[test], failmsg)
 
 
@@ -898,7 +895,7 @@ def test_sequential_generate_first_tis(self):
             logging.getLogger('openpathsampling.ensemble').info(
                 "Testing: "+str(test)
             )
-            self._single_test(ensemble, ttraj[test], 
+            self._single_test(ensemble, ttraj[test],
                               match_results[test], failmsg)
 
         append_results = {
@@ -916,18 +913,18 @@ def test_sequential_generate_first_tis(self):
             logging.getLogger('opentis.ensemble').info(
                 "Testing: "+str(test)
             )
-            self._single_test(ensemble.can_append, ttraj[test], 
+            self._single_test(ensemble.can_append, ttraj[test],
                               append_results[test], failmsg)
 
     def test_sequential_enter_exit(self):
         """SequentialEnsembles based on Enters/ExitsXEnsemble"""
         # TODO: this includes a test of the overlap ability
-        raise SkipTest
+        pytest.skip()
 
 
 
     def test_str(self):
-        assert_equal(self.pseudo_tis.__str__(), """[
+        assert self.pseudo_tis.__str__() == """[
 (
   x[t] in {x|Id(x) in [0.1, 0.5]} for all t
 )
@@ -943,7 +940,7 @@ def test_str(self):
 (
   len(x) = 1
 )
-]""")
+]"""
 
 
 class TestSequentialEnsembleCombination(EnsembleTest):
@@ -951,7 +948,7 @@ class TestSequentialEnsembleCombination(EnsembleTest):
     # useful to making sure that the ensemble combination of strict_can_*
     # works correctly, since this is where strict and normal have a
     # distinction
-    def setup(self):
+    def setup_method(self):
         self.ens1 = SequentialEnsemble([
             AllInXEnsemble(vol1) & LengthEnsemble(1),
             AllOutXEnsemble(vol1) & PartOutXEnsemble(vol2),
@@ -1185,7 +1182,7 @@ def test_strict_can_prepend(self):
         self._test_everything(self.combo_and.strict_can_prepend, and_true, False)
 
 class TestTISEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.tis = TISEnsemble(vol1, vol3, vol2, op)
         self.traj = ttraj['upper_in_out_cross_out_in']
         self.minl = min(op(self.traj))
@@ -1193,22 +1190,22 @@ def setup(self):
 
     def test_tis_trajectory_summary(self):
         summ = self.tis.trajectory_summary(self.traj)
-        assert_equal(summ['initial_state'], 0)
-        assert_equal(summ['final_state'], 0)
-        assert_equal(summ['max_lambda'], self.maxl)
-        assert_equal(summ['min_lambda'], self.minl)
+        assert summ['initial_state'] == 0
+        assert summ['final_state'] == 0
+        assert summ['max_lambda'] == self.maxl
+        assert summ['min_lambda'] == self.minl
 
     def test_tis_trajectory_summary_str(self):
         mystr = self.tis.trajectory_summary_str(self.traj)
         teststr = ("initial_state=stateA final_state=stateA min_lambda=" +
                    str(self.minl) + " max_lambda=" + str(self.maxl) + " ")
-        assert_equal(mystr, teststr)
+        assert mystr == teststr
 
     def test_no_frame_after_interface(self):
         traj_3 = make_1d_traj([0.2, 0.6,  2.1])
-        assert_equal(self.tis(traj_3), True)
+        assert self.tis(traj_3) is True
         traj_2 = make_1d_traj([0.2, 2.1])
-        assert_equal(self.tis(traj_2), True)
+        assert self.tis(traj_2) is True
 
     def test_tis_ensemble_candidate(self):
         tis = TISEnsemble(vol1, vol3, vol2, op, lambda_i=0.7)
@@ -1269,104 +1266,104 @@ def _was_cache_reset(self, cache):
         return cache.contents == { }
 
 class TestEnsembleCache(EnsembleCacheTest):
-    def setup(self):
+    def setup_method(self):
         self.fwd = EnsembleCache(direction=+1)
         self.rev = EnsembleCache(direction=-1)
         self.traj = ttraj['lower_in_out_in_in_out_in']
 
     def test_initially_reset(self):
-        assert_equal(self._was_cache_reset(self.fwd), True)
-        assert_equal(self._was_cache_reset(self.rev), True)
+        assert self._was_cache_reset(self.fwd) is True
+        assert self._was_cache_reset(self.rev) is True
 
     def test_change_trajectory(self):
         traj2 = ttraj['lower_in_out_in']
         # tests for forward
         self.fwd.contents = { 'test' : 'object' }
-        assert_equal(self._was_cache_reset(self.fwd), False)
+        assert self._was_cache_reset(self.fwd) is False
         self.fwd.check(self.traj)
-        assert_equal(self._was_cache_reset(self.fwd), True)
+        assert self._was_cache_reset(self.fwd) is True
         self.fwd.contents['ens_num'] = 1
-        assert_equal(self._was_cache_reset(self.fwd), False)
+        assert self._was_cache_reset(self.fwd) is False
         self.fwd.check(traj2)
-        assert_equal(self._was_cache_reset(self.fwd), True)
+        assert self._was_cache_reset(self.fwd) is True
         # tests for backward
         self.rev.contents = { 'test' : 'object' }
-        assert_equal(self._was_cache_reset(self.rev), False)
+        assert self._was_cache_reset(self.rev) is False
         self.rev.check(self.traj)
-        assert_equal(self._was_cache_reset(self.rev), True)
+        assert self._was_cache_reset(self.rev) is True
         self.rev.contents['ens_num'] = 1
-        assert_equal(self._was_cache_reset(self.rev), False)
+        assert self._was_cache_reset(self.rev) is False
         self.rev.check(traj2)
-        assert_equal(self._was_cache_reset(self.rev), True)
+        assert self._was_cache_reset(self.rev) is True
 
     def test_trajectory_by_frame(self):
         # tests for forward
         self.fwd.check(self.traj[0:1])
-        assert_equal(self._was_cache_reset(self.fwd), True)
+        assert self._was_cache_reset(self.fwd) is True
         self.fwd.contents = { 'test' : 'object' }
-        assert_equal(self._was_cache_reset(self.fwd), False)
+        assert self._was_cache_reset(self.fwd) is False
         self.fwd.check(self.traj[0:2])
-        assert_equal(self._was_cache_reset(self.fwd), False)
+        assert self._was_cache_reset(self.fwd) is False
         # tests for backward
         self.rev.check(self.traj[-1:])
-        assert_equal(self._was_cache_reset(self.rev), True)
+        assert self._was_cache_reset(self.rev) is True
         self.rev.contents = { 'test' : 'object' }
-        assert_equal(self._was_cache_reset(self.rev), False)
+        assert self._was_cache_reset(self.rev) is False
         self.rev.check(self.traj[-2:])
-        assert_equal(self._was_cache_reset(self.rev), False)
+        assert self._was_cache_reset(self.rev) is False
 
     def test_same_traj_twice_no_reset(self):
         # tests for forward
         self.fwd.check(self.traj)
-        assert_equal(self._was_cache_reset(self.fwd), True)
+        assert self._was_cache_reset(self.fwd) is True
         self.fwd.contents = { 'test' : 'object' }
         self.fwd.check(self.traj)
-        assert_equal(self._was_cache_reset(self.fwd), False)
+        assert self._was_cache_reset(self.fwd) is False
         # tests for backward
         self.rev.check(self.traj)
-        assert_equal(self._was_cache_reset(self.rev), True)
+        assert self._was_cache_reset(self.rev) is True
         self.rev.contents = { 'test' : 'object' }
         self.rev.check(self.traj)
-        assert_equal(self._was_cache_reset(self.rev), False)
+        assert self._was_cache_reset(self.rev) is False
 
 
     def test_trajectory_skips_frame(self):
         # tests for forward
         self.fwd.check(self.traj[0:1])
-        assert_equal(self._was_cache_reset(self.fwd), True)
+        assert self._was_cache_reset(self.fwd) is True
         self.fwd.contents = { 'test' : 'object' }
-        assert_equal(self._was_cache_reset(self.fwd), False)
+        assert self._was_cache_reset(self.fwd) is False
         self.fwd.check(self.traj[0:3])
-        assert_equal(self._was_cache_reset(self.fwd), True)
+        assert self._was_cache_reset(self.fwd) is True
         # tests for backward
         self.rev.check(self.traj[-1:])
-        assert_equal(self._was_cache_reset(self.rev), True)
+        assert self._was_cache_reset(self.rev) is True
         self.rev.contents = { 'test' : 'object' }
-        assert_equal(self._was_cache_reset(self.rev), False)
+        assert self._was_cache_reset(self.rev) is False
         self.rev.check(self.traj[-3:])
-        assert_equal(self._was_cache_reset(self.rev), True)
+        assert self._was_cache_reset(self.rev) is True
 
     def test_trajectory_middle_frame_changes(self):
         # tests for forward
         self.fwd.check(self.traj[0:2])
-        assert_equal(self._was_cache_reset(self.fwd), True)
+        assert self._was_cache_reset(self.fwd) is True
         self.fwd.contents = { 'test' : 'object' }
-        assert_equal(self._was_cache_reset(self.fwd), False)
+        assert self._was_cache_reset(self.fwd) is False
         new_traj = self.traj[0:1] + self.traj[3:5]
         self.fwd.check(new_traj)
-        assert_equal(self._was_cache_reset(self.fwd), True)
+        assert self._was_cache_reset(self.fwd) is True
         # tests for backward
         self.rev.check(self.traj[0:2])
-        assert_equal(self._was_cache_reset(self.rev), True)
+        assert self._was_cache_reset(self.rev) is True
         self.rev.contents = { 'test' : 'object' }
-        assert_equal(self._was_cache_reset(self.rev), False)
+        assert self._was_cache_reset(self.rev) is False
         new_traj = self.traj[-4:-2] + self.traj[-1:]
         self.rev.check(new_traj)
-        assert_equal(self._was_cache_reset(self.rev), True)
+        assert self._was_cache_reset(self.rev) is True
 
 
 class TestSequentialEnsembleCache(EnsembleCacheTest):
-    def setup(self):
+    def setup_method(self):
         self.inX = AllInXEnsemble(vol1)
         self.outX = AllOutXEnsemble(vol1)
         self.length1 = LengthEnsemble(1)
@@ -1383,92 +1380,94 @@ def test_all_in_as_seq_can_append(self):
         ens = SequentialEnsemble([AllInXEnsemble(vol1 | vol2 | vol3)])
         cache = ens._cache_can_append
         traj = ttraj['upper_in_in_out_out_in_in']
-        assert_equal(ens.can_append(traj[0:1]), True)
-        assert_equal(ens.can_append(traj[0:2]), True)
-        assert_equal(ens.can_append(traj[0:3]), True)
-        assert_equal(ens.can_append(traj[0:4]), True)
-        assert_equal(ens.can_append(traj[0:5]), True)
-        assert_equal(ens.can_append(traj[0:6]), True)
+        assert ens.can_append(traj[0:1]) is True
+        assert ens.can_append(traj[0:2]) is True
+        assert ens.can_append(traj[0:3]) is True
+        assert ens.can_append(traj[0:4]) is True
+        assert ens.can_append(traj[0:5]) is True
+        assert ens.can_append(traj[0:6]) is True
 
     def test_sequential_caching_can_append(self):
         cache = self.pseudo_minus._cache_can_append
-        assert_equal(self.pseudo_minus.can_append(self.traj[0:1]), True)
-        assert_equal(cache.contents['ens_num'], 1)
-        assert_equal(cache.contents['ens_from'], 0)
-        assert_equal(cache.contents['subtraj_from'], 1)
+        assert self.pseudo_minus.can_append(self.traj[0:1]) is True
+        assert cache.contents['ens_num'] == 1
+        assert cache.contents['ens_from'] == 0
+        assert cache.contents['subtraj_from'] == 1
         logging.getLogger('openpathsampling.ensemble').debug("Starting [0:2]")
-        assert_equal(self.pseudo_minus.can_append(self.traj[0:2]), True)
-        assert_equal(cache.contents['ens_num'], 1)
-        assert_equal(cache.contents['ens_from'], 0)
-        assert_equal(cache.contents['subtraj_from'], 1)
+        assert self.pseudo_minus.can_append(self.traj[0:2]) is True
+        assert cache.contents['ens_num'] == 1
+        assert cache.contents['ens_from'] == 0
+        assert cache.contents['subtraj_from'] == 1
         logging.getLogger('openpathsampling.ensemble').debug("Starting [0:3]")
-        assert_equal(self.pseudo_minus.can_append(self.traj[0:3]), True)
-        assert_equal(cache.contents['ens_num'], 2)
-        assert_equal(cache.contents['ens_from'], 0)
-        assert_equal(cache.contents['subtraj_from'], 2)
+        assert self.pseudo_minus.can_append(self.traj[0:3]) is True
+        assert cache.contents['ens_num'] == 2
+        assert cache.contents['ens_from'] == 0
+        assert cache.contents['subtraj_from'] == 2
         logging.getLogger('openpathsampling.ensemble').debug("Starting [0:4]")
-        assert_equal(self.pseudo_minus.can_append(self.traj[0:4]), True)
-        assert_equal(cache.contents['ens_num'], 2)
-        assert_equal(cache.contents['ens_from'], 0)
-        assert_equal(cache.contents['subtraj_from'], 2)
+        assert self.pseudo_minus.can_append(self.traj[0:4]) is True
+        assert cache.contents['ens_num'] == 2
+        assert cache.contents['ens_from'] == 0
+        assert cache.contents['subtraj_from'] == 2
         logging.getLogger('openpathsampling.ensemble').debug("Starting [0:5]")
-        assert_equal(self.pseudo_minus.can_append(self.traj[0:5]), True)
-        assert_equal(cache.contents['ens_num'], 3)
-        assert_equal(cache.contents['ens_from'], 0)
-        assert_equal(cache.contents['subtraj_from'], 4)
+        assert self.pseudo_minus.can_append(self.traj[0:5]) is True
+        assert cache.contents['ens_num'] == 3
+        assert cache.contents['ens_from'] == 0
+        assert cache.contents['subtraj_from'] == 4
         logging.getLogger('openpathsampling.ensemble').debug("Starting [0:6]")
-        assert_equal(self.pseudo_minus.can_append(self.traj[0:6]), False)
-        assert_equal(cache.contents['ens_num'], 4)
-        assert_equal(cache.contents['ens_from'], 0)
-        assert_equal(cache.contents['subtraj_from'], 5)
+        assert self.pseudo_minus.can_append(self.traj[0:6]) is False
+        assert cache.contents['ens_num'] == 4
+        assert cache.contents['ens_from'] == 0
+        assert cache.contents['subtraj_from'] == 5
 
     def test_sequential_caching_resets(self):
         #cache = self.pseudo_minus._cache_can_append
-        assert_equal(self.pseudo_minus.can_append(self.traj[2:3]), True)
-        assert_equal(self.pseudo_minus(self.traj[2:3]), False)
+        assert self.pseudo_minus.can_append(self.traj[2:3]) is True
+        assert self.pseudo_minus(self.traj[2:3]) is False
         #assert_equal(self._was_cache_reset(cache), True)
-        assert_equal(self.pseudo_minus.can_append(self.traj[2:4]), True)
-        assert_equal(self.pseudo_minus(self.traj[2:4]), False)
+        assert self.pseudo_minus.can_append(self.traj[2:4]) is True
+        assert self.pseudo_minus(self.traj[2:4]) is False
         #assert_equal(self._was_cache_reset(cache), True)
         for i in range(4, len(self.traj)-1):
-            assert_equal(self.pseudo_minus.can_append(self.traj[2:i+1]), True)
-            assert_equal(self.pseudo_minus(self.traj[2:i+1]), False)
+            assert self.pseudo_minus.can_append(self.traj[2:i+1]) is True
+            assert self.pseudo_minus(self.traj[2:i+1]) is False
             #assert_equal(self._was_cache_reset(cache), False)
-        assert_equal(self.pseudo_minus.can_append(self.traj[2:]), False)
-        assert_equal(self.pseudo_minus(self.traj[2:]), False)
+        assert self.pseudo_minus.can_append(self.traj[2:]) is False
+        assert self.pseudo_minus(self.traj[2:]) is False
         #assert_equal(self._was_cache_reset(cache), False)
+
+    def test_sequential_caching_resets_backward(self):
         # TODO: same story backward
-        raise SkipTest
+        pytest.skip()
 
     def test_sequential_caching_call(self):
-        raise SkipTest
+        pytest.skip()
 
     def test_sequential_caching_can_prepend(self):
         cache = self.pseudo_minus._cache_can_prepend
-        assert_equal(self.pseudo_minus.can_prepend(self.traj[5:6]), True)
-        assert_equal(cache.contents['ens_num'], 3)
-        assert_equal(cache.contents['ens_from'], 4)
-        assert_equal(cache.contents['subtraj_from'], -1)
-        assert_equal(self.pseudo_minus.can_prepend(self.traj[4:6]), True)
-        assert_equal(cache.contents['ens_num'], 3)
-        assert_equal(cache.contents['ens_from'], 4)
-        assert_equal(cache.contents['subtraj_from'], -1)
-        assert_equal(self.pseudo_minus.can_prepend(self.traj[3:6]), True)
-        assert_equal(cache.contents['ens_num'], 2)
-        assert_equal(cache.contents['ens_from'], 4)
-        assert_equal(cache.contents['subtraj_from'], -2)
-        assert_equal(self.pseudo_minus.can_prepend(self.traj[2:6]), True)
-        assert_equal(cache.contents['ens_num'], 2)
-        assert_equal(cache.contents['ens_from'], 4)
-        assert_equal(cache.contents['subtraj_from'], -2)
-        assert_equal(self.pseudo_minus.can_prepend(self.traj[1:6]), True)
-        assert_equal(cache.contents['ens_num'], 1)
-        assert_equal(cache.contents['ens_from'], 4)
-        assert_equal(cache.contents['subtraj_from'], -4)
-        assert_equal(self.pseudo_minus.can_prepend(self.traj[0:6]), False)
-        assert_equal(cache.contents['ens_num'], 0)
-        assert_equal(cache.contents['ens_from'], 4)
-        assert_equal(cache.contents['subtraj_from'], -5)
+        assert self.pseudo_minus.can_prepend(self.traj[5:6]) is True
+        assert cache.contents['ens_num'] == 3
+        assert cache.contents['ens_from'] == 4
+        assert cache.contents['subtraj_from'] == -1
+        assert self.pseudo_minus.can_prepend(self.traj[4:6]) is True
+        assert cache.contents['ens_num'] == 3
+        assert cache.contents['ens_from'] == 4
+        assert cache.contents['subtraj_from'] == -1
+        assert self.pseudo_minus.can_prepend(self.traj[3:6]) is True
+        assert cache.contents['ens_num'] == 2
+        assert cache.contents['ens_from'] == 4
+        assert cache.contents['subtraj_from'] == -2
+        assert self.pseudo_minus.can_prepend(self.traj[2:6]) is True
+        assert cache.contents['ens_num'] == 2
+        assert cache.contents['ens_from'] == 4
+        assert cache.contents['subtraj_from'] == -2
+        assert self.pseudo_minus.can_prepend(self.traj[1:6]) is True
+        assert cache.contents['ens_num'] == 1
+        assert cache.contents['ens_from'] == 4
+        assert cache.contents['subtraj_from'] == -4
+        assert self.pseudo_minus.can_prepend(self.traj[0:6]) is False
+        assert cache.contents['ens_num'] == 0
+        assert cache.contents['ens_from'] == 4
+        assert cache.contents['subtraj_from'] == -5
 
 
 
@@ -1478,8 +1477,8 @@ def test_sliced_ensemble_init(self):
         init_as_int = SlicedTrajectoryEnsemble(AllInXEnsemble(vol1), 3)
         init_as_slice = SlicedTrajectoryEnsemble(AllInXEnsemble(vol1),
                                                  slice(3, 4))
-        assert_equal(init_as_int, init_as_slice)
-        assert_equal(init_as_slice.region, init_as_int.region)
+        assert init_as_int == init_as_slice
+        assert init_as_slice.region == init_as_int.region
 
     def test_sliced_as_TISEnsemble(self):
         '''SlicedTrajectory and Sequential give same TIS results'''
@@ -1499,7 +1498,7 @@ def test_sliced_as_TISEnsemble(self):
             failmsg = "Failure in "+test+"("+tstr(ttraj[test])+"): "
             self._single_test(real_tis, ttraj[test],
                               sequential_tis(ttraj[test]), failmsg)
-            self._single_test(sliced_tis, ttraj[test], 
+            self._single_test(sliced_tis, ttraj[test],
                               sequential_tis(ttraj[test]), failmsg)
 
     def test_slice_outside_trajectory_range(self):
@@ -1507,7 +1506,7 @@ def test_slice_outside_trajectory_range(self):
         test = 'upper_in'
         # the slice should return the empty trajectory, and therefore should
         # return false
-        assert_equal(ens(ttraj[test]), False)
+        assert ens(ttraj[test]) is False
 
     def test_even_sliced_trajectory(self):
         even_slice = slice(None, None, 2)
@@ -1591,17 +1590,17 @@ def test_sliced_str(self):
         slice_no_ends = slice(1, -1)
         inX = AllInXEnsemble(vol1)
         inXstr = "x[t] in {x|Id(x) in [0.1, 0.5]} for all t"
-        assert_equal(SlicedTrajectoryEnsemble(inX, even_slice).__str__(),
-                     "("+inXstr+" in {:} every 2)")
-        assert_equal(SlicedTrajectoryEnsemble(inX, slice_1_10).__str__(),
-                     "("+inXstr+" in {1:10})")
-        assert_equal(SlicedTrajectoryEnsemble(inX, slice_1_end).__str__(),
-                     "("+inXstr+" in {1:})")
-        assert_equal(SlicedTrajectoryEnsemble(inX, slice_no_ends).__str__(),
-                     "("+inXstr+" in {1:-1})")
+        assert (SlicedTrajectoryEnsemble(inX, even_slice).__str__()
+                == "("+inXstr+" in {:} every 2)")
+        assert (SlicedTrajectoryEnsemble(inX, slice_1_10).__str__()
+                == "("+inXstr+" in {1:10})")
+        assert (SlicedTrajectoryEnsemble(inX, slice_1_end).__str__()
+                == "("+inXstr+" in {1:})")
+        assert (SlicedTrajectoryEnsemble(inX, slice_no_ends).__str__()
+                == "("+inXstr+" in {1:-1})")
 
 class TestOptionalEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.start_opt = SequentialEnsemble([
             OptionalEnsemble(AllOutXEnsemble(vol1)),
             AllInXEnsemble(vol1),
@@ -1854,10 +1853,10 @@ def test_optional_middle_strict_can_prepend(self):
     def test_optional_str(self):
         inX = AllInXEnsemble(vol1)
         opt_inX = OptionalEnsemble(inX)
-        assert_equal(opt_inX.__str__(), "{"+inX.__str__()+"} (OPTIONAL)")
+        assert opt_inX.__str__() == "{"+inX.__str__()+"} (OPTIONAL)"
 
 class TestPrefixTrajectoryEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.inX = AllInXEnsemble(vol1)
 
     def test_bad_start_traj(self):
@@ -1866,9 +1865,9 @@ def test_bad_start_traj(self):
             SequentialEnsemble([self.inX]),
             traj[0:2]
         )
-        assert_equal(ens.can_append(traj[0:3]), False)
-        assert_equal(ens.strict_can_append(traj[0:3]), False)
-        assert_equal(ens(traj[0:3]), False)
+        assert ens.can_append(traj[0:3]) is False
+        assert ens.strict_can_append(traj[0:3]) is False
+        assert ens(traj[0:3]) is False
 
     def test_good_start_traj(self):
         traj = ttraj['upper_in_in_in']
@@ -1876,27 +1875,27 @@ def test_good_start_traj(self):
             SequentialEnsemble([self.inX]),
             traj[0:2]
         )
-        assert_equal(ens.can_append(traj[2:3]), True)
-        assert_equal(ens.strict_can_append(traj[2:3]), True)
-        assert_equal(ens(traj[2:3]), True)
+        assert ens.can_append(traj[2:3]) is True
+        assert ens.strict_can_append(traj[2:3]) is True
+        assert ens(traj[2:3]) is True
 
-    @raises(RuntimeError)
     def test_can_prepend(self):
         traj = ttraj['upper_in_in_in']
         ens = PrefixTrajectoryEnsemble(
             SequentialEnsemble([self.inX]),
             traj[0:2]
         )
-        ens.can_prepend(traj[2:3])
+        with pytest.raises(RuntimeError):
+            ens.can_prepend(traj[2:3])
 
-    @raises(RuntimeError)
     def test_strict_can_prepend(self):
         traj = ttraj['upper_in_in_in']
         ens = PrefixTrajectoryEnsemble(
             SequentialEnsemble([self.inX]),
             traj[0:2]
         )
-        ens.strict_can_prepend(traj[2:3])
+        with pytest.raises(RuntimeError):
+            ens.strict_can_prepend(traj[2:3])
 
     def test_caching_in_fwdapp_seq(self):
         inX = AllInXEnsemble(vol1)
@@ -1907,29 +1906,29 @@ def test_caching_in_fwdapp_seq(self):
             outX,
             inX,
             outX,
-            inX & length1 
+            inX & length1
         ])
         traj = ttraj['upper_in_out_in_in_out_in']
         ens = PrefixTrajectoryEnsemble(pseudo_minus, traj[0:2])
-        assert_equal(ens.can_append(traj[2:3]), True)
-        assert_equal(ens._cached_trajectory, traj[0:3])
-        assert_equal(ens._cache_can_append.trusted, False)
+        assert ens.can_append(traj[2:3]) is True
+        assert ens._cached_trajectory == traj[0:3]
+        assert ens._cache_can_append.trusted is False
 
-        assert_equal(ens.can_append(traj[2:4]), True)
-        assert_equal(ens._cached_trajectory, traj[0:4])
-        assert_equal(ens._cache_can_append.trusted, True)
+        assert ens.can_append(traj[2:4]) is True
+        assert ens._cached_trajectory == traj[0:4]
+        assert ens._cache_can_append.trusted is True
 
-        assert_equal(ens.can_append(traj[2:5]), True)
-        assert_equal(ens._cached_trajectory, traj[0:5])
-        assert_equal(ens._cache_can_append.trusted, True)
+        assert ens.can_append(traj[2:5]) is True
+        assert ens._cached_trajectory == traj[0:5]
+        assert ens._cache_can_append.trusted is True
 
-        assert_equal(ens.can_append(traj[2:6]), False)
-        assert_equal(ens._cached_trajectory, traj[0:6])
-        assert_equal(ens._cache_can_append.trusted, True)
+        assert ens.can_append(traj[2:6]) is False
+        assert ens._cached_trajectory == traj[0:6]
+        assert ens._cache_can_append.trusted is True
 
 
 class TestSuffixTrajectoryEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         xval = paths.FunctionCV("x", lambda s : s.xyz[0][0])
         vol = paths.CVDefinedVolume(xval, 0.1, 0.5)
         self.inX = AllInXEnsemble(vol)
@@ -1941,9 +1940,9 @@ def test_bad_end_traj(self):
             SequentialEnsemble([self.inX]),
             traj[-2:]
         )
-        assert_equal(ens.can_prepend(traj[-3:2]), False)
-        assert_equal(ens.strict_can_prepend(traj[-3:2]), False)
-        assert_equal(ens(traj[-3:2]), False)
+        assert ens.can_prepend(traj[-3:2]) is False
+        assert ens.strict_can_prepend(traj[-3:2]) is False
+        assert ens(traj[-3:2]) is False
 
     def test_good_end_traj(self):
         traj = ttraj['upper_out_in_in_in']
@@ -1951,30 +1950,30 @@ def test_good_end_traj(self):
             SequentialEnsemble([self.inX]),
             traj[-2:]
         )
-        assert_equal(ens.can_prepend(traj[-3:-2]), True)
-        assert_equal(ens.strict_can_prepend(traj[-3:-2]), True)
-        assert_equal(ens(traj[-3:-2]), True)
-        assert_equal(ens.can_prepend(traj[-4:-2]), False)
-        assert_equal(ens.strict_can_prepend(traj[-4:-2]), False)
-        assert_equal(ens(traj[-4:-2]), False)
-
-    @raises(RuntimeError)
+        assert ens.can_prepend(traj[-3:-2]) is True
+        assert ens.strict_can_prepend(traj[-3:-2]) is True
+        assert ens(traj[-3:-2]) is True
+        assert ens.can_prepend(traj[-4:-2]) is False
+        assert ens.strict_can_prepend(traj[-4:-2]) is False
+        assert ens(traj[-4:-2]) is False
+
     def test_can_append(self):
         traj = ttraj['upper_out_in_in_in']
         ens = SuffixTrajectoryEnsemble(
             SequentialEnsemble([self.inX]),
             traj[-2:]
         )
-        ens.can_append(traj)
+        with pytest.raises(RuntimeError):
+            ens.can_append(traj)
 
-    @raises(RuntimeError)
     def test_strict_can_append(self):
         traj = ttraj['upper_out_in_in_in']
         ens = SuffixTrajectoryEnsemble(
             SequentialEnsemble([self.inX]),
             traj[-2:]
         )
-        ens.strict_can_append(traj)
+        with pytest.raises(RuntimeError):
+            ens.strict_can_append(traj)
 
     def test_caching_in_bkwdprep_seq(self):
         length1 = LengthEnsemble(1)
@@ -1983,33 +1982,33 @@ def test_caching_in_bkwdprep_seq(self):
             self.outX,
             self.inX,
             self.outX,
-            self.inX & length1 
+            self.inX & length1
         ])
         traj = ttraj['upper_in_out_in_in_out_in']
 
         # sanity checks before running the suffixed version
-        assert_equal(pseudo_minus(traj), True)
+        assert pseudo_minus(traj) is True
         for i in range(-1, -6):
-            assert_equal(pseudo_minus.can_prepend(traj[i:]), True)
+            assert pseudo_minus.can_prepend(traj[i:]) is True
 
         logger.debug("alltraj " + str([id(i) for i in traj]))
         ens = SuffixTrajectoryEnsemble(pseudo_minus, traj[-3:])
-        assert_equal(len(ens._cached_trajectory), 3)
+        assert len(ens._cached_trajectory) == 3
 
-        assert_equal(ens.can_prepend(traj[-4:-3].reversed), True)
-        assert_equal(len(ens._cached_trajectory), 4)
-        assert_equal(ens._cache_can_prepend.trusted, False)
+        assert ens.can_prepend(traj[-4:-3].reversed) is True
+        assert len(ens._cached_trajectory) == 4
+        assert ens._cache_can_prepend.trusted is False
 
-        assert_equal(ens.can_prepend(traj[-5:-3].reversed), True)
-        assert_equal(len(ens._cached_trajectory), 5)
-        assert_equal(ens._cache_can_prepend.trusted, True)
+        assert ens.can_prepend(traj[-5:-3].reversed) is True
+        assert len(ens._cached_trajectory) == 5
+        assert ens._cache_can_prepend.trusted is True
 
-        assert_equal(ens.can_prepend(traj[-6:-3].reversed), False)
-        assert_equal(len(ens._cached_trajectory), 6)
-        assert_equal(ens._cache_can_prepend.trusted, True)
+        assert ens.can_prepend(traj[-6:-3].reversed) is False
+        assert len(ens._cached_trajectory) == 6
+        assert ens._cache_can_prepend.trusted is True
 
 class TestMinusInterfaceEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         # Mostly we use minus ensembles where the state matches the first
         # interface. We also test the case where that isn't in, in which
         # case there's an interstitial zone. (Only test it for nl=2 to keep
@@ -2034,13 +2033,13 @@ def test_dict_round_trip(self):
         dct = self.minus_nl2.to_dict()
         rebuilt = MinusInterfaceEnsemble.from_dict(dct)
         dct2 = rebuilt.to_dict()
-        assert_equal(dct, dct2)
+        assert dct == dct2
 
-    @raises(ValueError)
     def test_minus_nl1_fail(self):
-        minus_nl1 = MinusInterfaceEnsemble(state_vol=vol1,
-                                           innermost_vols=vol2,
-                                           n_l=1)
+        with pytest.raises(ValueError):
+            minus_nl1 = MinusInterfaceEnsemble(state_vol=vol1,
+                                               innermost_vols=vol2,
+                                               n_l=1)
 
 
     def test_minus_nl2_ensemble(self):
@@ -2311,13 +2310,13 @@ def test_extend_sample_from_trajectories(self):
             sset, replica=-1, engine=engine, level='complex'
         )
 
-        assert_equal(sample.ensemble(sample.trajectory), True)
-        assert_equal(sample.ensemble, self.minus_nl2)
-        assert_equal(sample.replica, -1)
-        assert_equal(len(sample.trajectory), 5)
+        assert sample.ensemble(sample.trajectory) is True
+        assert sample.ensemble == self.minus_nl2
+        assert sample.replica == -1
+        assert len(sample.trajectory) == 5
         expected = trajB + ttraj['upper_out_in']
         for (t, b) in zip(sample.trajectory, expected):
-            assert_equal(t.xyz[0][0], b.xyz[0][0])
+            assert t.xyz[0][0] == b.xyz[0][0]
 
         # test with a different trajectory
         predestined_snaps = [trajB[-1]]+ttraj['upper_in_out_in']
@@ -2327,18 +2326,18 @@ def test_extend_sample_from_trajectories(self):
             sset, replica=-1, engine=engine, level='complex'
         )
 
-        assert_equal(sample.ensemble(sample.trajectory), True)
-        assert_equal(sample.ensemble, self.minus_nl2)
-        assert_equal(sample.replica, -1)
-        assert_equal(len(sample.trajectory), 6)
+        assert sample.ensemble(sample.trajectory) is True
+        assert sample.ensemble == self.minus_nl2
+        assert sample.replica == -1
+        assert len(sample.trajectory) == 6
         expected = trajB + ttraj['upper_in_out_in']
         for (t, b) in zip(sample.trajectory, expected):
-            assert_equal(t.xyz[0][0], b.xyz[0][0])
+            assert t.xyz[0][0] == b.xyz[0][0]
 
 
 # TODO: this whole class should become a single test in SeqEns
 class TestSingleEnsembleSequentialEnsemble(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         #self.inner_ens = AllInXEnsemble(vol1 | vol2)
         self.inner_ens = LengthEnsemble(3) & AllInXEnsemble( vol1 | vol2 )
         self.ens = SequentialEnsemble([self.inner_ens])
@@ -2355,35 +2354,34 @@ def test_it_all(self):
 
 
 class TestEnsembleSplit(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.inA = AllInXEnsemble(vol1)
         self.outA = AllOutXEnsemble(vol1)
 
     def test_split(self):
-#        raise SkipTest
         traj1 = ttraj['upper_in_out_in_in']
         # print [s for s in traj1]
         subtrajs_in_1 = self.inA.split(traj1)
         # print subtrajs_in_1
         # print [[s for s in t] for t in subtrajs_in_1]
-        assert_equal(len(subtrajs_in_1), 2)
-        assert_equal(len(subtrajs_in_1[0]), 1)
-        assert_equal(len(subtrajs_in_1[1]), 2)
+        assert len(subtrajs_in_1) == 2
+        assert len(subtrajs_in_1[0]) == 1
+        assert len(subtrajs_in_1[1]) == 2
         subtrajs_out_1 = self.outA.split(traj1)
-        assert_equal(len(subtrajs_out_1), 1)
+        assert len(subtrajs_out_1) == 1
 
         traj2 = ttraj['upper_in_out_in_in_out_in']
         # print [s for s in traj2]
         subtrajs_in_2 = self.inA.split(traj2)
         # print [[s for s in t] for t in subtrajs_in_2]
-        assert_equal(len(subtrajs_in_2), 3)
-        assert_equal(len(subtrajs_in_2[0]), 1)
-        assert_equal(len(subtrajs_in_2[1]), 2)
-        assert_equal(len(subtrajs_in_2[2]), 1)
+        assert len(subtrajs_in_2) == 3
+        assert len(subtrajs_in_2[0]) == 1
+        assert len(subtrajs_in_2[1]) == 2
+        assert len(subtrajs_in_2[2]) == 1
         subtrajs_out_2 = self.outA.split(traj2)
-        assert_equal(len(subtrajs_out_2), 2)
-        assert_equal(len(subtrajs_out_2[0]), 1)
-        assert_equal(len(subtrajs_out_2[1]), 1)
+        assert len(subtrajs_out_2) == 2
+        assert len(subtrajs_out_2[0]) == 1
+        assert len(subtrajs_out_2[1]) == 1
 
         ensembleAXA = paths.SequentialEnsemble([
             self.inA,
@@ -2397,54 +2395,54 @@ def test_split(self):
         assert(self.outA(paths.Trajectory([traj3[1]])))
 
         subtrajs_in_3 = ensembleAXA.split(traj3)
-        assert_equal((len(subtrajs_in_3)), 2)
-        assert_equal((len(subtrajs_in_3[0])), 3)
-        assert_equal((len(subtrajs_in_3[1])), 3)
+        assert (len(subtrajs_in_3)) == 2
+        assert (len(subtrajs_in_3[0])) == 3
+        assert (len(subtrajs_in_3[1])) == 3
         assert(traj3.subtrajectory_indices(subtrajs_in_3[0]) == [0, 1, 2])
         assert(traj3.subtrajectory_indices(subtrajs_in_3[1]) == [2, 3, 4])
 
         subtrajs_in_3 = ensembleAXA.split(traj3, reverse=True)
-        assert_equal((len(subtrajs_in_3)), 2)
-        assert_equal((len(subtrajs_in_3[0])), 3)
-        assert_equal((len(subtrajs_in_3[1])), 3)
+        assert (len(subtrajs_in_3)) == 2
+        assert (len(subtrajs_in_3[0])) == 3
+        assert (len(subtrajs_in_3[1])) == 3
         assert(traj3.subtrajectory_indices(subtrajs_in_3[0]) == [2, 3, 4])
         assert(traj3.subtrajectory_indices(subtrajs_in_3[1]) == [0, 1, 2])
 
         subtrajs_in_3 = ensembleAXA.split(traj3, overlap=0)
-        assert_equal((len(subtrajs_in_3)), 1)
-        assert_equal((len(subtrajs_in_3[0])), 3)
+        assert (len(subtrajs_in_3)) == 1
+        assert (len(subtrajs_in_3[0])) == 3
         assert(traj3.subtrajectory_indices(subtrajs_in_3[0]) == [0, 1, 2])
 
         subtrajs_in_3 = ensembleAXA.split(traj3, reverse=True, overlap=0)
-        assert_equal((len(subtrajs_in_3)), 1)
-        assert_equal((len(subtrajs_in_3[0])), 3)
+        assert (len(subtrajs_in_3)) == 1
+        assert (len(subtrajs_in_3[0])) == 3
         assert(traj3.subtrajectory_indices(subtrajs_in_3[0]) == [2, 3, 4])
 
         subtrajs_in_3 = ensembleAXA.split(traj3, overlap=1, max_length=2)
-        assert_equal((len(subtrajs_in_3)), 0)
+        assert (len(subtrajs_in_3)) == 0
 
         subtrajs_in_3 = ensembleAXA.split(traj3, reverse=True, max_length=2)
-        assert_equal((len(subtrajs_in_3)), 0)
+        assert (len(subtrajs_in_3)) == 0
 
         subtrajs_in_3 = ensembleAXA.split(traj3, max_length=3)
-        assert_equal(len(subtrajs_in_3), 2)
-        assert_equal((len(subtrajs_in_3[0])), 3)
-        assert_equal((len(subtrajs_in_3[1])), 3)
+        assert len(subtrajs_in_3) == 2
+        assert (len(subtrajs_in_3[0])) == 3
+        assert (len(subtrajs_in_3[1])) == 3
         assert(traj3.subtrajectory_indices(subtrajs_in_3[0]) == [0, 1, 2])
         assert(traj3.subtrajectory_indices(subtrajs_in_3[1]) == [2, 3, 4])
 
         subtrajs_in_3 = ensembleAXA.split(traj3, reverse=True, max_length=3)
-        assert_equal((len(subtrajs_in_3)), 2)
-        assert_equal((len(subtrajs_in_3[0])), 3)
-        assert_equal((len(subtrajs_in_3[1])), 3)
+        assert (len(subtrajs_in_3)) == 2
+        assert (len(subtrajs_in_3[0])) == 3
+        assert (len(subtrajs_in_3[1])) == 3
         assert(traj3.subtrajectory_indices(subtrajs_in_3[1]) == [0, 1, 2])
         assert(traj3.subtrajectory_indices(subtrajs_in_3[0]) == [2, 3, 4])
 
         subtrajs_in_3 = ensembleAXA.split(traj3, reverse=False, min_length=4)
-        assert_equal((len(subtrajs_in_3)), 0)
+        assert (len(subtrajs_in_3)) == 0
 
         subtrajs_in_3 = ensembleAXA.split(traj3, reverse=True, min_length=4)
-        assert_equal((len(subtrajs_in_3)), 0)
+        assert (len(subtrajs_in_3)) == 0
 
         sub_traj = ensembleAXA.find_first_subtrajectory(traj3)
         assert(traj3.subtrajectory_indices(sub_traj) == [0,1,2])
@@ -2453,7 +2451,7 @@ def test_split(self):
         assert(traj3.subtrajectory_indices(sub_traj) == [2,3,4])
 
 class TestVolumeCombinations(EnsembleTest):
-    def setup(self):
+    def setup_method(self):
         self.outA = paths.AllOutXEnsemble(vol1)
         self.outB = paths.AllOutXEnsemble(~vol2)
         self.outA.special_debug = True
@@ -2475,7 +2473,7 @@ def setup(self):
     def _test_trusted(self, trajectory, function, results,
                       cache_results=None, direction=+1, start_traj_len=1):
         # Tests `trajectory` frame by frame in a forward direction for the
-        # `function`, expecting `results`. Additionally, can take the 
+        # `function`, expecting `results`. Additionally, can take the
 
         if cache_results is None:
             cache_results = {}
@@ -2492,11 +2490,11 @@ def _test_trusted(self, trajectory, function, results,
                 end = len(trajectory)
                 start = end - (i+start_traj_len)
             # test untrusted
-            assert_equal(function(trajectory[start:end]), results[i])
+            assert function(trajectory[start:end]) == results[i]
             # test trusted
             trusted_val = function(trajectory[start:end], trusted=True)
             # print i, "["+str(start)+":"+str(end)+"]", trusted_val, results[i]
-            assert_equal(trusted_val, results[i])
+            assert trusted_val == results[i]
             for cache in list(cache_results.keys()):
                 # TODO: this is currently very specific to the caches used
                 # by volumes ensembles. That should be generalized by
@@ -2510,8 +2508,8 @@ def _test_trusted(self, trajectory, function, results,
                         contents = None
                     #print contents, cache_results[cache][i]
 
-                    assert_equal(cache.contents['previous'],
-                                 cache_results[cache][i])
+                    assert (cache.contents['previous']
+                            == cache_results[cache][i])
 
     def test_call_outA_or_outB(self):
         # print self.local_ttraj['upper_out_in_out_out_cross'].xyz[:,0,0]
@@ -2577,8 +2575,8 @@ def test_call_outA_and_outB(self):
     def test_can_append_outA_or_outB(self):
         self._test_trusted(
             trajectory=self.local_ttraj['upper_out_in_out_out_cross'],
-            function=self.outA_or_outB.can_append, 
-            results=[True, True, True, True, False], 
+            function=self.outA_or_outB.can_append,
+            results=[True, True, True, True, False],
             cache_results={
                 self.outA._cache_can_append : [True, False, False, False, False],
                 self.outB._cache_can_append : [None, True, True, True, False]
@@ -2722,8 +2720,8 @@ class TestEnsembleEquality(object):
     def test_empty_ensemble_equality(self):
         ens1 = paths.EmptyEnsemble()
         ens2 = paths.EmptyEnsemble()
-        assert_true(ens1 == ens2)
-        assert_false(ens1 != ens2)
+        assert ens1 == ens2
+        assert not ens1 != ens2  # needed __ne__ tested in the Py27 days
 
     # TODO: may add tests for other ensembles, or may move this test
     # somewhere else
diff --git a/openpathsampling/tests/test_external_engine.py b/openpathsampling/tests/test_external_engine.py
index b163bc3da..a294374db 100644
--- a/openpathsampling/tests/test_external_engine.py
+++ b/openpathsampling/tests/test_external_engine.py
@@ -92,7 +92,7 @@ def teardown_module():
 
 
 class TestExternalEngine(object):
-    def setup(self):
+    def setup_method(self):
         self.descriptor = SnapshotDescriptor.construct(
             snapshot_class=ToySnapshot,
             snapshot_dimensions={'n_spatial': 1,
diff --git a/openpathsampling/tests/test_external_snapshots.py b/openpathsampling/tests/test_external_snapshots.py
index 8d63304ab..729048d97 100644
--- a/openpathsampling/tests/test_external_snapshots.py
+++ b/openpathsampling/tests/test_external_snapshots.py
@@ -45,7 +45,7 @@ def read_frame_data(self, filename, position):
 
 
 class TestExternalMDSnapshot(object):
-    def setup(self):
+    def setup_method(self):
         self.box = np.array([[1.0, 0.0], [0.0, 1.0]])
         self.vel = np.array([[1.0, 0.0]])
 
diff --git a/openpathsampling/tests/test_features.py b/openpathsampling/tests/test_features.py
index 6c833a7e6..59a52ad54 100644
--- a/openpathsampling/tests/test_features.py
+++ b/openpathsampling/tests/test_features.py
@@ -1,9 +1,8 @@
 from __future__ import absolute_import
 from builtins import hex
 from builtins import object
-from nose.tools import raises
 
-from nose.plugins.skip import SkipTest
+import pytest
 from .test_helpers import u
 
 import logging
@@ -42,7 +41,7 @@ def test_copy_with_replacement_toy(self):
 
     def test_copy_with_replacement_openmm(self):
         if not paths.integration_tools.HAS_OPENMM or omt is None:
-            raise SkipTest
+            pytest.skip()
         # test an openmm snapshot
         sys = omt.testsystems.AlanineDipeptideVacuum()
         omm_snap = omm_engine.snapshot_from_testsystem(sys)
@@ -63,10 +62,10 @@ def test_copy_with_replacement_openmm(self):
         assert(hex(id(omm_snap.kinetics)) == hex(id(omm_copy.kinetics)))
         assert(hex(id(omm_snap.statics)) != hex(id(omm_copy.statics)))
 
-    @raises(TypeError)
     def test_parameter_error(self):
         init_coord = np.array([1.0, 2.0])
         init_vel = np.array([3.0, 4.0])
         toy_snap = toy_engine.Snapshot(
             coordinates=init_coord, velocities=init_vel)
-        toy_snap.copy_with_replacement(dummy=0)
+        with pytest.raises(TypeError):
+            toy_snap.copy_with_replacement(dummy=0)
diff --git a/openpathsampling/tests/test_gromacs_engine.py b/openpathsampling/tests/test_gromacs_engine.py
index 66535365f..a1282278a 100644
--- a/openpathsampling/tests/test_gromacs_engine.py
+++ b/openpathsampling/tests/test_gromacs_engine.py
@@ -1,9 +1,5 @@
 import pytest
 import numpy.testing as npt
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises, assert_true)
-from nose.plugins.skip import Skip, SkipTest
-import numpy.testing as npt
 import tempfile
 
 from .test_helpers import data_filename, assert_items_equal
@@ -44,7 +40,7 @@
 
 
 class TestGroFileEngine(object):
-    def setup(self):
+    def setup_method(self):
         if not HAS_MDTRAJ:
             pytest.skip("MDTraj not installed.")
         self.gro = os.path.join(data_filename("gromacs_engine"), "conf.gro")
@@ -83,18 +79,21 @@ class TestGromacsEngine(object):
     # conf.gro, md.mdp, topol.top : standard Gromacs input files
     # project_trr/0000000.trr : working file, 4 frames
     # project_trr/0000099.trr : 49 working frames, final frame partial
-    def setup(self):
+    def setup_method(self):
         if not HAS_MDTRAJ:
             pytest.skip("MDTraj not installed.")
         self.test_dir = data_filename("gromacs_engine")
         self.engine = Engine(gro="conf.gro",
                              mdp="md.mdp",
                              top="topol.top",
-                             options={'mdrun_args': '-nt 1'},
+                             options={
+                                 'mdrun_args': '-nt 1',
+                                 'grompp_args': '-maxwarn 2',  # Berendsen
+                             },
                              base_dir=self.test_dir,
                              prefix="project")
 
-    def teardown(self):
+    def teardown_method(self):
         files = ['topol.tpr', 'mdout.mdp', 'initial_frame.trr', 'state.cpt',
                  'state_prev.cpt', 'traj_comp.xtc',
                  self.engine.trajectory_filename(1)]
@@ -110,30 +109,30 @@ def test_read_frame_from_file_success(self):
         # when the frame is present, we should return it
         fname = os.path.join(self.test_dir, "project_trr", "0000000.trr")
         result = self.engine.read_frame_from_file(fname, 0)
-        assert_true(isinstance(result, ExternalMDSnapshot))
-        assert_equal(result.file_name, fname)
-        assert_equal(result.file_position, 0)
+        assert isinstance(result, ExternalMDSnapshot)
+        assert result.file_name == fname
+        assert result.file_position == 0
         # TODO: add caching of xyz, vel, box; check that we have it now
 
         fname = os.path.join(self.test_dir, "project_trr", "0000000.trr")
         result = self.engine.read_frame_from_file(fname, 3)
-        assert_true(isinstance(result, ExternalMDSnapshot))
-        assert_equal(result.file_name, fname)
-        assert_equal(result.file_position, 3)
+        assert isinstance(result, ExternalMDSnapshot)
+        assert result.file_name == fname
+        assert result.file_position == 3
 
     def test_read_frame_from_file_partial(self):
         # if a frame is partial, return 'partial'
         fname = os.path.join(self.test_dir, "project_trr", "0000099.trr")
         frame_2 = self.engine.read_frame_from_file(fname, 49)
-        assert_true(isinstance(frame_2, ExternalMDSnapshot))
+        assert isinstance(frame_2, ExternalMDSnapshot)
         frame_3 = self.engine.read_frame_from_file(fname, 50)
-        assert_equal(frame_3, "partial")
+        assert frame_3 == "partial"
 
     def test_read_frame_from_file_none(self):
         # if a frame is beyond the last frame, return None
         fname = os.path.join(self.test_dir, "project_trr", "0000000.trr")
         result = self.engine.read_frame_from_file(fname, 4)
-        assert_equal(result, None)
+        assert result is None
 
     def test_write_frame_to_file_read_back(self):
         # write random frame; read back
@@ -155,8 +154,8 @@ def test_write_frame_to_file_read_back(self):
         self.engine.write_frame_to_file(traj_50, snap)
 
         snap2 = self.engine.read_frame_from_file(traj_50, 0)
-        assert_equal(snap2.file_name, traj_50)
-        assert_equal(snap2.file_position, 0)
+        assert snap2.file_name == traj_50
+        assert snap2.file_position == 0
         npt.assert_array_almost_equal(snap.xyz, snap2.xyz)
         npt.assert_array_almost_equal(snap.velocities, snap2.velocities)
         npt.assert_array_almost_equal(snap.box_vectors, snap2.box_vectors)
@@ -173,20 +172,25 @@ def test_set_filenames_int(self):
                 os.path.join(self.test_dir, "initial_frame.trr")
         assert test_engine.output_file == \
                 os.path.join(self.test_dir, "proj_trr", "0000001.trr")
-        assert_equal(test_engine.edr_file,
-                     os.path.join(self.test_dir, "proj_edr", "0000001.edr"))
-        assert_equal(test_engine.log_file,
-                     os.path.join(self.test_dir, "proj_log", "0000001.log"))
+        assert test_engine.edr_file == os.path.join(self.test_dir,
+                                                    "proj_edr",
+                                                    "0000001.edr")
+        assert test_engine.log_file == os.path.join(self.test_dir,
+                                                    "proj_log",
+                                                    "0000001.log")
 
         test_engine.set_filenames(99)
-        assert_equal(test_engine.input_file,
-                     os.path.join(self.test_dir, "initial_frame.trr"))
-        assert_equal(test_engine.output_file,
-                     os.path.join(self.test_dir, "proj_trr", "0000100.trr"))
-        assert_equal(test_engine.edr_file,
-                     os.path.join(self.test_dir, "proj_edr", "0000100.edr"))
-        assert_equal(test_engine.log_file,
-                     os.path.join(self.test_dir, "proj_log", "0000100.log"))
+        assert test_engine.input_file == os.path.join(self.test_dir,
+                                                      "initial_frame.trr")
+        assert test_engine.output_file == os.path.join(self.test_dir,
+                                                       "proj_trr",
+                                                       "0000100.trr")
+        assert test_engine.edr_file == os.path.join(self.test_dir,
+                                                    "proj_edr",
+                                                    "0000100.edr")
+        assert test_engine.log_file == os.path.join(self.test_dir,
+                                                    "proj_log",
+                                                    "0000100.log")
 
     def test_set_filenames_fixed(self):
         test_engine = Engine(gro="conf.gro", mdp="md.mdp", top="topol.top",
@@ -220,7 +224,7 @@ def test_engine_command(self):
 
     def test_generate(self):
         if not has_gmx:
-            raise SkipTest("Gromacs 5 (gmx) not found. Skipping test.")
+            pytest.skip("gmx not found. Skipping test.")
 
         if not HAS_MDTRAJ:
             pytest.skip("MDTraj not found. Skipping test.")
@@ -231,16 +235,16 @@ def test_generate(self):
 
         ens = paths.LengthEnsemble(5)
         traj = self.engine.generate(snap, running=[ens.can_append])
-        assert_equal(self.engine.proc.is_running(), False)
-        assert_equal(len(traj), 5)
+        assert self.engine.proc.is_running() is False
+        assert len(traj) == 5
         ttraj = md.load(self.engine.trajectory_filename(1),
                         top=self.engine.gro)
         # the mdp suggests a max length of 100 frames
-        assert_true(len(ttraj) < 100)
+        assert len(ttraj) < 100
 
     def test_prepare(self):
         if not has_gmx:
-            raise SkipTest("Gromacs 5 (gmx) not found. Skipping test.")
+            pytest.skip("gmx not found. Skipping test.")
         self.engine.set_filenames(0)
         traj_0 = self.engine.trajectory_filename(0)
         snap = self.engine.read_frame_from_file(traj_0, 0)
@@ -250,7 +254,7 @@ def test_prepare(self):
             if os.path.isfile(f):
                 raise AssertionError("File " + str(f) + " already exists!")
 
-        assert_equal(self.engine.prepare(), 0)
+        assert self.engine.prepare() == 0
         for f in files:
             if not os.path.isfile(f):
                 raise AssertionError("File " + str(f) + " was not created!")
@@ -270,7 +274,7 @@ def test_iter_generate_clear_cache(self):
         # snapshot should contain data -- the others should have their cache
         # cleared
         if not has_gmx:
-            pytest.skip("Gromacs (gmx) not found. Skipping test.")
+            pytest.skip("gmx not found. Skipping test.")
         if not HAS_MDTRAJ:
             pytest.skip("MDTraj not found. Skipping test.")
 
@@ -316,7 +320,7 @@ def test_serialization_cycle(self):
 
 
 class TestGromacsExternalMDSnapshot(object):
-    def setup(self):
+    def setup_method(self):
         if not HAS_MDTRAJ:
             pytest.skip("MDTraj not installed.")
 
@@ -336,7 +340,7 @@ def setup(self):
         self.snapshot_shape = (1651, 3)
         self.storage_filename = "gmx_snap.nc"
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.storage_filename):
             os.remove(self.storage_filename)
 
diff --git a/openpathsampling/tests/test_helpers.py b/openpathsampling/tests/test_helpers.py
index 198a9a9f4..3ed2ed199 100644
--- a/openpathsampling/tests/test_helpers.py
+++ b/openpathsampling/tests/test_helpers.py
@@ -278,7 +278,10 @@ def generate_next_frame(self):
 
 def raises_with_message_like(err, message=None):
     """
-    Decorator that allows to run nosetests with raises and testing if the message starts with a txt.
+    Decorator that allows to run tests with raises and testing if the
+    message starts with a txt.
+
+    TODO: this should be deprecated in favor of pytest functionality
 
     Notes
     -----
diff --git a/openpathsampling/tests/test_histogram.py b/openpathsampling/tests/test_histogram.py
index 0823c4a96..5311d3a46 100644
--- a/openpathsampling/tests/test_histogram.py
+++ b/openpathsampling/tests/test_histogram.py
@@ -45,7 +45,7 @@ def test_histograms_to_pandas_dataframe(self):
 
 
 class TestHistogram(object):
-    def setup(self):
+    def setup_method(self):
         self.data = [1.0, 1.1, 1.2, 1.3, 2.0, 1.4, 2.3, 2.5, 3.1, 3.5]
         self.nbins = 5
         self.bins = [1.0, 1.5, 2.0, 2.5, 3.0, 3.5]
@@ -189,7 +189,7 @@ def test_left_bin_error(self):
 
 
 class TestSparseHistogram(object):
-    def setup(self):
+    def setup_method(self):
         data = [(0.0, 0.1), (0.2, 0.7), (0.3, 0.6), (0.6, 0.9)]
         self.histo = SparseHistogram(bin_widths=(0.5, 0.3),
                                      left_bin_edges=(0.0, -0.1))
@@ -232,7 +232,7 @@ def test_mangled_input(self):
 
 
 class TestHistogramPlotter2D(object):
-    def setup(self):
+    def setup_method(self):
         data = [(0.0, 0.1), (0.2, 0.7), (0.3, 0.6), (0.6, 0.9)]
         histo = SparseHistogram(bin_widths=(0.5, 0.3),
                                 left_bin_edges=(0.0, -0.1))
diff --git a/openpathsampling/tests/test_interface_set.py b/openpathsampling/tests/test_interface_set.py
index a0b932777..d0f684da8 100644
--- a/openpathsampling/tests/test_interface_set.py
+++ b/openpathsampling/tests/test_interface_set.py
@@ -2,9 +2,8 @@
 from builtins import zip
 from builtins import range
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises)
-from nose.plugins.skip import Skip, SkipTest
+
+import pytest
 from .test_helpers import (
     true_func, assert_equal_array_array, make_1d_traj, data_filename,
     assert_items_equal
@@ -20,7 +19,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestInterfaceSet(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV(name="x", f=lambda s: s.xyz[0][0])
         self.lambdas = [0.0, 0.1, 0.2, 0.3]
@@ -35,37 +34,37 @@ def setup(self):
         self.no_lambda_set = paths.InterfaceSet(self.volumes, self.cv)
 
     def test_direction(self):
-        assert_equal(self.interface_set.direction, 1)
-        assert_equal(self.no_lambda_set.direction, 0)
-        assert_equal(self.decreasing.direction, -1)
+        assert self.interface_set.direction == 1
+        assert self.no_lambda_set.direction == 0
+        assert self.decreasing.direction == -1
 
     def test_get_lambda(self):
         for (v, l) in zip(self.volumes, self.lambdas):
-            assert_equal(self.interface_set.get_lambda(v), l)
-            assert_equal(self.no_lambda_set.get_lambda(v), None)
+            assert self.interface_set.get_lambda(v) == l
+            assert self.no_lambda_set.get_lambda(v) is None
 
     def test_list_behavior(self):
         # len
-        assert_equal(len(self.interface_set), 4)
-        assert_equal(len(self.no_lambda_set), 4)
+        assert len(self.interface_set) == 4
+        assert len(self.no_lambda_set) == 4
         # getitem, contains
         for i in range(4):
-            assert_equal(self.volumes[i], self.interface_set[i])
-            assert_equal(self.volumes[i] in self.interface_set, True)
+            assert self.volumes[i] == self.interface_set[i]
+            assert self.volumes[i] in self.interface_set
         # getitem for slices
         sliced = self.interface_set[0:2]
         for vol in sliced:
-            assert_equal(sliced.get_lambda(vol),
-                         self.interface_set.get_lambda(vol))
+            assert (sliced.get_lambda(vol)
+                    == self.interface_set.get_lambda(vol))
         # special case of -1 needs to work (used frequently!)
-        assert_equal(self.volumes[-1], self.interface_set[-1])
+        assert self.volumes[-1] == self.interface_set[-1]
         # iter
         for vol in self.interface_set:
-            assert_equal(vol in self.volumes, True)
+            assert vol in self.volumes
         # reversed
         i = 0
         for vol in reversed(self.interface_set):
-            assert_equal(vol, self.volumes[3-i])
+            assert vol == self.volumes[3-i]
             i += 1
 
     def test_no_direction_possible(self):
@@ -74,37 +73,37 @@ def test_no_direction_possible(self):
         volumes = [paths.CVDefinedVolume(self.cv, min_v, max_v)
                    for min_v, max_v in zip(min_vals, max_vals)]
         ifaces = paths.InterfaceSet(volumes)
-        assert_equal(ifaces.cv, None)
-        assert_equal(ifaces.cv_max, None)
-        assert_equal(ifaces.direction, 0)
+        assert ifaces.cv is None
+        assert ifaces.cv_max is None
+        assert ifaces.direction == 0
 
 
 class TestGenericVolumeInterfaceSet(object):
     def test_sanitize_input(self):
         # this is just to make the rest a little more readable
         sanitize = GenericVolumeInterfaceSet._sanitize_input
-        assert_equal(([float("-inf")]*3, [0.0, 0.1, 0.2], 1),
-                     sanitize(float("-inf"), [0.0, 0.1, 0.2]))
-        assert_equal(([0.2, 0.1, 0.0], [float("inf")]*3, -1),
-                     sanitize([0.2, 0.1, 0.0], float("inf")))
-        assert_equal(([-0.1, -0.2], [0.1, 0.2], 0),
-                     sanitize([-0.1, -0.2], [0.1, 0.2]))
-        assert_equal(([0.0, 0.0], [0.1, 0.2], 1),
-                     sanitize([0.0, 0.0], [0.1, 0.2]))
-        assert_equal(([-0.1, -0.2], [0.0, 0.0], -1),
-                     sanitize([-0.1, -0.2], [0.0, 0.0]))
+        assert (([float("-inf")]*3, [0.0, 0.1, 0.2], 1)
+                 == sanitize(float("-inf"), [0.0, 0.1, 0.2]))
+        assert (([0.2, 0.1, 0.0], [float("inf")]*3, -1)
+                 == sanitize([0.2, 0.1, 0.0], float("inf")))
+        assert (([-0.1, -0.2], [0.1, 0.2], 0)
+                 == sanitize([-0.1, -0.2], [0.1, 0.2]))
+        assert (([0.0, 0.0], [0.1, 0.2], 1)
+                 == sanitize([0.0, 0.0], [0.1, 0.2]))
+        assert (([-0.1, -0.2], [0.0, 0.0], -1)
+                 == sanitize([-0.1, -0.2], [0.0, 0.0]))
         # and the idiot case:
-        assert_equal(([-0.1, -0.1], [0.1, 0.1], 0),
-                     sanitize([-0.1, -0.1], [0.1, 0.1]))
+        assert (([-0.1, -0.1], [0.1, 0.1], 0)
+                 == sanitize([-0.1, -0.1], [0.1, 0.1]))
 
-    @raises(RuntimeError)
     def test_bad_sanitize(self):
-        GenericVolumeInterfaceSet._sanitize_input([0.0, -0.1],
-                                                  [0.1, 0.2, 0.3])
+        with pytest.raises(RuntimeError):
+            GenericVolumeInterfaceSet._sanitize_input([0.0, -0.1],
+                                                      [0.1, 0.2, 0.3])
 
 
 class TestVolumeInterfaceSet(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV(name="x", f=lambda s: s.xyz[0][0])
         self.increasing_set = paths.VolumeInterfaceSet(cv=self.cv,
@@ -118,31 +117,31 @@ def setup(self):
                                                   maxvals=[0.1, 0.2])
 
     def test_initialization(self):
-        assert_equal(len(paths.InterfaceSet._cv_max_dict), 1)
+        assert len(paths.InterfaceSet._cv_max_dict) == 1
         cv_max = list(paths.InterfaceSet._cv_max_dict.values())[0]
 
-        assert_equal(len(self.increasing_set), 2)
-        assert_equal(self.increasing_set.direction, 1)
-        assert_equal(self.increasing_set.lambdas, [0.0, 0.1])
-        assert_equal(self.increasing_set.cv_max, cv_max)
+        assert len(self.increasing_set) == 2
+        assert self.increasing_set.direction == 1
+        assert self.increasing_set.lambdas == [0.0, 0.1]
+        assert self.increasing_set.cv_max == cv_max
 
-        assert_equal(len(self.decreasing_set), 2)
-        assert_equal(self.decreasing_set.direction, -1)
-        assert_equal(self.decreasing_set.lambdas, [0.0, -0.1])
+        assert len(self.decreasing_set) == 2
+        assert self.decreasing_set.direction == -1
+        assert self.decreasing_set.lambdas == [0.0, -0.1]
         # TODO: decide what to do about cv_max for decreasing/weird
 
-        assert_equal(len(self.weird_set), 2)
-        assert_equal(self.weird_set.direction, 0)
-        assert_equal(self.weird_set.lambdas, None)
+        assert len(self.weird_set) == 2
+        assert self.weird_set.direction == 0
+        assert self.weird_set.lambdas is None
 
     def test_new_interface(self):
         new_iface = self.increasing_set.new_interface(0.25)
         expected = paths.CVDefinedVolume(self.cv, float("-inf"), 0.25)
-        assert_equal(expected, new_iface)
+        assert expected == new_iface
 
-    @raises(TypeError)
     def test_bad_new_interface(self):
-        self.weird_set.new_interface(0.25)
+        with pytest.raises(TypeError):
+            self.weird_set.new_interface(0.25)
 
     def test_storage(self):
         import os
@@ -161,17 +160,17 @@ def test_storage(self):
 
         assert_items_equal(reloaded.lambdas, self.increasing_set.lambdas)
         for (truth, beauty) in zip(self.increasing_set, reloaded):
-            assert_equal(truth, beauty)
+            assert truth == beauty
 
         for (v, l) in zip(reloaded.volumes, reloaded.lambdas):
-            assert_equal(reloaded.get_lambda(v), l)
+            assert reloaded.get_lambda(v) == l
 
         if os.path.isfile(fname):
             os.remove(fname)
 
 
 class TestPeriodicVolumeInterfaceSet(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV(name="x", f=lambda s: s.xyz[0][0])
         self.increasing_set = paths.PeriodicVolumeInterfaceSet(
@@ -183,14 +182,14 @@ def setup(self):
         )
 
     def test_initialization(self):
-        assert_equal(self.increasing_set.direction, 1)
-        assert_equal(len(self.increasing_set), 3)
-        assert_equal(self.increasing_set.lambdas, [100, 150, -160])
+        assert self.increasing_set.direction == 1
+        assert len(self.increasing_set) == 3
+        assert self.increasing_set.lambdas == [100, 150, -160]
 
     def test_new_interface(self):
         new_iface = self.increasing_set.new_interface(-140)
         expected = paths.PeriodicCVDefinedVolume(self.cv, 0.0, -140, -180, 180)
-        assert_equal(new_iface, expected)
+        assert new_iface == expected
 
     def test_storage(self):
         import os
@@ -208,13 +207,13 @@ def test_storage(self):
         reloaded = storage_r.interfacesets[0]
 
         assert_items_equal(reloaded.lambdas, self.increasing_set.lambdas)
-        assert_equal(reloaded.period_min, self.increasing_set.period_min)
-        assert_equal(reloaded.period_max, self.increasing_set.period_max)
+        assert reloaded.period_min == self.increasing_set.period_min
+        assert reloaded.period_max == self.increasing_set.period_max
         for (truth, beauty) in zip(self.increasing_set, reloaded):
-            assert_equal(truth, beauty)
+            assert truth == beauty
 
         for (v, l) in zip(reloaded.volumes, reloaded.lambdas):
-            assert_equal(reloaded.get_lambda(v), l)
+            assert reloaded.get_lambda(v) == l
 
         storage_r.close()
         storage_w.close()
diff --git a/openpathsampling/tests/test_lookup_function.py b/openpathsampling/tests/test_lookup_function.py
index 40b3aef50..51266ad1e 100644
--- a/openpathsampling/tests/test_lookup_function.py
+++ b/openpathsampling/tests/test_lookup_function.py
@@ -1,11 +1,10 @@
 from __future__ import absolute_import
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises, assert_in)
-from nose.plugins.skip import SkipTest
 from numpy import isnan
+from numpy.testing import assert_almost_equal
 from .test_helpers import assert_items_almost_equal, assert_items_equal
 import collections
+import pytest
 
 import logging
 
@@ -14,7 +13,7 @@
 )
 
 class TestLookupFunctionGroup(object):
-    def setup(self):
+    def setup_method(self):
         x1 = [0.0, 1.0, 2.0, 3.0, 4.0]
         y1 = [0.1, 1.3, 1.8, 3.3, 4.2]
         luf1 = LookupFunction(x1, y1)
@@ -75,7 +74,7 @@ def test_append(self):
 
 
 class TestVoxelLookupFunction(object):
-    def setup(self):
+    def setup_method(self):
         counter = collections.Counter({(0,0): 1.0, (0,2): 2.0, (1,4): 4.0,
                                        (-1,-1): 5.0})
         self.lookup = VoxelLookupFunction(left_bin_edges=(-1.0, 1.0),
@@ -84,15 +83,15 @@ def setup(self):
 
     def test_keys(self):
         keys = [(0,0), (0,2), (1,4), (-1,-1)]
-        assert_equal(len(list(self.lookup.keys())), len(keys))
+        assert len(list(self.lookup.keys())) == len(keys)
         for key in list(self.lookup.keys()):
-            assert_in(key, keys)
+            assert key in keys
 
     def test_values(self):
         values = [1.0, 2.0, 4.0, 5.0]
-        assert_equal(len(list(self.lookup.values())), len(values))
+        assert len(list(self.lookup.values())) == len(values)
         for val in list(self.lookup.values()):
-            assert_in(val, values)
+            assert val in values
 
     def test_bin_to_left_edge(self):
         assert_items_equal(self.lookup.bin_to_left_edge((0,0)), (-1.0, 1.0))
@@ -100,41 +99,41 @@ def test_bin_to_left_edge(self):
 
     def test_val_to_bin(self):
         result_bin = self.lookup.val_to_bin((-0.25, 2.0))
-        assert_equal(len(result_bin), 2)
+        assert len(result_bin) == 2
         assert_items_equal(result_bin, [1.5, 4.0])
 
     def test_counter_by_bin_edges(self):
         bin_edge_counter = self.lookup.counter_by_bin_edges
         expected = {(-1.0, 1.0): 1.0, (-1.0,1.5): 2.0, (-0.5, 2.0): 4.0,
                     (-1.5, 0.75): 5.0}
-        assert_equal(len(expected), len(bin_edge_counter))
+        assert len(expected) == len(bin_edge_counter)
         for k in list(expected.keys()):
-            assert_equal(bin_edge_counter[k], expected[k])
+            assert bin_edge_counter[k] == expected[k]
 
     def test_df_2d(self):
         df1 = self.lookup.df_2d()
         assert_items_equal(df1.index, [-1, 0, 1])
         assert_items_equal(df1.columns, [-1, 0, 2, 4])
-        assert_equal(df1.at[-1, -1], 5.0)
-        assert_equal(isnan(df1.at[0, 4]), True)
+        assert df1.at[-1, -1] == 5.0
+        assert isnan(df1.at[0, 4])
 
         df2 = self.lookup.df_2d(x_range=(-1, 3), y_range=(-1, 4))
         assert_items_equal(df2.index, [-1, 0, 1, 2, 3])
         assert_items_equal(df2.columns, [-1, 0, 1, 2, 3, 4])
-        assert_equal(df2.at[-1, -1], 5.0)
-        assert_equal(isnan(df2.at[0, 4]), True)
-        assert_equal(isnan(df2.at[2, 3]), True)
+        assert df2.at[-1, -1] == 5.0
+        assert isnan(df2.at[0, 4])
+        assert isnan(df2.at[2, 3])
 
-    @raises(RuntimeError)
     def test_df_2d_not_2d(self):
         counter = collections.Counter({(0,0,0): 1.0})
         luf = VoxelLookupFunction(left_bin_edges=(0,0,0),
                                   bin_widths=(1,1,1),
                                   counter=counter)
-        luf.df_2d()
+        with pytest.raises(RuntimeError):
+            luf.df_2d()
 
     def test_call(self):
-        assert_equal(self.lookup((0.0, 0.0)), 0.0)   # no bin
-        assert_equal(self.lookup((-0.5, 2.0)), 4.0)  # bin edge
-        assert_equal(self.lookup((-0.4, 2.1)), 4.0)  # in bin
+        assert self.lookup((0.0, 0.0)) == 0.0   # no bin
+        assert self.lookup((-0.5, 2.0)) == 4.0  # bin edge
+        assert self.lookup((-0.4, 2.1)) == 4.0  # in bin
 
diff --git a/openpathsampling/tests/test_mdtraj_support.py b/openpathsampling/tests/test_mdtraj_support.py
index d2ca421ac..8a14571eb 100644
--- a/openpathsampling/tests/test_mdtraj_support.py
+++ b/openpathsampling/tests/test_mdtraj_support.py
@@ -4,10 +4,6 @@
 
 import pytest
 
-from nose.tools import (
-    assert_equal, assert_not_equal, raises
-)
-from nose.plugins.skip import SkipTest
 import numpy.testing as nptest
 from .test_helpers import data_filename, assert_items_equal, md, u
 
@@ -27,9 +23,9 @@
 # add tests for topology conversions as well
 
 class TestMDTrajSupport(object):
-    def setup(self):
+    def setup_method(self):
         if not md:
-            raise SkipTest("mdtraj not installed")
+            pytest.skip("mdtraj not installed")
         self.md_trajectory = md.load(data_filename("ala_small_traj.pdb"))
         _ = pytest.importorskip("simtk.unit")
         self.ops_trajectory = trajectory_from_mdtraj(self.md_trajectory)
@@ -49,16 +45,16 @@ def test_trajectory_mdtraj_round_trip(self):
         nptest.assert_allclose(self.md_trajectory.unitcell_vectors,
                                md_trajectory_2.unitcell_vectors)
 
-    @raises(ValueError)
     def test_empty_traj_to_mdtraj(self):
         empty = paths.Trajectory([])
-        empty.to_mdtraj()
+        with pytest.raises(ValueError):
+            empty.to_mdtraj()
 
     def test_trajectory_to_mdtraj_other_input(self):
         snap = self.ops_trajectory[0]
         md1 = trajectory_to_mdtraj(snap)
         md2 = trajectory_to_mdtraj([snap])
-        assert_equal(md1, md2)
+        assert md1 == md2
 
     def test_ops_load_trajectory_pdb(self):
         pdb_file = data_filename("ala_small_traj.pdb")
diff --git a/openpathsampling/tests/test_movescheme.py b/openpathsampling/tests/test_movescheme.py
index b72fe0a46..5663cfe35 100644
--- a/openpathsampling/tests/test_movescheme.py
+++ b/openpathsampling/tests/test_movescheme.py
@@ -5,10 +5,7 @@
 from builtins import range
 from builtins import object
 from past.utils import old_div
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        assert_in, raises, assert_is, assert_is_not,
-                        assert_true)
-from nose.plugins.skip import Skip, SkipTest
+from numpy.testing import assert_almost_equal
 from .test_helpers import (true_func, assert_equal_array_array,
                            make_1d_traj, assert_items_equal)
 
@@ -92,7 +89,7 @@ def _make_null_mover_step(mccycle, path_sim_mover, null_mover):
 
 
 class TestMoveAcceptanceAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         paths.InterfaceSet._reset()
@@ -146,7 +143,7 @@ def setup(self):
                          'normal': acceptance,
                          'with_null': acceptance_null}
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
     @pytest.mark.parametrize('step_num', [0, 1, 2, 3, 4])
@@ -391,7 +388,7 @@ def test_format_as_text(self, simulation):
 
 
 class TestMoveScheme(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s : -s.xyz[0][0])
@@ -414,69 +411,69 @@ def test_append_individuals_default_levels(self):
         shootstrat = OneWayShootingStrategy()
         repexstrat = NearestNeighborRepExStrategy()
         defaultstrat = OrganizeByMoveGroupStrategy()
-        assert_equal(len(list(self.scheme.strategies.keys())), 0)
+        assert len(list(self.scheme.strategies.keys())) == 0
         self.scheme.append(shootstrat)
         self.scheme.append(repexstrat)
         self.scheme.append(defaultstrat)
 
         strats = self.scheme.strategies
-        assert_equal(len(list(strats.keys())), 3)
-        pairs = [(levels.MOVER, shootstrat), (levels.SIGNATURE, repexstrat), 
+        assert len(list(strats.keys())) == 3
+        pairs = [(levels.MOVER, shootstrat), (levels.SIGNATURE, repexstrat),
                  (levels.GLOBAL, defaultstrat)]
         for (k, v) in pairs:
-            assert_in(v, strats[k])
+            assert v in strats[k]
 
     def test_append_groups_default_levels(self):
         shootstrat = OneWayShootingStrategy()
         repexstrat = NearestNeighborRepExStrategy()
         defaultstrat = OrganizeByMoveGroupStrategy()
-        assert_equal(len(list(self.scheme.strategies.keys())), 0)
+        assert len(list(self.scheme.strategies.keys())) == 0
         self.scheme.append([shootstrat, repexstrat, defaultstrat])
 
         strats = self.scheme.strategies
-        assert_equal(len(list(strats.keys())), 3)
-        pairs = [(levels.MOVER, shootstrat), (levels.SIGNATURE, repexstrat), 
+        assert len(list(strats.keys())) == 3
+        pairs = [(levels.MOVER, shootstrat), (levels.SIGNATURE, repexstrat),
                  (levels.GLOBAL, defaultstrat)]
         for (k, v) in pairs:
-            assert_in(v, strats[k])
+            assert v in strats[k]
 
 
     def test_append_individuals_custom_levels(self):
         shootstrat = OneWayShootingStrategy()
         repexstrat = NearestNeighborRepExStrategy()
         defaultstrat = OrganizeByMoveGroupStrategy()
-        assert_equal(len(list(self.scheme.strategies.keys())), 0)
+        assert len(list(self.scheme.strategies.keys())) == 0
         self.scheme.append(shootstrat, 60)
         self.scheme.append(repexstrat, 60)
         self.scheme.append(defaultstrat, 60)
 
         strats = self.scheme.strategies
-        assert_equal(len(list(strats.keys())), 1)
+        assert len(list(strats.keys())) == 1
         assert_items_equal(strats[60], [shootstrat, repexstrat, defaultstrat])
 
     def test_append_groups_same_custom_level(self):
         shootstrat = OneWayShootingStrategy()
         repexstrat = NearestNeighborRepExStrategy()
         defaultstrat = OrganizeByMoveGroupStrategy()
-        assert_equal(len(list(self.scheme.strategies.keys())), 0)
+        assert len(list(self.scheme.strategies.keys())) == 0
         self.scheme.append([shootstrat, repexstrat, defaultstrat], 60)
 
         strats = self.scheme.strategies
-        assert_equal(len(list(strats.keys())), 1)
+        assert len(list(strats.keys())) == 1
         assert_items_equal(strats[60], [shootstrat, repexstrat, defaultstrat])
 
     def test_append_group_different_custom_levels(self):
         shootstrat = OneWayShootingStrategy()
         repexstrat = NearestNeighborRepExStrategy()
         defaultstrat = OrganizeByMoveGroupStrategy()
-        assert_equal(len(list(self.scheme.strategies.keys())), 0)
-        self.scheme.append([shootstrat, repexstrat, defaultstrat], 
+        assert len(list(self.scheme.strategies.keys())) == 0
+        self.scheme.append([shootstrat, repexstrat, defaultstrat],
                            [45, 55, 65])
 
         strats = self.scheme.strategies
-        assert_equal(len(list(strats.keys())), 3)
+        assert len(list(strats.keys())) == 3
         for (k, v) in [(45, shootstrat), (55, repexstrat), (65, defaultstrat)]:
-            assert_in(v, strats[k])
+            assert v in strats[k]
 
     def test_apply_strategy(self):
         if self.scheme.movers == {}:
@@ -500,36 +497,36 @@ def test_apply_strategy(self):
 
         self.scheme.apply_strategy(shoot_strat_1)
         assert_items_equal(list(self.scheme.movers.keys()), ['shooting'])
-        assert_equal(len(self.scheme.movers['shooting']), 3)
+        assert len(self.scheme.movers['shooting']) == 3
 
         self.scheme.apply_strategy(shoot_strat_2)
         assert_items_equal(list(self.scheme.movers.keys()), ['shooting'])
-        assert_equal(len(self.scheme.movers['shooting']), 7)
+        assert len(self.scheme.movers['shooting']) == 7
         old_movers = copy.copy(self.scheme.movers['shooting'])
 
         self.scheme.apply_strategy(shoot_strat_3)
         assert_items_equal(list(self.scheme.movers.keys()), ['shooting'])
-        assert_equal(len(self.scheme.movers['shooting']), 7)
+        assert len(self.scheme.movers['shooting']) == 7
         new_movers = self.scheme.movers['shooting']
         for (o, n) in zip(old_movers, new_movers):
-            assert_equal(o is n, False)
+            assert o is not n
 
         shoot_strat_3.replace_signatures = True
         self.scheme.apply_strategy(shoot_strat_3)
-        assert_equal(len(self.scheme.movers['shooting']), 6)
+        assert len(self.scheme.movers['shooting']) == 6
 
         self.scheme.movers = {}
         shoot_strat_1.set_replace(True)
         self.scheme.apply_strategy(shoot_strat_1)
-        assert_equal(len(self.scheme.movers['shooting']), 3)
+        assert len(self.scheme.movers['shooting']) == 3
         old_movers = copy.copy(self.scheme.movers['shooting'])
 
         shoot_strat_3.replace_signatures = False
         self.scheme.apply_strategy(shoot_strat_3)
-        assert_equal(len(self.scheme.movers['shooting']), 6)
+        assert len(self.scheme.movers['shooting']) == 6
         new_movers = self.scheme.movers['shooting']
         for (o, n) in zip(old_movers, new_movers):
-            assert_equal(o is n, False)
+            assert o is not n
 
     def test_move_decision_tree(self):
         shoot = OneWayShootingStrategy()
@@ -537,24 +534,24 @@ def test_move_decision_tree(self):
         default = OrganizeByMoveGroupStrategy()
         self.scheme.append([default, shoot, repex])
 
-        assert_equal(self.scheme.root_mover, None)
+        assert self.scheme.root_mover is None
         root = self.scheme.move_decision_tree()
-        assert_not_equal(self.scheme.root_mover, None)
+        assert self.scheme.root_mover is not None
 
-        assert_equal(len(root.movers), 2)
+        assert len(root.movers) == 2
         names = ['ShootingChooser', 'RepexChooser']
         name_dict = {root.movers[i].name : i for i in range(len(root.movers))}
         for name in names:
-            assert_in(name, list(name_dict.keys()))
+            assert name in list(name_dict.keys())
 
-        assert_equal(len(root.movers[name_dict['ShootingChooser']].movers), 6)
-        assert_equal(len(root.movers[name_dict['RepexChooser']].movers), 4)
+        assert len(root.movers[name_dict['ShootingChooser']].movers) == 6
+        assert len(root.movers[name_dict['RepexChooser']].movers) == 4
 
         new_root = self.scheme.move_decision_tree()
-        assert_is(new_root, root)
+        assert new_root is root
 
         new_root = self.scheme.move_decision_tree(rebuild=True)
-        assert_is_not(new_root, root)
+        assert new_root is not root
 
     def test_repex_style_switching(self):
         nn_repex = NearestNeighborRepExStrategy()
@@ -563,15 +560,15 @@ def test_repex_style_switching(self):
 
         self.scheme.append([default, nn_repex])
         root = self.scheme.move_decision_tree(rebuild=True)
-        assert_equal(len(self.scheme.movers['repex']), 4)
+        assert len(self.scheme.movers['repex']) == 4
 
         self.scheme.append(all_repex, force=True)
         root = self.scheme.move_decision_tree(rebuild=True)
-        assert_equal(len(self.scheme.movers['repex']), 6)
+        assert len(self.scheme.movers['repex']) == 6
 
         self.scheme.append(nn_repex, force=True)
         root = self.scheme.move_decision_tree(rebuild=True)
-        assert_equal(len(self.scheme.movers['repex']), 4)
+        assert len(self.scheme.movers['repex']) == 4
 
     def test_build_balance_partners(self):
         ensA = self.scheme.network.sampling_transitions[0].ensembles[0]
@@ -582,15 +579,14 @@ def test_build_balance_partners(self):
         self.scheme.append(strategies.OrganizeByMoveGroupStrategy())
         root = self.scheme.move_decision_tree()
         self.scheme.build_balance_partners()
-        assert_equal(self.scheme.balance_partners[hopAB], [hopBA])
-        assert_equal(self.scheme.balance_partners[hopBA], [hopAB])
+        assert self.scheme.balance_partners[hopAB] == [hopBA]
+        assert self.scheme.balance_partners[hopBA] == [hopAB]
 
-    @raises(RuntimeWarning)
     def test_build_balance_partners_premature(self):
         self.scheme.movers = {}
-        self.scheme.build_balance_partners()
+        with pytest.raises(RuntimeWarning):
+            self.scheme.build_balance_partners()
 
-    @raises(RuntimeWarning)
     def test_build_balance_partners_no_partner(self):
         ensA = self.scheme.network.sampling_transitions[0].ensembles[0]
         ensB = self.scheme.network.sampling_transitions[0].ensembles[1]
@@ -599,9 +595,9 @@ def test_build_balance_partners_no_partner(self):
         self.scheme.movers['hop'] = [hopAB]
         self.scheme.append(strategies.OrganizeByMoveGroupStrategy())
         root = self.scheme.move_decision_tree()
-        self.scheme.build_balance_partners()
+        with pytest.raises(RuntimeWarning):
+            self.scheme.build_balance_partners()
 
-    @raises(RuntimeWarning)
     def test_build_balance_partners_two_partners(self):
         ensA = self.scheme.network.sampling_transitions[0].ensembles[0]
         ensB = self.scheme.network.sampling_transitions[0].ensembles[1]
@@ -611,7 +607,8 @@ def test_build_balance_partners_two_partners(self):
         self.scheme.movers['hop'] = [hopAB, hopBA, hopAB2]
         self.scheme.append(strategies.OrganizeByMoveGroupStrategy())
         root = self.scheme.move_decision_tree()
-        self.scheme.build_balance_partners()
+        with pytest.raises(RuntimeWarning):
+            self.scheme.build_balance_partners()
 
     def test_sanity_check_sane(self):
         self.scheme.append([NearestNeighborRepExStrategy(),
@@ -620,7 +617,6 @@ def test_sanity_check_sane(self):
         root = self.scheme.move_decision_tree()
         self.scheme.sanity_check()
 
-    @raises(AssertionError)
     def test_sanity_check_unused_sampling(self):
         ensemble_subset = self.scheme.network.sampling_transitions[0].ensembles
         self.scheme.append([
@@ -628,9 +624,9 @@ def test_sanity_check_unused_sampling(self):
             OrganizeByMoveGroupStrategy()
         ])
         root = self.scheme.move_decision_tree()
-        self.scheme.sanity_check()
+        with pytest.raises(AssertionError):
+            self.scheme.sanity_check()
 
-    @raises(AssertionError)
     def test_sanity_check_choice_prob_fails(self):
         self.scheme.append([NearestNeighborRepExStrategy(),
                             OneWayShootingStrategy(),
@@ -638,9 +634,9 @@ def test_sanity_check_choice_prob_fails(self):
         root = self.scheme.move_decision_tree()
         key0 = list(self.scheme.choice_probability.keys())[0]
         self.scheme.choice_probability[key0] = 0.0
-        self.scheme.sanity_check()
+        with pytest.raises(AssertionError):
+            self.scheme.sanity_check()
 
-    @raises(AssertionError)
     def test_sanity_check_duplicated_movers(self):
         ensemble_subset = self.scheme.network.sampling_transitions[0].ensembles
         self.scheme.append([
@@ -649,17 +645,18 @@ def test_sanity_check_duplicated_movers(self):
         ])
         root = self.scheme.move_decision_tree()
         self.scheme.movers['foo'] = [self.scheme.movers['shooting'][0]]
-        self.scheme.sanity_check()
+        with pytest.raises(AssertionError):
+            self.scheme.sanity_check()
 
-    @raises(TypeError)
     def test_select_movers_no_choice_probability(self):
         self.scheme.append([OneWayShootingStrategy(),
                             OrganizeByMoveGroupStrategy()])
-        movers = self.scheme._select_movers('shooting')
+        with pytest.raises(TypeError):
+            movers = self.scheme._select_movers('shooting')
 
     def test_select_movers(self):
         self.scheme.append([
-            OneWayShootingStrategy(), 
+            OneWayShootingStrategy(),
             NearestNeighborRepExStrategy(),
             OrganizeByMoveGroupStrategy()
         ])
@@ -667,17 +664,17 @@ def test_select_movers(self):
         some_shooters = self.scheme.movers['shooting'][0:2]
 
         movers = self.scheme._select_movers('shooting')
-        assert_equal(movers, self.scheme.movers['shooting'])
+        assert movers == self.scheme.movers['shooting']
 
         movers = self.scheme._select_movers(some_shooters)
-        assert_equal(movers, some_shooters)
+        assert movers == some_shooters
 
         movers = self.scheme._select_movers(some_shooters[0])
-        assert_equal(movers, [some_shooters[0]])
+        assert movers == [some_shooters[0]]
 
     def test_n_trials_for_steps(self):
         self.scheme.append([
-            OneWayShootingStrategy(), 
+            OneWayShootingStrategy(),
             NearestNeighborRepExStrategy(),
             OrganizeByMoveGroupStrategy()
         ])
@@ -728,15 +725,15 @@ def test_n_steps_for_trials(self):
 
 
 class TestDefaultScheme(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s : -s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(cvA, float("-inf"), -0.5)
         self.stateB = paths.CVDefinedVolume(cvB, float("-inf"), -0.5)
-        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"), 
+        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"),
                                                [-0.5, -0.3, -0.1])
-        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"), 
+        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"),
                                                [-0.5, -0.3, -0.1])
         self.network = paths.MSTISNetwork(
             [(self.stateA, interfacesA),
@@ -760,35 +757,28 @@ def test_default_scheme(self):
             'Ms_outer_shootingChooser' : paths.OneWayShootingMover
         }
         names = list(chooser_type_dict.keys())
-        assert_equal(len(root.movers), len(names))
+        assert len(root.movers) == len(names)
 
         name_dict = {root.movers[i].name : i for i in range(len(root.movers))}
         for name in names:
-            assert_in(name, list(name_dict.keys()))
+            assert name in list(name_dict.keys())
 
         n_normal_repex = 4
         n_msouter_repex = 2
         n_repex = n_normal_repex + n_msouter_repex
 
-        assert_equal(
-            len(root.movers[name_dict['ShootingChooser']].movers), 6
-        )
-        assert_equal(
-            len(root.movers[name_dict['PathreversalChooser']].movers), 7
-        )
-        assert_equal(
-            len(root.movers[name_dict['RepexChooser']].movers), n_repex
-        )
-        assert_equal(
-            len(root.movers[name_dict['MinusChooser']].movers), 2
-        )
-        assert_equal(
-            len(root.movers[name_dict['Ms_outer_shootingChooser']].movers), 1
+        assert len(root.movers[name_dict['ShootingChooser']].movers) == 6
+        assert len(root.movers[name_dict['PathreversalChooser']].movers) == 7
+        assert len(root.movers[name_dict['RepexChooser']].movers) == n_repex
+        assert len(root.movers[name_dict['MinusChooser']].movers) == 2
+        assert (
+            len(root.movers[name_dict['Ms_outer_shootingChooser']].movers)
+            == 1
         )
 
         for choosername in names:
             for mover in root.movers[name_dict[choosername]].movers:
-                assert_equal(type(mover), chooser_type_dict[choosername])
+                assert type(mover) == chooser_type_dict[choosername]
 
     def test_default_scheme_no_ms_outer(self):
         scheme = DefaultScheme(self.no_ms_outer)
@@ -800,28 +790,20 @@ def test_default_scheme_no_ms_outer(self):
             'MinusChooser' : paths.MinusMover
         }
         names = list(chooser_type_dict.keys())
-        assert_equal(len(root.movers), len(names))
+        assert len(root.movers) == len(names)
 
         name_dict = {root.movers[i].name : i for i in range(len(root.movers))}
         for name in names:
-            assert_in(name, list(name_dict.keys()))
+            assert name in list(name_dict.keys())
 
         n_normal_repex = 4
         n_msouter_repex = 0
         n_repex = n_normal_repex + n_msouter_repex
 
-        assert_equal(
-            len(root.movers[name_dict['ShootingChooser']].movers), 6
-        )
-        assert_equal(
-            len(root.movers[name_dict['PathreversalChooser']].movers), 6
-        )
-        assert_equal(
-            len(root.movers[name_dict['RepexChooser']].movers), n_repex
-        )
-        assert_equal(
-            len(root.movers[name_dict['MinusChooser']].movers), 2
-        )
+        assert len(root.movers[name_dict['ShootingChooser']].movers) == 6
+        assert len(root.movers[name_dict['PathreversalChooser']].movers) == 6
+        assert len(root.movers[name_dict['RepexChooser']].movers) == n_repex
+        assert len(root.movers[name_dict['MinusChooser']].movers) == 2
 
     def test_default_sanity(self):
         scheme = DefaultScheme(self.network)
@@ -832,13 +814,13 @@ def test_default_hidden_ensembles(self):
         scheme = DefaultScheme(self.network)
         root = scheme.move_decision_tree()
         hidden = scheme.find_hidden_ensembles()
-        assert_equal(len(hidden), 2)
+        assert len(hidden) == 2
 
     def test_default_unused_ensembles(self):
         scheme = DefaultScheme(self.network)
         root = scheme.move_decision_tree()
         unused = scheme.find_unused_ensembles()
-        assert_equal(len(unused), 0) # will change when minus/msouter
+        assert len(unused) == 0  # will change when minus/msouter
 
     def test_default_balance_partners(self):
         scheme = DefaultScheme(self.network)
@@ -847,7 +829,7 @@ def test_default_balance_partners(self):
         # by default, every mover is its own balance partner
         for group in list(scheme.movers.values()):
             for mover in group:
-                assert_equal(scheme.balance_partners[mover], [mover])
+                assert scheme.balance_partners[mover] == [mover]
 
     def test_default_choice_probability(self):
         scheme = DefaultScheme(self.network)
@@ -862,7 +844,7 @@ def test_default_choice_probability(self):
 
         assert_almost_equal(sum(scheme.choice_probability.values()), 1.0)
 
-        tot_norm = sum([default_group_weights[group] 
+        tot_norm = sum([default_group_weights[group]
                         for group in scheme.movers])
 
         prob_shoot0 = scheme.choice_probability[scheme.movers['shooting'][0]]
@@ -878,7 +860,7 @@ def test_default_choice_probability(self):
     def test_initial_conditions_from_trajectory(self):
         scheme = DefaultScheme(self.network)
         # root = scheme.move_decision_tree()
-        assert_equal(len(scheme.list_initial_ensembles()), 9)
+        assert len(scheme.list_initial_ensembles()) == 9
 
         traj1 = make_1d_traj([-0.6, -0.2, -0.6])
         traj2 = make_1d_traj([-0.6, -0.2, -0.05, -0.4, -0.6])
@@ -897,12 +879,12 @@ def assert_init_cond(sample_set, ensembles, expected):
 
             sample_set.sanity_check()
 
-            assert_equal(len(sample_set), len(expected))
+            assert len(sample_set) == len(expected)
 
             for ensemble, traj in zip(ensembles, expected):
                 # print ensemble.name, sample_set[ensemble].trajectory.xyz[:,0,0], traj.xyz[:, 0,0],
                 # print hex(id(traj)), hex(id(sample_set[ensemble].trajectory.xyz[:,0,0]))
-                assert_equal(sample_set[ensemble].trajectory, traj)
+                assert sample_set[ensemble].trajectory == traj
 
         transAB = transBA = None
         for trans in self.network.sampling_transitions:
@@ -974,21 +956,21 @@ def assert_init_cond(sample_set, ensembles, expected):
         )
 
         init_cond.sanity_check()
-        assert_equal(len(init_cond), 7)
+        assert len(init_cond) == 7
 
         for ensemble, traj in zip(ensembles[:3], [traj1, traj2, traj3]):
-            assert_equal(init_cond[ensemble].trajectory, traj)
+            assert init_cond[ensemble].trajectory == traj
         for ensemble, traj in zip(ensembles[4:], [traj3r] * 3):
-            assert_equal(init_cond[ensemble].trajectory, traj)
+            assert init_cond[ensemble].trajectory == traj
 
         # because of the way the scheme ensembles are creating involving a
         # set, the order in which the ensemble are created changes.
         # in some cases traj3 is used and hence avoided in the outer
         # in some cases traj3r, but both are fine.
         try:
-            assert_equal(init_cond[ensembles[3]].trajectory, traj3)
+            assert init_cond[ensembles[3]].trajectory == traj3
         except AssertionError:
-            assert_equal(init_cond[ensembles[3]].trajectory, traj3r)
+            assert init_cond[ensembles[3]].trajectory == traj3r
 
         init_cond = scheme.initial_conditions_from_trajectories(
             trajectories=all_trajs,
@@ -997,17 +979,17 @@ def assert_init_cond(sample_set, ensembles, expected):
             strategies=['get']
         )
         init_cond.sanity_check()
-        assert_equal(len(init_cond), 7)
+        assert len(init_cond) == 7
 
         for ensemble, traj in zip(ensembles[:3], [traj1, traj1r, traj3]):
-            assert_equal(init_cond[ensemble].trajectory, traj)
+            assert init_cond[ensemble].trajectory == traj
         for ensemble, traj in zip(ensembles[4:], [traj3r] * 3):
-            assert_equal(init_cond[ensemble].trajectory, traj)
+            assert init_cond[ensemble].trajectory == traj
 
         try:
-            assert_equal(init_cond[ensembles[3]].trajectory, traj3)
+            assert init_cond[ensembles[3]].trajectory == traj3
         except AssertionError:
-            assert_equal(init_cond[ensembles[3]].trajectory, traj3r)
+            assert init_cond[ensembles[3]].trajectory == traj3r
 
         # this one avoids reversed copies
         init_cond = scheme.initial_conditions_from_trajectories(
@@ -1061,23 +1043,22 @@ def test_check_initial_conditions(self):
         traj3 = make_1d_traj([-0.6, -0.2, 0.2, 0.6])
         # cheating a bit, since we know what this gives
         init_cond = scheme.initial_conditions_from_trajectories(traj3)
-        assert_equal(len(init_cond), 7)
-        assert_equal(len(scheme.list_initial_ensembles()), 9)
+        assert len(init_cond) == 7
+        assert len(scheme.list_initial_ensembles()) == 9
         (missing, extra) = scheme.check_initial_conditions(init_cond)
-        assert_equal(len(missing), 2)
-        assert_equal(len(extra), 0)
+        assert len(missing) == 2
+        assert len(extra) == 0
         for ens in list(self.network.special_ensembles['minus'].keys()):
-            assert_in([ens], missing)
+            assert [ens] in missing
         init_cond.append_as_new_replica(
             paths.Sample(trajectory=traj3,
                          ensemble=paths.LengthEnsemble(4),
                          replica=None)
         )
         (missing, extra) = scheme.check_initial_conditions(init_cond)
-        assert_equal(len(missing), 2)
-        assert_equal(len(extra), 1)
+        assert len(missing) == 2
+        assert len(extra) == 1
 
-    @raises(AssertionError)
     def test_assert_initial_conditions(self):
         scheme = DefaultScheme(self.network)
         traj3 = make_1d_traj([-0.6, -0.2, 0.2, 0.6])
@@ -1087,7 +1068,8 @@ def test_assert_initial_conditions(self):
                          ensemble=paths.LengthEnsemble(4),
                          replica=None)
         )
-        scheme.assert_initial_conditions(init_cond)
+        with pytest.raises(AssertionError):
+            scheme.assert_initial_conditions(init_cond)
 
     def test_initial_conditions_report(self):
         scheme = DefaultScheme(self.network)
@@ -1106,21 +1088,21 @@ def test_initial_conditions_report(self):
         expected_BA = start + missing_B + missing_A + finish
         result = scheme.initial_conditions_report(init_cond)
         try:
-            assert_equal(result, expected_AB)
+            assert result == expected_AB
         except AssertionError:
-            assert_equal(result, expected_BA)
+            assert result == expected_BA
 
 
 class TestLockedMoveScheme(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s : -s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(cvA, float("-inf"), -0.5)
         self.stateB = paths.CVDefinedVolume(cvB, float("-inf"), -0.5)
-        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"), 
+        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"),
                                                [-0.5, -0.3, -0.1])
-        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"), 
+        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"),
                                                [-0.5, -0.3, -0.1])
         self.network = paths.MSTISNetwork(
             [(self.stateA, interfacesA),
@@ -1134,39 +1116,39 @@ def setup(self):
 
     def test_initialization(self):
         scheme = LockedMoveScheme(self.root_mover, self.network)
-        assert_equal(scheme.network, self.network)
-        assert_equal(scheme.move_decision_tree(), self.root_mover)
+        assert scheme.network == self.network
+        assert scheme.move_decision_tree() == self.root_mover
 
     def test_build_move_decision_tree(self):
         scheme = LockedMoveScheme(self.root_mover, self.network)
         scheme.move_decision_tree(rebuild=True)
-        assert_equal(scheme.move_decision_tree(), self.root_mover)
+        assert scheme.move_decision_tree() == self.root_mover
 
-    @raises(TypeError)
     def test_append(self):
         scheme = LockedMoveScheme(self.root_mover, self.network)
-        scheme.append(AllSetRepExStrategy())
+        with pytest.raises(TypeError):
+            scheme.append(AllSetRepExStrategy())
 
-    @raises(TypeError)
     def test_apply_strategy(self):
         scheme = LockedMoveScheme(self.root_mover, self.network)
         strategy = AllSetRepExStrategy()
-        scheme.apply_strategy(strategy)
+        with pytest.raises(TypeError):
+            scheme.apply_strategy(strategy)
 
-    @raises(AttributeError)
     def test_choice_probability_fail(self):
         scheme = LockedMoveScheme(self.root_mover, self.network)
-        vals = scheme.choice_probability
+        with pytest.raises(AttributeError):
+            vals = scheme.choice_probability
 
     def test_choice_probability_works(self):
         scheme = LockedMoveScheme(self.root_mover, self.network)
         scheme.choice_probability = self.basic_scheme.choice_probability
         vals = scheme.choice_probability
 
-    @raises(AttributeError)
     def test_movers_fail(self):
         scheme = LockedMoveScheme(self.root_mover, self.network)
-        vals = scheme.movers
+        with pytest.raises(AttributeError):
+            vals = scheme.movers
 
     def test_movers_works(self):
         scheme = LockedMoveScheme(self.root_mover, self.network)
@@ -1175,15 +1157,15 @@ def test_movers_works(self):
 
 
 class TestOneWayShootingMoveScheme(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s : s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s : -s.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(cvA, float("-inf"), -0.5)
         self.stateB = paths.CVDefinedVolume(cvB, float("-inf"), -0.5)
-        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"), 
+        interfacesA = paths.VolumeInterfaceSet(cvA, float("-inf"),
                                                [-0.5, -0.3, -0.1])
-        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"), 
+        interfacesB = paths.VolumeInterfaceSet(cvB, float("-inf"),
                                                [-0.5, -0.3, -0.1])
         self.network = paths.MSTISNetwork(
             [(self.stateA, interfacesA),
@@ -1196,8 +1178,8 @@ def setup(self):
     def test_scheme(self):
         scheme = OneWayShootingMoveScheme(self.network)
         root = scheme.move_decision_tree()
-        assert_equal(len(scheme.movers), 1)
-        assert_equal(len(root.movers), 1)
+        assert len(scheme.movers) == 1
+        assert len(root.movers) == 1
 
     def test_sanity(self):
         scheme = OneWayShootingMoveScheme(self.network)
@@ -1209,16 +1191,16 @@ def test_unused_ensembles(self):
         root = scheme.move_decision_tree()
         unused = scheme.find_unused_ensembles()
         specials = self.network.special_ensembles
-        expected_unused = sum([list(specials[special_type].keys()) 
+        expected_unused = sum([list(specials[special_type].keys())
                                for special_type in specials], [])
-        assert_equal(set(expected_unused), set(unused))
+        assert set(expected_unused) == set(unused)
 
     def test_check_initial_conditions(self):
         scheme = OneWayShootingMoveScheme(self.network)
         traj3 = make_1d_traj([-0.6, -0.2, 0.2, 0.6])
         init_cond = scheme.initial_conditions_from_trajectories(traj3)
-        assert_equal(len(scheme.list_initial_ensembles()), 6)
-        assert_equal(len(init_cond), 6)
+        assert len(scheme.list_initial_ensembles()) == 6
+        assert len(init_cond) == 6
         scheme.assert_initial_conditions(init_cond)
-        assert_equal(scheme.initial_conditions_report(init_cond),
-                     "No missing ensembles.\nNo extra ensembles.\n")
+        assert (scheme.initial_conditions_report(init_cond)
+                == "No missing ensembles.\nNo extra ensembles.\n")
diff --git a/openpathsampling/tests/test_movestrategy.py b/openpathsampling/tests/test_movestrategy.py
index 62d6fa2c0..2118b8e62 100644
--- a/openpathsampling/tests/test_movestrategy.py
+++ b/openpathsampling/tests/test_movestrategy.py
@@ -5,9 +5,7 @@
 from builtins import range
 from past.utils import old_div
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises, assert_in, assert_not_in)
-from nose.plugins.skip import Skip, SkipTest
+from numpy.testing import assert_almost_equal
 from .test_helpers import (
     true_func, assert_equal_array_array, make_1d_traj, MoverWithSignature,
     setify_ensemble_signature, reorder_ensemble_signature
@@ -46,17 +44,17 @@ def find_mover(scheme, group, sig):
 
 class TestStrategyLevels(object):
     def test_level_type(self):
-        assert_equal(levels.level_type(10), levels.SIGNATURE)
-        assert_equal(levels.level_type(1), levels.SIGNATURE)
-        assert_equal(levels.level_type(19), levels.SIGNATURE)
-        assert_equal(levels.level_type(20), None)
-        assert_equal(levels.level_type(21), levels.MOVER)
-        assert_equal(levels.level_type(35), levels.MOVER)
-        assert_equal(levels.level_type(100), levels.GLOBAL)
+        assert levels.level_type(10) == levels.SIGNATURE
+        assert levels.level_type(1) == levels.SIGNATURE
+        assert levels.level_type(19) == levels.SIGNATURE
+        assert levels.level_type(20) is None
+        assert levels.level_type(21) == levels.MOVER
+        assert levels.level_type(35) == levels.MOVER
+        assert levels.level_type(100) == levels.GLOBAL
 
 
 class MoveStrategyTestSetup(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         cvA = paths.FunctionCV(name="xA", f=lambda s: s.xyz[0][0])
         cvB = paths.FunctionCV(name="xB", f=lambda s: -s.xyz[0][0])
@@ -78,21 +76,21 @@ def setup(self):
 class TestMoveStrategy(MoveStrategyTestSetup):
     def test_levels(self):
         strategy = MockMoveStrategy(ensembles=None, group="test", replace=True)
-        assert_equal(strategy.level, -1)
-        assert_equal(strategy.replace_signatures, False)
-        assert_equal(strategy.replace_movers, False)
+        assert strategy.level == -1
+        assert strategy.replace_signatures is False
+        assert strategy.replace_movers is False
         strategy.level = 10
-        assert_equal(strategy.level, levels.SIGNATURE)
-        assert_equal(strategy.replace_signatures, True)
-        assert_equal(strategy.replace_movers, False)
+        assert strategy.level == levels.SIGNATURE
+        assert strategy.replace_signatures
+        assert not strategy.replace_movers
         strategy.level = 25
-        assert_not_equal(strategy.level, levels.MOVER)
-        assert_equal(levels.level_type(strategy.level), levels.MOVER)
-        assert_equal(strategy.replace_signatures, False)
-        assert_equal(strategy.replace_movers, True)
+        assert strategy.level != levels.MOVER
+        assert levels.level_type(strategy.level) == levels.MOVER
+        assert not strategy.replace_signatures
+        assert strategy.replace_movers
         strategy.level = 99
-        assert_equal(strategy.replace_signatures, False)
-        assert_equal(strategy.replace_movers, False)
+        assert not strategy.replace_signatures
+        assert not strategy.replace_movers
 
     def test_get_ensembles(self):
         self.strategy = MockMoveStrategy(ensembles=None, group="test",
@@ -102,32 +100,32 @@ def test_get_ensembles(self):
         transition_ensembles = []
         for transition in self.network.sampling_transitions:
             transition_ensembles.append(transition.ensembles)
-        assert_equal(len(transition_ensembles), 2)
+        assert len(transition_ensembles) == 2
         for ens_set in transition_ensembles:
-            assert_equal(len(ens_set), 3)
+            assert len(ens_set) == 3
         ensA = self.network.from_state[self.stateA].ensembles
-        assert_equal(len(ensA), 3)
+        assert len(ensA) == 3
         # if you error before this, something is wrong in setup
         ensembles = self.strategy.get_ensembles(scheme, None)
-        assert_equal(ensembles, transition_ensembles)
+        assert ensembles == transition_ensembles
 
         ensembles = self.strategy.get_ensembles(scheme, ensA)
-        assert_equal(ensembles, [ensA])
+        assert ensembles == [ensA]
 
         extra_ens = transition_ensembles[1][0]
         weird_ens_list = [[ensA[0]], ensA[1], [extra_ens]]
         ensembles = self.strategy.get_ensembles(scheme, weird_ens_list)
-        assert_equal(ensembles, [[ensA[0]], [ensA[1]], [extra_ens]])
+        assert ensembles == [[ensA[0]], [ensA[1]], [extra_ens]]
 
         ensembles = self.strategy.get_ensembles(scheme, extra_ens)
-        assert_equal(len(ensembles), 1)
-        assert_equal(len(ensembles[0]), 1)
-        assert_equal(ensembles[0][0], extra_ens)
+        assert len(ensembles) == 1
+        assert len(ensembles[0]) == 1
+        assert ensembles[0][0] == extra_ens
 
 
 class TestSingleEnsembleMoveStrategy(MoveStrategyTestSetup):
-    def setup(self):
-        super(TestSingleEnsembleMoveStrategy, self).setup()
+    def setup_method(self):
+        super(TestSingleEnsembleMoveStrategy, self).setup_method()
         self.strategy = MockSingleEnsembleMoveStrategy(
             ensembles=None,
             group='test_group',
@@ -171,10 +169,10 @@ def test_make_movers(self):
         strategy = ForwardShootingStrategy()
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 6)
+        assert len(movers) == 6
         for mover in movers:
-            assert_equal(type(mover), paths.ForwardShootMover)
-            assert_equal(type(mover.selector), paths.UniformSelector)
+            assert type(mover) == paths.ForwardShootMover
+            assert type(mover.selector) == paths.UniformSelector
 
     def test_make_movers_with_list(self):
         list_of_selectors = [
@@ -187,13 +185,13 @@ def test_make_movers_with_list(self):
         )
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 6)
-        assert_equal(len(list_of_selectors), 6)
+        assert len(movers) == 6
+        assert len(list_of_selectors) == 6
         for mover, sel in zip(movers, list_of_selectors):
-            assert_equal(type(mover), paths.ForwardShootMover)
-            assert_equal(type(mover.selector),
-                         paths.shooting.InterfaceConstrainedSelector)
-            assert_equal(mover.selector, sel)
+            assert type(mover) == paths.ForwardShootMover
+            assert (type(mover.selector)
+                    == paths.shooting.InterfaceConstrainedSelector)
+            assert mover.selector == sel
 
 
 class TestOneWayShootingStrategy(MoveStrategyTestSetup):
@@ -201,32 +199,32 @@ def test_make_movers(self):
         strategy = OneWayShootingStrategy()
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 6)
+        assert len(movers) == 6
         for mover in movers:
-            assert_equal(type(mover), paths.OneWayShootingMover)
-            assert_equal(type(mover.selector), paths.UniformSelector)
+            assert type(mover) == paths.OneWayShootingMover
+            assert type(mover.selector) == paths.UniformSelector
 
 class TestTwoWayShootingStrategy(MoveStrategyTestSetup):
     def test_make_movers(self):
         strategy = TwoWayShootingStrategy(modifier=paths.NoModification())
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 6)
+        assert len(movers) == 6
         for mover in movers:
-            assert_equal(type(mover), paths.TwoWayShootingMover)
-            assert_equal(type(mover.selector), paths.UniformSelector)
-            assert_equal(type(mover.modifier), paths.NoModification)
+            assert type(mover) == paths.TwoWayShootingMover
+            assert type(mover.selector) == paths.UniformSelector
+            assert type(mover.modifier) == paths.NoModification
 
     def test_composition_with_default_scheme(self):
         strategy = TwoWayShootingStrategy(modifier=paths.NoModification())
         scheme = DefaultScheme(self.network, engine=None)
         scheme.append(strategy)
         scheme.build_move_decision_tree()
-        assert_equal(len(scheme.movers['shooting']), 6)
+        assert len(scheme.movers['shooting']) == 6
         for mover in scheme.movers['shooting']:
-            assert_equal(type(mover), paths.TwoWayShootingMover)
-            assert_equal(type(mover.selector), paths.UniformSelector)
-            assert_equal(type(mover.modifier), paths.NoModification)
+            assert type(mover) == paths.TwoWayShootingMover
+            assert type(mover.selector) == paths.UniformSelector
+            assert type(mover.modifier) == paths.NoModification
 
 
 class TestNearestNeighborRepExStrategy(MoveStrategyTestSetup):
@@ -234,24 +232,24 @@ def test_make_movers(self):
         strategy = NearestNeighborRepExStrategy()
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 4)
+        assert len(movers) == 4
         ens0 = self.network.sampling_transitions[0].ensembles
         ens1 = self.network.sampling_transitions[1].ensembles
-        assert_equal(movers[0].ensemble_signature_set,
-                     (set([ens0[0], ens0[1]]), set([ens0[0], ens0[1]])))
-        assert_equal(movers[1].ensemble_signature_set,
-                     (set([ens0[1], ens0[2]]), set([ens0[1], ens0[2]])))
-        assert_equal(movers[2].ensemble_signature_set,
-                     (set([ens1[0], ens1[1]]), set([ens1[0], ens1[1]])))
-        assert_equal(movers[3].ensemble_signature_set,
-                     (set([ens1[1], ens1[2]]), set([ens1[1], ens1[2]])))
+        assert (movers[0].ensemble_signature_set
+                == (set([ens0[0], ens0[1]]), set([ens0[0], ens0[1]])))
+        assert (movers[1].ensemble_signature_set
+                == (set([ens0[1], ens0[2]]), set([ens0[1], ens0[2]])))
+        assert (movers[2].ensemble_signature_set
+                == (set([ens1[0], ens1[1]]), set([ens1[0], ens1[1]])))
+        assert (movers[3].ensemble_signature_set
+                == (set([ens1[1], ens1[2]]), set([ens1[1], ens1[2]])))
 
 class TestAllSetRepExStrategy(MoveStrategyTestSetup):
     def test_make_movers(self):
         strategy = AllSetRepExStrategy()
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 6)
+        assert len(movers) == 6
         ens0 = self.network.sampling_transitions[0].ensembles
         ens1 = self.network.sampling_transitions[1].ensembles
 
@@ -267,7 +265,7 @@ def test_make_movers(self):
         ]
         for sig in expected_signatures:
             set_sig = (set(sig[0]), set(sig[1]))
-            assert_in(set_sig, signatures)
+            assert set_sig in signatures
 
 class TestSelectedPairsRepExStrategy(MoveStrategyTestSetup):
     def test_make_movers(self):
@@ -276,18 +274,18 @@ def test_make_movers(self):
         strategy = SelectedPairsRepExStrategy(ensembles=[ens00, ens02])
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 1)
-        assert_equal(movers[0].ensemble_signature_set,
-                     ({ ens00, ens02 }, ({ ens00, ens02 })))
+        assert len(movers) == 1
+        assert (movers[0].ensemble_signature_set
+                == ({ ens00, ens02 }, ({ ens00, ens02 })))
 
-    @raises(RuntimeError)
     def test_init_ensembles_none(self):
-        strategy = SelectedPairsRepExStrategy()
+        with pytest.raises(RuntimeError):
+            strategy = SelectedPairsRepExStrategy()
 
-    @raises(RuntimeError)
     def test_init_ensembles_triplet(self):
         ensembles = self.network.sampling_transitions[0].ensembles
-        strategy = SelectedPairsRepExStrategy(ensembles=ensembles)
+        with pytest.raises(RuntimeError):
+            strategy = SelectedPairsRepExStrategy(ensembles=ensembles)
 
     def test_make_movers_multiple_pairs(self):
         ens00 = self.network.sampling_transitions[0].ensembles[0]
@@ -298,13 +296,13 @@ def test_make_movers_multiple_pairs(self):
                                                          [ens01, ens02]])
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 3)
-        assert_equal(movers[0].ensemble_signature_set,
-                     ({ens00, ens01}, {ens00, ens01}))
-        assert_equal(movers[1].ensemble_signature_set,
-                     ({ens00, ens02}, {ens00, ens02}))
-        assert_equal(movers[2].ensemble_signature_set,
-                     ({ens01, ens02}, {ens01, ens02}))
+        assert len(movers) == 3
+        assert (movers[0].ensemble_signature_set
+                == ({ens00, ens01}, {ens00, ens01}))
+        assert (movers[1].ensemble_signature_set
+                == ({ens00, ens02}, {ens00, ens02}))
+        assert (movers[2].ensemble_signature_set
+                == ({ens01, ens02}, {ens01, ens02}))
 
 class TestReplicaExchangeStrategy(MoveStrategyTestSetup):
     def test_make_movers(self):
@@ -312,36 +310,36 @@ def test_make_movers(self):
         scheme = MoveScheme(self.network)
         scheme.apply_strategy(NearestNeighborRepExStrategy())
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 4)
+        assert len(movers) == 4
 
     def test_swap_to_hop_to_swap(self):
         scheme = DefaultScheme(self.network)
         root = scheme.move_decision_tree()
-        assert_equal(len(scheme.movers['repex']), 6)
+        assert len(scheme.movers['repex']) == 6
         old_movers = scheme.movers['repex']
         scheme.append(EnsembleHopStrategy(), force=True)
         root = scheme.move_decision_tree(rebuild=True)
-        assert_equal(len(scheme.movers['repex']), 12)
+        assert len(scheme.movers['repex']) == 12
         scheme.append(ReplicaExchangeStrategy(), force=True)
         root = scheme.move_decision_tree(rebuild=True)
-        assert_equal(len(scheme.movers['repex']), 6)
+        assert len(scheme.movers['repex']) == 6
         new_movers = scheme.movers['repex']
-        assert_not_equal(old_movers, new_movers)
+        assert old_movers != new_movers
         old_sigs = [m.ensemble_signature_set for m in old_movers]
         new_sigs = [m.ensemble_signature_set for m in new_movers]
         for old in old_sigs:
-            assert_in(old, new_sigs)
+            assert old in new_sigs
 
-    @raises(RuntimeError)
     def test_detailed_balance_partners(self):
         scheme = DefaultScheme(self.network)
         scheme.append(EnsembleHopStrategy(), force=True)
         root = scheme.move_decision_tree()
-        assert_equal(len(scheme.movers['repex']), 12)
+        assert len(scheme.movers['repex']) == 12
         scheme.movers['repex'].pop()
-        assert_equal(len(scheme.movers['repex']), 11)
+        assert len(scheme.movers['repex']) == 11
         strategy = ReplicaExchangeStrategy()
-        strategy.make_movers(scheme)
+        with pytest.raises(RuntimeError):
+            strategy.make_movers(scheme)
 
 
 class TestEnsembleHopStrategy(MoveStrategyTestSetup):
@@ -351,30 +349,29 @@ def test_make_movers(self):
         scheme.apply_strategy(NearestNeighborRepExStrategy())
         movers = strategy.make_movers(scheme)
         # defaults to 4 repex movers, so
-        assert_equal(len(movers), 8)
+        assert len(movers) == 8
 
         # set up the swap pairs
         swap_pairs = []
         for trans in self.network.sampling_transitions:
-            swap_pairs.extend([[trans.ensembles[i], trans.ensembles[i+1]] 
+            swap_pairs.extend([[trans.ensembles[i], trans.ensembles[i+1]]
                                for i in range(len(trans.ensembles)-1)])
-        assert_equal(len(swap_pairs), 4)
+        assert len(swap_pairs) == 4
 
         # check that each swap pair has a hop
         mover_sigs = [m.ensemble_signature for m in movers]
         for pair in swap_pairs:
             sig1 = ((pair[0],),(pair[1],))
             sig2 = ((pair[1],),(pair[0],))
-            assert_in(sig1, mover_sigs)
-            assert_in(sig2, mover_sigs)
+            assert sig1 in mover_sigs
+            assert sig2 in mover_sigs
 
         scheme.movers['repex'] = movers
         newmovers = strategy.make_movers(scheme)
-        assert_equal(len(newmovers), 8)
+        assert len(newmovers) == 8
         for mover in newmovers:
-            assert_in(mover.ensemble_signature, mover_sigs)
+            assert mover.ensemble_signature in mover_sigs
 
-    @raises(RuntimeError)
     def test_different_number_input_output_ensembles(self):
         ens0 = self.network.sampling_ensembles[0]
         ens1 = self.network.sampling_ensembles[1]
@@ -383,14 +380,14 @@ def test_different_number_input_output_ensembles(self):
             input_ensembles=[ens0, ens1, ens2],
             output_ensembles=[ens0, ens1]
         )
-        assert_equal(weird_mover.ensemble_signature,
-                     ((ens0,ens1,ens2),(ens0,ens1)))
+        assert (weird_mover.ensemble_signature
+                == ((ens0,ens1,ens2),(ens0,ens1)))
         scheme = MoveScheme(self.network)
         scheme.movers['weird'] = [weird_mover]
         strategy = EnsembleHopStrategy(group='weird')
-        strategy.make_movers(scheme)
+        with pytest.raises(RuntimeError):
+            strategy.make_movers(scheme)
 
-    @raises(RuntimeError)
     def test_wrong_number_ensembles_in_signature(self):
         ens0 = self.network.sampling_ensembles[0]
         ens1 = self.network.sampling_ensembles[1]
@@ -399,14 +396,14 @@ def test_wrong_number_ensembles_in_signature(self):
             input_ensembles=[ens0, ens1, ens2],
             output_ensembles=[ens0, ens1, ens2]
         )
-        assert_equal(weird_mover.ensemble_signature,
-                     ((ens0,ens1,ens2),(ens0,ens1,ens2)))
+        assert (weird_mover.ensemble_signature
+                == ((ens0,ens1,ens2),(ens0,ens1,ens2)))
         scheme = MoveScheme(self.network)
         scheme.movers['weird'] = [weird_mover]
         strategy = EnsembleHopStrategy(group='weird')
-        strategy.make_movers(scheme)
+        with pytest.raises(RuntimeError):
+            strategy.make_movers(scheme)
 
-    @raises(RuntimeError)
     def test_not_replica_exchange_signature(self):
         ens0 = self.network.sampling_ensembles[0]
         ens1 = self.network.sampling_ensembles[1]
@@ -415,12 +412,12 @@ def test_not_replica_exchange_signature(self):
             input_ensembles=[ens0, ens1],
             output_ensembles=[ens1, ens2]
         )
-        assert_equal(weird_mover.ensemble_signature, 
-                     ((ens0,ens1),(ens1,ens2)))
+        assert weird_mover.ensemble_signature == ((ens0,ens1),(ens1,ens2))
         scheme = MoveScheme(self.network)
         scheme.movers['weird'] = [weird_mover]
         strategy = EnsembleHopStrategy(group='weird')
-        strategy.make_movers(scheme)
+        with pytest.raises(RuntimeError):
+            strategy.make_movers(scheme)
 
     def test_replace_nofrom(self):
         # this is the default behavior
@@ -429,31 +426,31 @@ def test_replace_nofrom(self):
         scheme.append(EnsembleHopStrategy(replace=True, from_group=None))
         scheme.build_move_decision_tree()
         # 4 normal repex + 2 ms-outer repex = 6 repex * 2 hop/repex = 12
-        assert_equal(len(scheme.movers['repex']), 12)
+        assert len(scheme.movers['repex']) == 12
 
     def test_noreplace_from(self):
         # if replace is False and from_group is given, we end up with two
         # groups: both the new and the old.
         scheme = DefaultScheme(self.network)
         scheme.movers ={}
-        scheme.append(EnsembleHopStrategy(replace=False, 
+        scheme.append(EnsembleHopStrategy(replace=False,
                                           group='hop',
                                           from_group='repex'))
         scheme.build_move_decision_tree()
-        assert_equal(len(scheme.movers['repex']), 6)
-        assert_equal(len(scheme.movers['hop']), 12)
+        assert len(scheme.movers['repex']) == 6
+        assert len(scheme.movers['hop']) == 12
 
     def test_replace_from(self):
         # if replace is True and we have a different from_group, we should
         # remove the old from_group from existence
         scheme = DefaultScheme(self.network)
         scheme.movers ={}
-        scheme.append(EnsembleHopStrategy(replace=True, 
+        scheme.append(EnsembleHopStrategy(replace=True,
                                           group='hop',
                                           from_group='repex'))
         scheme.build_move_decision_tree()
-        assert_equal(len(scheme.movers['hop']), 12)
-        assert_not_in("repex", list(scheme.movers.keys()))
+        assert len(scheme.movers['hop']) == 12
+        assert "repex" not in list(scheme.movers.keys())
 
     def test_noreplace_nofrom(self):
         # if replace==False and the from_group is not given, we should
@@ -462,7 +459,7 @@ def test_noreplace_nofrom(self):
         scheme.movers ={}
         scheme.append(EnsembleHopStrategy(replace=False, from_group=None))
         scheme.build_move_decision_tree()
-        assert_equal(len(scheme.movers['repex']), 18)
+        assert len(scheme.movers['repex']) == 18
 
 
 class TestPathReversalStrategy(MoveStrategyTestSetup):
@@ -470,9 +467,9 @@ def test_make_movers(self):
         strategy = PathReversalStrategy()
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 6)
+        assert len(movers) == 6
         for m in movers:
-            assert_equal(type(m), paths.PathReversalMover)
+            assert type(m) == paths.PathReversalMover
 
 
 class TestMinusMoveStrategy(MoveStrategyTestSetup):
@@ -480,10 +477,10 @@ def test_get_ensembles(self):
         strategy = MinusMoveStrategy()
         scheme = MoveScheme(self.network)
         ensembles = strategy.get_ensembles(scheme, None)
-        assert_equal(len(ensembles), 2)
+        assert len(ensembles) == 2
         for ens_group in ensembles:
-            assert_equal(len(ens_group), 1)
-        assert_not_equal(ensembles[0][0].state_vol, ensembles[1][0].state_vol)
+            assert len(ens_group) == 1
+        assert ensembles[0][0].state_vol != ensembles[1][0].state_vol
 
     def test_get_ensembles_multiple_minus(self):
         strategy = MinusMoveStrategy()
@@ -498,23 +495,23 @@ def test_get_ensembles_multiple_minus(self):
         self.network.special_ensembles['minus'][extra_minus] = [innerA, innerB]
         scheme = MoveScheme(self.network)
         ensembles = strategy.get_ensembles(scheme, None)
-        assert_equal(len(ensembles), 2)
-        assert_equal({ len(ensembles[0]), len(ensembles[1]) }, { 1, 2 })
+        assert len(ensembles) == 2
+        assert { len(ensembles[0]), len(ensembles[1]) } == { 1, 2 }
 
     def test_get_ensembles_fixed_ensembles(self):
         strategy = MinusMoveStrategy()
         minusA = list(self.network.special_ensembles['minus'].keys())[0]
         scheme = MoveScheme(self.network)
         ensembles = strategy.get_ensembles(scheme, minusA)
-        assert_equal(len(ensembles), 1)
-        assert_equal(len(ensembles[0]), 1)
-        assert_equal(ensembles[0][0], minusA)
+        assert len(ensembles) == 1
+        assert len(ensembles[0]) == 1
+        assert ensembles[0][0] == minusA
 
     def test_make_movers(self):
         strategy = MinusMoveStrategy()
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 2)
+        assert len(movers) == 2
 
         minuses = self.network.special_ensembles['minus']
         ens_minusA = list(minuses.keys())[0]
@@ -526,28 +523,26 @@ def test_make_movers(self):
         all_ens_sigs = [m.ensemble_signature_set for m in movers]
 
         # check the signatures
-        assert_not_equal(sig_A, sig_B)
-        assert_in(tuple([sig_A, sig_A]), all_ens_sigs)
-        assert_in(tuple([sig_B, sig_B]), all_ens_sigs)
+        assert sig_A != sig_B
+        assert tuple([sig_A, sig_A]) in all_ens_sigs
+        assert tuple([sig_B, sig_B]) in all_ens_sigs
 
         # check that these are inner ensembles
         inners = [t.ensembles[0] for t in self.network.sampling_transitions]
         for inner in ens_innerA + ens_innerB:
-            assert_in(inner, inners)
+            assert inner in inners
 
         # check that we've got the right inner for the right state
         stateA_inner = self.network.from_state[ens_minusA.state_vol].ensembles[0]
-        assert_equal([stateA_inner], ens_innerA)
+        assert [stateA_inner] == ens_innerA
         stateB_inner = self.network.from_state[ens_minusB.state_vol].ensembles[0]
-        assert_equal([stateB_inner], ens_innerB)
+        assert [stateB_inner] == ens_innerB
 
         # check that we've got minus ensembles
         for mover in movers:
-            assert_in(mover.minus_ensemble, self.network.minus_ensembles)
-            assert_equal(
-                isinstance(mover.minus_ensemble, paths.MinusInterfaceEnsemble),
-                True
-            )
+            assert mover.minus_ensemble in self.network.minus_ensembles
+            assert isinstance(mover.minus_ensemble,
+                              paths.MinusInterfaceEnsemble)
 
 
 class TestSingleReplicaMinusMoveStrategy(MoveStrategyTestSetup):
@@ -555,7 +550,7 @@ def test_make_movers(self):
         strategy = SingleReplicaMinusMoveStrategy()
         scheme = MoveScheme(self.network)
         movers = strategy.make_movers(scheme)
-        assert_equal(len(movers), 2)
+        assert len(movers) == 2
 
         minuses = self.network.special_ensembles['minus']
         ens_minusA = list(minuses.keys())[0]
@@ -603,12 +598,12 @@ def test_choice_probability(self):
         # norm = 3*1.0 + 2*0.5 = 4.0
         # each shooting prob: 0.25
         # each repex prob: 0.125
-        assert_equal(len(choice_prob), len(sum(list(scheme.movers.values()), [])))
+        assert len(choice_prob) == len(sum(list(scheme.movers.values()), []))
         for m in list(choice_prob.keys()):
             if m in scheme.movers['shooting']:
-                assert_equal(choice_prob[m], 0.25)
+                assert choice_prob[m] == 0.25
             elif m in scheme.movers['repex']:
-                assert_equal(choice_prob[m], 0.125)
+                assert choice_prob[m] == 0.125
             else:
                 raise RuntimeError("Unknown mover "+repr(m))
 
@@ -625,15 +620,14 @@ def test_choice_probability(self):
         for m in list(choice_prob.keys()):
             if m in scheme.movers['shooting']:
                    if m.ensemble_signature == ens0_sig:
-                       assert_equal(choice_prob[m], 0.4)
+                       assert choice_prob[m] == 0.4
                    else:
-                       assert_equal(choice_prob[m], 0.2)
+                       assert choice_prob[m] == 0.2
             elif m in scheme.movers['repex']:
-                assert_equal(choice_prob[m], 0.1)
+                assert choice_prob[m] == 0.1
             else:
                 raise RuntimeError("Unknown mover "+repr(m))
 
-    @raises(KeyError)
     def test_choice_probability_bad_group_weights(self):
         scheme = MoveScheme(self.network)
         ens0 = self.network.sampling_transitions[0].ensembles[0]
@@ -656,9 +650,10 @@ def test_choice_probability_bad_group_weights(self):
         for groupname in list(scheme.movers.keys()):
             for mover in scheme.movers[groupname]:
                 mover_weights[(groupname, mover.ensemble_signature)] = 1.0
-        assert_equal(len(mover_weights), 5)
-        choice_prob = strategy.choice_probability(scheme, group_weights,
-                                                  mover_weights)
+        assert len(mover_weights) == 5
+        with pytest.raises(KeyError):
+            choice_prob = strategy.choice_probability(scheme, group_weights,
+                                                      mover_weights)
 
     def test_weights_from_choice_probability(self):
         scheme = self.scheme_setup_shooting_repex()
@@ -679,8 +674,8 @@ def test_weights_from_choice_probability(self):
             scheme, choice_prob
         )
 
-        assert_equal(group_weights, group_w)
-        assert_equal(mover_weights, mover_w)
+        assert group_weights == group_w
+        assert mover_weights == mover_w
         #TODO: run more thorough tests of this
 
     def test_chooser_root_weights(self):
@@ -707,7 +702,7 @@ def test_chooser_root_weights(self):
                 mover_weights[(groupname, mover.ensemble_signature)] = 1.0
 
         w = strategy.chooser_root_weights(scheme, group_weights, mover_weights)
-        assert_equal(w, {'shooting' : 3.0, 'repex' : 1.0})
+        assert w == {'shooting' : 3.0, 'repex' : 1.0}
 
     def test_chooser_mover_weights(self):
         scheme = self.scheme_setup_shooting_repex()
@@ -717,7 +712,7 @@ def test_chooser_mover_weights(self):
         for groupname in list(scheme.movers.keys()):
             for mover in scheme.movers[groupname]:
                 mover_weights[(groupname, mover.ensemble_signature)] = 1.0
-    
+
         m_sig = {}
         for g in scheme.movers:
             for m in scheme.movers[g]:
@@ -726,7 +721,7 @@ def test_chooser_mover_weights(self):
         for g in scheme.movers:
             expected_w = {m : mover_weights[m_sig[m]] for m in scheme.movers[g]}
             w = strategy.chooser_mover_weights(scheme, g, mover_weights)
-            assert_equal(w, expected_w)
+            assert w == expected_w
 
 
     def test_make_movers(self):
@@ -756,40 +751,40 @@ def test_make_movers(self):
 
         strategy = OrganizeByMoveGroupStrategy()
         root = strategy.make_movers(scheme)
-        
-        assert_equal(len(root.movers), 4)
-        names = ['ShootingChooser', 'RepexChooser', 'PathreversalChooser', 
+
+        assert len(root.movers) == 4
+        names = ['ShootingChooser', 'RepexChooser', 'PathreversalChooser',
                  'MinusChooser']
         name_dict = {root.movers[i].name : i for i in range(len(root.movers))}
         for name in names:
-            assert_in(name, list(name_dict.keys()))
+            assert name in list(name_dict.keys())
 
         name = 'ShootingChooser'
         weight = root.weights[name_dict[name]]
         chooser = root.movers[name_dict[name]]
-        assert_equal(type(chooser), paths.RandomChoiceMover)
-        assert_equal(weight, 3.0)
-        assert_equal(len(chooser.movers), 3)
+        assert type(chooser) == paths.RandomChoiceMover
+        assert weight == 3.0
+        assert len(chooser.movers) == 3
         for w in chooser.weights:
-            assert_equal(w, 1.0)
+            assert w == 1.0
 
         name = 'RepexChooser'
         weight = root.weights[name_dict[name]]
         chooser = root.movers[name_dict[name]]
-        assert_equal(type(chooser), paths.RandomChoiceMover)
-        assert_equal(weight, 1.0)
-        assert_equal(len(chooser.movers), 2)
+        assert type(chooser) == paths.RandomChoiceMover
+        assert weight == 1.0
+        assert len(chooser.movers) == 2
         for w in chooser.weights:
-            assert_equal(w, 1.0)
+            assert w == 1.0
 
         name = 'MinusChooser'
         weight = root.weights[name_dict[name]]
         chooser = root.movers[name_dict[name]]
-        assert_equal(type(chooser), paths.RandomChoiceMover)
-        assert_equal(len(chooser.movers), 1)
-        assert_equal(weight, 0.2)
+        assert type(chooser) == paths.RandomChoiceMover
+        assert len(chooser.movers) == 1
+        assert weight == 0.2
         for w in chooser.weights:
-            assert_equal(w, 1.0)
+            assert w == 1.0
 
     def test_make_movers_unknown_group(self):
         scheme = MoveScheme(self.network)
@@ -809,11 +804,11 @@ def test_make_movers_unknown_group(self):
         name = 'BlahblahChooser'
         weight = root.weights[name_dict[name]]
         chooser = root.movers[name_dict[name]]
-        assert_equal(type(chooser), paths.RandomChoiceMover)
-        assert_equal(weight, 2.0)
-        assert_equal(len(chooser.movers), 2)
+        assert type(chooser) == paths.RandomChoiceMover
+        assert weight == 2.0
+        assert len(chooser.movers) == 2
         for w in chooser.weights:
-            assert_equal(w, 1.0)
+            assert w == 1.0
 
     def test_make_movers_custom_group(self):
         scheme = MoveScheme(self.network)
@@ -830,15 +825,15 @@ def test_make_movers_custom_group(self):
         root = strategy.make_movers(scheme)
 
         name_dict = {root.movers[i].name : i for i in range(len(root.movers))}
-        
+
         name = 'BlahblahblahChooser'
         weight = root.weights[name_dict[name]]
         chooser = root.movers[name_dict[name]]
-        assert_equal(type(chooser), paths.RandomChoiceMover)
-        assert_equal(weight, 4.0)
-        assert_equal(len(chooser.movers), 2)
+        assert type(chooser) == paths.RandomChoiceMover
+        assert weight == 4.0
+        assert len(chooser.movers) == 2
         for w in chooser.weights:
-            assert_equal(w, 1.0)
+            assert w == 1.0
 
     def test_get_weights_scheme_all_unset(self):
         strategy = OrganizeByMoveGroupStrategy()
@@ -847,49 +842,49 @@ def test_get_weights_scheme_all_unset(self):
         scheme.append(NearestNeighborRepExStrategy())
         scheme.append(OneWayShootingStrategy())
         root = scheme.move_decision_tree()
-        assert_equal(len(scheme.movers), 2)
+        assert len(scheme.movers) == 2
         all_movers = scheme.movers['shooting'] + scheme.movers['repex']
         all_movers_sigs = [m.ensemble_signature for m in all_movers]
-        assert_equal(strategy.group_weights, {})
-        assert_equal(strategy.mover_weights, {})
+        assert strategy.group_weights == {}
+        assert strategy.mover_weights == {}
 
         (group_weights, mover_weights) = strategy.get_weights(
             scheme=scheme,
             sorted_movers=scheme.movers
         )
-        assert_equal(group_weights, {'shooting' : 1.0, 'repex' : 0.5})
+        assert group_weights == {'shooting' : 1.0, 'repex' : 0.5}
 
         # check that the number of sigs in a each group matches the number
         # of movers in that group
         for group in list(group_weights.keys()):
-            mover_sigs = [sig for sig in list(mover_weights.keys()) 
+            mover_sigs = [sig for sig in list(mover_weights.keys())
                           if sig[0]==group]
-            assert_equal(len(mover_sigs), len(scheme.movers[group]))
+            assert len(mover_sigs) == len(scheme.movers[group])
 
         for sig in list(mover_weights.keys()):
-            assert_equal(mover_weights[sig], 1.0)
-            assert_in(sig[1], all_movers_sigs)
+            assert mover_weights[sig] == 1.0
+            assert sig[1] in all_movers_sigs
 
         # check that we can reuse these in a different scheme
         scheme2 = MoveScheme(self.network)
         scheme2.append(OneWayShootingStrategy())
         root = scheme2.move_decision_tree()
-        assert_equal(len(scheme2.movers), 1)
+        assert len(scheme2.movers) == 1
 
         (group_weights, mover_weights) = strategy.get_weights(
             scheme=scheme2,
             sorted_movers=scheme2.movers
         )
-        assert_equal(group_weights, {'shooting' : 1.0})
+        assert group_weights == {'shooting' : 1.0}
         for sig in mover_weights:
-            assert_equal(mover_weights[sig], 1.0)
-            assert_in(sig[1], [m.ensemble_signature 
-                               for m in scheme2.movers[sig[0]]])
+            assert mover_weights[sig] == 1.0
+            assert sig[1] in [m.ensemble_signature
+                              for m in scheme2.movers[sig[0]]]
 
         for group in list(scheme2.movers.keys()):
             mover_sigs = [sig for sig in list(mover_weights.keys())
                           if sig[0]==group]
-            assert_equal(len(mover_sigs), len(scheme2.movers[group]))
+            assert len(mover_sigs) == len(scheme2.movers[group])
 
     def test_get_weights_both_internal_weights_set(self):
         strategy = OrganizeByMoveGroupStrategy()
@@ -897,18 +892,18 @@ def test_get_weights_both_internal_weights_set(self):
         ensA_sig = ((ensA,),(ensA,))
 
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
-                       OneWayShootingStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
+                       OneWayShootingStrategy(),
                        strategy])
         root = scheme.move_decision_tree()
         old_choice_probability = scheme.choice_probability
-        old_shooter_ensA = [m for m in scheme.movers['shooting'] 
+        old_shooter_ensA = [m for m in scheme.movers['shooting']
                             if m.ensemble_signature == ensA_sig][0]
 
         strategy.group_weights['repex'] = 3.0
         strategy.mover_weights[('shooting',ensA_sig)]= 2.0
         scheme.choice_probability = {} # this is unset here
- 
+
         (group_weights, mover_weights) = strategy.get_weights(
             scheme=scheme,
             sorted_movers=scheme.movers,
@@ -917,7 +912,7 @@ def test_get_weights_both_internal_weights_set(self):
         )
         root = scheme.move_decision_tree(rebuild=True)
 
-        assert_equal(group_weights, {'shooting' : 1.0, 'repex' : 3.0})
+        assert group_weights == {'shooting' : 1.0, 'repex' : 3.0}
         expected_mover_weights = {}
         for group in scheme.movers:
             for mover in scheme.movers[group]:
@@ -929,27 +924,27 @@ def test_get_weights_both_internal_weights_set(self):
                 else:
                     expected_mover_weights[(group,sig)] = 1.0
 
-        assert_equal(mover_weights, expected_mover_weights)
+        assert mover_weights == expected_mover_weights
 
         new_choice_probability = scheme.choice_probability
         new_repex_chooser = [m for m in root if m.name=="RepexChooser"][0]
         repex_chooser_idx = root.movers.index(new_repex_chooser)
-        assert_equal(root.weights[repex_chooser_idx],
-                     3.0*len(new_repex_chooser.movers))
+        assert (root.weights[repex_chooser_idx]
+                == 3.0*len(new_repex_chooser.movers))
 
         new_shoot_chooser = [m for m in root if m.name=="ShootingChooser"][0]
-        new_shooter_ensA = [m for m in scheme.movers['shooting'] 
+        new_shooter_ensA = [m for m in scheme.movers['shooting']
                             if m.ensemble_signature == ensA_sig][0]
         shooter_ensA_idx = new_shoot_chooser.movers.index(new_shooter_ensA)
-        assert_equal(new_shoot_chooser.weights[shooter_ensA_idx], 2.0)
+        assert new_shoot_chooser.weights[shooter_ensA_idx] == 2.0
 
-        assert_not_equal(new_choice_probability, old_choice_probability)
+        assert new_choice_probability != old_choice_probability
 
     def test_get_weights_group_weights_set(self):
         strategy = OrganizeByMoveGroupStrategy()
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
-                       OneWayShootingStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
+                       OneWayShootingStrategy(),
                        strategy])
         root = scheme.move_decision_tree()
 
@@ -965,19 +960,19 @@ def test_get_weights_group_weights_set(self):
         strategy.group_weights['shooting'] = 2.0
         root = scheme.move_decision_tree(rebuild=True)
 
-        assert_equal(strategy.group_weights, {'shooting' : 2.0, 'repex' : 0.5})
+        assert strategy.group_weights == {'shooting' : 2.0, 'repex' : 0.5}
 
         (group_weights, mover_weights) = strategy.get_weights(
             scheme=scheme,
             sorted_movers=scheme.movers,
             sort_weights_override=strategy.group_weights
         )
-        assert_equal(group_weights, {'shooting' : 2.0, 'repex' : 0.5})
+        assert group_weights == {'shooting' : 2.0, 'repex' : 0.5}
         # everything within the group should have the same mover_weight
         for group in scheme.movers:
             group_sigs = [s for s in mover_weights if s[0]==group]
             for sig in group_sigs:
-                assert_equal(mover_weights[sig], mover_weights[group_sigs[0]])
+                assert mover_weights[sig] == mover_weights[group_sigs[0]]
 
         choice_prob = strategy.choice_probability(
             scheme, group_weights, mover_weights
@@ -986,12 +981,12 @@ def test_get_weights_group_weights_set(self):
             assert_almost_equal(choice_prob[mover], old_div(1.0,7.0))
         for mover in scheme.movers['repex']:
             assert_almost_equal(choice_prob[mover], old_div(1.0,28.0))
-        
+
 
     def test_get_weights_mover_weights_set(self):
         strategy = OrganizeByMoveGroupStrategy()
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
                        OneWayShootingStrategy(),
                        strategy])
         # build it so there's an old version
@@ -1002,10 +997,10 @@ def test_get_weights_mover_weights_set(self):
             sorted_movers=scheme.movers,
             sort_weights_override=strategy.group_weights
         )
-        assert_equal(group_weights, {'shooting' : 1.0, 'repex' : 3.0})
+        assert group_weights == {'shooting' : 1.0, 'repex' : 3.0}
 
         ensA = self.network.sampling_transitions[0].ensembles[0]
-        ensA_sig = ((ensA,),(ensA,)) 
+        ensA_sig = ((ensA,),(ensA,))
         strategy.group_weights = {}
         strategy.mover_weights[('shooting',ensA_sig)] = 2.0
 
@@ -1018,9 +1013,9 @@ def test_get_weights_mover_weights_set(self):
 
         for sig in mover_weights:
             if sig == ('shooting',ensA_sig):
-                assert_equal(mover_weights[sig], 2.0)
+                assert mover_weights[sig] == 2.0
             elif sig[0] == 'shooting':
-                assert_equal(mover_weights[sig], 1.0)
+                assert mover_weights[sig] == 1.0
 
         assert_almost_equal(group_weights['shooting'], 1.0)
         assert_almost_equal(group_weights['repex'], 3.0)
@@ -1028,7 +1023,7 @@ def test_get_weights_mover_weights_set(self):
     def test_get_weights_internal_unset_choice_prob_set(self):
         strategy = OrganizeByMoveGroupStrategy()
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
                        OneWayShootingStrategy(),
                        strategy])
         ensA = self.network.sampling_transitions[0].ensembles[0]
@@ -1066,28 +1061,28 @@ def test_get_weights_internal_unset_choice_prob_set(self):
         for group in scheme.movers:
             for mover in scheme.movers[group]:
                 old_mover = [
-                    old for old in old_movers[group] 
+                    old for old in old_movers[group]
                     if mover.ensemble_signature==old.ensemble_signature
                 ][0]
-                assert_almost_equal(new_choice_prob[mover], 
+                assert_almost_equal(new_choice_prob[mover],
                                     old_choice_prob[old_mover])
 
         #print new_choice_prob
         for (old, new) in zip(list(old_mover_weights.keys()), list(mover_weights.keys())):
             try:
-                assert_equal(old_mover_weights[old], mover_weights[new])
+                assert old_mover_weights[old] == mover_weights[new]
             except AssertionError:
                 print(old_mover_weights[old])
                 print(mover_weights[new])
                 raise
-        assert_equal(old_mover_weights, mover_weights)
+        assert old_mover_weights == mover_weights
 
     def test_get_weights_mover_weights_set_no_shooting(self):
         # follows test_get_weights_mover_weights_set, replacing shooting
         # with path reversal
         strategy = OrganizeByMoveGroupStrategy()
         scheme = MoveScheme(self.network)
-        scheme.append([NearestNeighborRepExStrategy(), 
+        scheme.append([NearestNeighborRepExStrategy(),
                        PathReversalStrategy(),
                        strategy])
         strategy.group_weights['repex'] = 3.0
@@ -1098,9 +1093,9 @@ def test_get_weights_mover_weights_set_no_shooting(self):
             sorted_movers=scheme.movers,
             sort_weights_override=strategy.group_weights
         )
-        assert_equal(group_weights, {'pathreversal' : 1.0, 'repex' : 3.0})
+        assert group_weights == {'pathreversal' : 1.0, 'repex' : 3.0}
         ensA = self.network.sampling_transitions[0].ensembles[0]
-        ensA_sig = ((ensA,),(ensA,)) 
+        ensA_sig = ((ensA,),(ensA,))
         strategy.group_weights = {}
         strategy.mover_weights[('pathreversal',ensA_sig)] = 2.0
 
@@ -1114,9 +1109,9 @@ def test_get_weights_mover_weights_set_no_shooting(self):
             weight_sigA = mover_weights[('pathreversal',ensA_sig)]
             ratio = old_div(mover_weights[sig], weight_sigA)
             if sig == ('pathreversal',ensA_sig):
-                assert_equal(ratio, 1.0)
+                assert ratio == 1.0
             else:
-                assert_equal(ratio, 0.5)
+                assert ratio == 0.5
 
         assert_almost_equal(group_weights['pathreversal'], 1.0)
         assert_almost_equal(group_weights['repex'], 3.0)
@@ -1124,8 +1119,8 @@ def test_get_weights_mover_weights_set_no_shooting(self):
 
 class TestOrganizeByEnsembleStrategy(MoveStrategyTestSetup):
     StrategyClass = OrganizeByEnsembleStrategy
-    def setup(self):
-        super(TestOrganizeByEnsembleStrategy, self).setup()
+    def setup_method(self):
+        super(TestOrganizeByEnsembleStrategy, self).setup_method()
         scheme = MoveScheme(self.network)
         ens0 = self.network.sampling_transitions[0].ensembles[0]
         ens1 = self.network.sampling_transitions[0].ensembles[1]
@@ -1142,7 +1137,7 @@ def setup(self):
             paths.ReplicaExchangeMover(ens1, ens2)
         ]
         scheme.movers['pathreversal'] = [
-            paths.PathReversalMover(ensemble=ens) 
+            paths.PathReversalMover(ensemble=ens)
             for ens in [ens0, ens1, ens2]
         ]
         scheme.movers['minus'] = [paths.MinusMover(
@@ -1184,8 +1179,8 @@ def test_choice_probability(self):
         # minus: 1/9
 
         # ens0 moves: shooting (2.0), repex01 (1.0), minus (1.0), rev (1.0)
-        # ens1 moves: shooting (1.0), repex01 (1.0), repex12 (1.0), rev (1.0) 
-        # ens2 moves: shooting (1.0), repex12 (1.0), rev (1.0) 
+        # ens1 moves: shooting (1.0), repex01 (1.0), repex12 (1.0), rev (1.0)
+        # ens2 moves: shooting (1.0), repex12 (1.0), rev (1.0)
         # minus moves: minus (1.0)
 
         # ens0 norm: 5.0
@@ -1197,7 +1192,7 @@ def test_choice_probability(self):
         #   shooting0 = 4/9 * 2.0/5.0 = 8.0/45.0
         #   shooting1 = 2/9 * 1.0/4.0 = 1.0/18.0
         #   shooting2 = 2/9 * 1.0/3.0 = 2.0/27.0
-        #   repex01 = 4/9 * 1.0/5.0 + 2/9 * 1.0/4.0 = 13.0/90.0 
+        #   repex01 = 4/9 * 1.0/5.0 + 2/9 * 1.0/4.0 = 13.0/90.0
         #   repex12 = 2/9 * 1.0/4.0 + 2/9 * 1.0/3.0 = 7.0/54.0
         #   minus = 1/9*1.0 + 4/9*1.0/5.0 = 1.8/9.0
         #   rev0 = 4/9 * 1.0/5.0 = 4.0/45.0
@@ -1226,7 +1221,7 @@ def test_choice_probability(self):
             ('pathreversal', sig1) : old_div(1.0,18.0),
             ('pathreversal', sig2) : old_div(2.0,27.0)
         }
-        assert_equal(set(expected.keys()), set(found.keys()))
+        assert set(expected.keys()) == set(found.keys())
         for k in list(expected.keys()):
             assert_almost_equal(expected[k], found[k])
 
@@ -1242,17 +1237,17 @@ def test_chooser_mover_weights(self):
         (ensemble_weights, mover_weights) = strategy.default_weights(scheme)
 
         for ens in [ens0, ens1, ens2, minus]:
-            chooser_mweights = strategy.chooser_mover_weights(scheme, ens, 
+            chooser_mweights = strategy.chooser_mover_weights(scheme, ens,
                                                               mover_weights)
             for mover in chooser_mweights:
-                assert_in(ens, mover.ensemble_signature[0])
+                assert ens in mover.ensemble_signature[0]
 
             if ens in [ens0, ens1]:
-                assert_equal(len(chooser_mweights), 4)
+                assert len(chooser_mweights) == 4
             elif ens is minus:
-                assert_equal(len(chooser_mweights), 1)
+                assert len(chooser_mweights) == 1
             elif ens is ens2:
-                assert_equal(len(chooser_mweights), 3)
+                assert len(chooser_mweights) == 3
         # that test feels a little minimal, but I guess it does the job
 
     def test_default_weights(self):
@@ -1265,12 +1260,12 @@ def test_default_weights(self):
 
         (ensemble_weights, mover_weights)= strategy.default_weights(scheme)
 
-        assert_equal(ensemble_weights, 
-                     {e : 1.0 for e in [ens0, ens1, ens2, minus]})
+        assert ensemble_weights == {e : 1.0
+                                    for e in [ens0, ens1, ens2, minus]}
 
         # get the correct order on repex and minus signatures for testing
-        double_sigs = [m.ensemble_signature 
-                       for m in sum([scheme.movers[g] 
+        double_sigs = [m.ensemble_signature
+                       for m in sum([scheme.movers[g]
                                      for g in ['repex', 'minus']], [])
                       ]
         repex01_sig = reorder_ensemble_signature(((ens0,ens1),(ens0,ens1)),
@@ -1295,11 +1290,11 @@ def test_default_weights(self):
             ('minus', minus_sig, ens0),
         ]
 
-        assert_equal(len(mover_ens_sigs), len(list(mover_weights.keys())))
-        assert_equal(set(mover_weights.keys()), set(mover_ens_sigs))
-        
+        assert len(mover_ens_sigs) == len(list(mover_weights.keys()))
+        assert set(mover_weights.keys()) == set(mover_ens_sigs)
+
         expected = {s : 1.0 for s in mover_ens_sigs}
-        assert_equal(expected, mover_weights)
+        assert expected == mover_weights
 
     def test_weights_from_choice_probability(self):
         scheme = self.scheme
@@ -1343,10 +1338,10 @@ def test_make_movers(self):
 
         choosers = root.movers
         chooser_weights = root.weights
-        assert_equal(list(chooser_weights), [1.0]*4)
+        assert list(chooser_weights) == [1.0]*4
         for (mover, w) in zip(choosers, chooser_weights):
             n_moves = len(mover.movers)
-            assert_equal(mover.weights, [1.0]*n_moves)
+            assert mover.weights == [1.0]*n_moves
 
 
     def test_make_mover_rebuild_choice_probability(self):
@@ -1406,22 +1401,22 @@ def test_chooser_mover_weights(self):
         (ens_w, mov_w) = strategy.get_weights(scheme, scheme.movers,
                                               strategy.ensemble_weights,
                                               strategy.mover_weights)
-        scheme.choice_probability = strategy.choice_probability(scheme, 
+        scheme.choice_probability = strategy.choice_probability(scheme,
                                                                 ens_w, mov_w)
         for ens in [ens0, ens1, ens2, minus]:
-            chooser_mweights = strategy.chooser_mover_weights(scheme, ens, 
+            chooser_mweights = strategy.chooser_mover_weights(scheme, ens,
                                                               mover_weights)
             if ens in [ens0, ens1]:
-                assert_equal(len(chooser_mweights), 5)
+                assert len(chooser_mweights) == 5
             elif ens is minus:
-                assert_equal(len(chooser_mweights), 2)
+                assert len(chooser_mweights) == 2
             elif ens is ens2:
-                assert_equal(len(chooser_mweights), 4)
+                assert len(chooser_mweights) == 4
 
-            real_movers = [m for m in list(chooser_mweights.keys()) 
+            real_movers = [m for m in list(chooser_mweights.keys())
                            if m != strategy.null_mover]
             for m in real_movers:
-                assert_equal(scheme.choice_probability[m], chooser_mweights[m])
+                assert scheme.choice_probability[m] == chooser_mweights[m]
             assert_almost_equal(sum(chooser_mweights.values()), 1.0)
 
     def test_make_movers(self):
@@ -1429,7 +1424,7 @@ def test_make_movers(self):
         strategy = self.StrategyClass()
         root = strategy.make_movers(scheme)
 
-        assert_equal(len(root.movers), 4)
+        assert len(root.movers) == 4
 
         for mover in list(scheme.choice_probability.keys()):
             assert_almost_equal(
diff --git a/openpathsampling/tests/test_ms_outer_interface.py b/openpathsampling/tests/test_ms_outer_interface.py
index 9ad8e0fac..7c9af2ed2 100644
--- a/openpathsampling/tests/test_ms_outer_interface.py
+++ b/openpathsampling/tests/test_ms_outer_interface.py
@@ -1,8 +1,5 @@
 from __future__ import absolute_import
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises)
-from nose.plugins.skip import Skip, SkipTest
 from .test_helpers import (
     true_func, assert_equal_array_array, make_1d_traj, data_filename,
     assert_items_equal
@@ -18,10 +15,10 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestMSOuterTISInterface(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv_inc = paths.FunctionCV(name="inc", f=lambda s: s.xyz[0][0])
-        self.cv_dec = paths.FunctionCV(name="dec", 
+        self.cv_dec = paths.FunctionCV(name="dec",
                                         f=lambda s: 1.0-s.xyz[0][0])
         self.lambdas = [0.0, 0.1, 0.2, 0.3]
         self.interfaces_inc = paths.VolumeInterfaceSet(cv=self.cv_inc,
@@ -62,37 +59,38 @@ def test_initialization(self):
         by_lambda = self.ms_outer
         explicit = self.ms_outer_explicit
         for iface_set in explicit.interface_sets:
-            assert_equal(by_lambda.volume_for_interface_set(iface_set),
-                         explicit.volume_for_interface_set(iface_set))
-            assert_equal(by_lambda.lambda_for_interface_set(iface_set),
-                         explicit.lambda_for_interface_set(iface_set))
+            assert (by_lambda.volume_for_interface_set(iface_set)
+                    == explicit.volume_for_interface_set(iface_set))
+            assert (by_lambda.lambda_for_interface_set(iface_set)
+                    == explicit.lambda_for_interface_set(iface_set))
 
-        assert_equal(len(explicit.volumes), len(by_lambda.volumes))
-        assert_equal(len(explicit.lambdas), len(by_lambda.lambdas))
+        assert len(explicit.volumes) == len(by_lambda.volumes)
+        assert len(explicit.lambdas) == len(by_lambda.lambdas)
         assert_items_equal(set(explicit.interface_sets),
                            set(by_lambda.interface_sets))
 
 
     def test_volume_for_interface_set(self):
-        assert_equal(
-            self.ms_outer.volume_for_interface_set(self.interfaces_inc),
-            self.volumes[0]
+        assert (
+            self.ms_outer.volume_for_interface_set(self.interfaces_inc)
+            == self.volumes[0]
         )
-        assert_equal(
-            self.ms_outer.volume_for_interface_set(self.interfaces_dec),
-            self.volumes[1]
+        assert (
+            self.ms_outer.volume_for_interface_set(self.interfaces_dec)
+            == self.volumes[1]
         )
 
     def test_lambda_for_interface_set(self):
-        assert_equal(
-            self.ms_outer.lambda_for_interface_set(self.interfaces_inc),
-            0.5
+        assert (
+            self.ms_outer.lambda_for_interface_set(self.interfaces_inc)
+            == 0.5
         )
-        assert_equal(
-            self.ms_outer.lambda_for_interface_set(self.interfaces_dec),
-            0.4
+        assert (
+            self.ms_outer.lambda_for_interface_set(self.interfaces_dec)
+            == 0.4
         )
 
+
     def test_relevant_transitions(self):
         extra_set = paths.VolumeInterfaceSet(self.cv_inc, 0.0, [0.2, 0.3])
         extra = paths.TISTransition(self.stateA, self.stateB, extra_set,
@@ -101,8 +99,8 @@ def test_relevant_transitions(self):
         # TODO: switch once network is working
         relevant = self.post_network.relevant_transitions(transitions)
         #relevant = self.ms_outer.relevant_transitions(transitions)
-        assert_equal(len(relevant), 2)
-        assert_equal(set(self.network.sampling_transitions), set(relevant))
+        assert len(relevant) == 2
+        assert set(self.network.sampling_transitions) == set(relevant)
 
     def test_make_ensemble(self):
         transitions = self.network.sampling_transitions
@@ -115,12 +113,12 @@ def test_make_ensemble(self):
         test_BXB = make_1d_traj([1.1, 0.5, 1.2])
         test_AXB = make_1d_traj([-0.1, 0.6, 1.1])
         test_BXA = make_1d_traj([1.1, 0.5, -0.1])
-        assert_equal(ensemble(test_AA), False)
-        assert_equal(ensemble(test_AXA), True)
-        assert_equal(ensemble(test_BB), False)
-        assert_equal(ensemble(test_BXB), True)
-        assert_equal(ensemble(test_BXA), True)
-        assert_equal(ensemble(test_AXB), True)
+        assert ensemble(test_AA) is False
+        assert ensemble(test_AXA) is True
+        assert ensemble(test_BB) is False
+        assert ensemble(test_BXB) is True
+        assert ensemble(test_BXA) is True
+        assert ensemble(test_AXB) is True
 
     def test_make_ensemble_with_forbidden(self):
         forbidden = paths.CVDefinedVolume(self.cv_inc, 0.55, 0.65)
@@ -138,13 +136,13 @@ def test_make_ensemble_with_forbidden(self):
         test_AFB = make_1d_traj([-0.1, 0.6, 1.1])
         test_BXA = make_1d_traj([1.1, 0.5, -0.1])
         test_BFA = make_1d_traj([1.1, 0.6, -0.1])
-        assert_equal(ensemble(test_AA), False)
-        assert_equal(ensemble(test_AXA), True)
-        assert_equal(ensemble(test_BB), False)
-        assert_equal(ensemble(test_BXB), True)
-        assert_equal(ensemble(test_BXA), True)
-        assert_equal(ensemble(test_AXB), True)
-        assert_equal(ensemble(test_AFA), False)
-        assert_equal(ensemble(test_BFB), False)
-        assert_equal(ensemble(test_AFB), False)
-        assert_equal(ensemble(test_BFA), False)
+        assert ensemble(test_AA) is False
+        assert ensemble(test_AXA) is True
+        assert ensemble(test_BB) is False
+        assert ensemble(test_BXB) is True
+        assert ensemble(test_BXA) is True
+        assert ensemble(test_AXB) is True
+        assert ensemble(test_AFA) is False
+        assert ensemble(test_BFB) is False
+        assert ensemble(test_AFB) is False
+        assert ensemble(test_BFA) is False
diff --git a/openpathsampling/tests/test_netcdfplus_base.py b/openpathsampling/tests/test_netcdfplus_base.py
index 1146350b9..bda78e0d8 100644
--- a/openpathsampling/tests/test_netcdfplus_base.py
+++ b/openpathsampling/tests/test_netcdfplus_base.py
@@ -3,7 +3,7 @@
 from openpathsampling.netcdfplus.base import *
 
 class TestStorableNamedObject(object):
-    def setup(self):
+    def setup_method(self):
         self.obj = StorableNamedObject()
 
     def test_named(self):
diff --git a/openpathsampling/tests/test_network.py b/openpathsampling/tests/test_network.py
index 3ab91522f..0b26adabf 100644
--- a/openpathsampling/tests/test_network.py
+++ b/openpathsampling/tests/test_network.py
@@ -3,9 +3,6 @@
 from builtins import object
 import numpy as np
 
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises)
-from nose.plugins.skip import Skip, SkipTest
 from .test_helpers import (
     true_func, assert_equal_array_array, make_1d_traj, data_filename
 )
@@ -25,7 +22,7 @@
 
 class TestMultipleStateTIS(object):
     # generic class to set up states and ifaces
-    def setup(self):
+    def setup_method(self):
         # need to clear this before each run, otherwise it saves the
         # previous setup
         paths.InterfaceSet._reset()
@@ -91,8 +88,8 @@ def setup(self):
 
 
 class TestMSTISNetwork(TestMultipleStateTIS):
-    def setup(self):
-        super(TestMSTISNetwork, self).setup()
+    def setup_method(self):
+        super(TestMSTISNetwork, self).setup_method()
 
         ifacesA = self.ifacesA[:-1]
         ifacesB = self.ifacesB[:-1]
@@ -124,37 +121,37 @@ def test_set_fluxes(self):
             myflux = {self.stateA: 2.0,
                       self.stateB: 4.0,
                       self.stateC: 5.0}[trans[0]]
-            assert_equal(self.mstis.transitions[trans]._flux, myflux)
+            assert self.mstis.transitions[trans]._flux == myflux
 
     def test_all_states(self):
-        assert_equal(set(self.mstis.all_states),
-                     set([self.stateA, self.stateB, self.stateC]))
+        assert (set(self.mstis.all_states)
+                == set([self.stateA, self.stateB, self.stateC]))
 
     def test_trajectories(self):
         # TODO; make this test fully comprehensive? (loop over all
         # possibilities?)
         ensA0 = self.mstis.from_state[self.stateA].ensembles[0]
         ensAm = self.mstis.from_state[self.stateA].ensembles[-1]
-        assert_equal(ensA0(self.traj['AA']), True)
-        assert_equal(ensAm(self.traj['AA']), False)
-        assert_equal(ensAm(self.traj['AB']), True)
+        assert ensA0(self.traj['AA'])
+        assert not ensAm(self.traj['AA'])
+        assert ensAm(self.traj['AB'])
         ensB0 = self.mstis.from_state[self.stateB].ensembles[0]
-        assert_equal(ensB0(self.traj['AB']), False)
-        assert_equal(ensB0(self.traj['BA']), True)
-        assert_equal(ensB0(self.traj['BB']), True)
-        assert_equal(ensB0(self.traj['BC']), True)
-        assert_equal(ensB0(self.traj['AC']), False)
+        assert not ensB0(self.traj['AB'])
+        assert ensB0(self.traj['BA'])
+        assert ensB0(self.traj['BB'])
+        assert ensB0(self.traj['BC'])
+        assert not ensB0(self.traj['AC'])
         ensC0 = self.mstis.from_state[self.stateC].ensembles[0]
-        assert_equal(ensC0(self.traj['CC']), True)
-        assert_equal(ensC0(self.traj['CB']), True)
-        assert_equal(ensC0(self.traj['CA']), True)
-        assert_equal(ensC0(self.traj['BC']), False)
-        assert_equal(ensC0(self.traj['AC']), False)
-        assert_equal(ensC0(self.traj['BB']), False)
+        assert ensC0(self.traj['CC'])
+        assert ensC0(self.traj['CB'])
+        assert ensC0(self.traj['CA'])
+        assert not ensC0(self.traj['BC'])
+        assert not ensC0(self.traj['AC'])
+        assert not ensC0(self.traj['BB'])
 
     def test_ms_outers(self):
         for traj_label in ['AB', 'BA', 'AC', 'CA', 'BC', 'CB']:
-            assert_equal(self.mstis.ms_outers[0](self.traj[traj_label]), True)
+            assert self.mstis.ms_outers[0](self.traj[traj_label])
 
     def test_minus_ensembles(self):
         good_traj_seq = [-0.51, -0.49, -0.40, -0.52, -0.48, -0.51]  # AXXAXA
@@ -169,50 +166,49 @@ def test_minus_ensembles(self):
         good_minus_traj = make_1d_traj(good_traj_seq)
         bad_minus_traj = make_1d_traj(bad_traj_seq)
         # test that the call works
-        assert_equal(minus_A(good_minus_traj), True)
-        assert_equal(minus_A(bad_minus_traj), False)
+        assert minus_A(good_minus_traj)
+        assert not minus_A(bad_minus_traj)
         # test that the can_append works for the good traj
         good_building_traj = paths.Trajectory([])
         for (i, snap) in enumerate(good_minus_traj):
             good_building_traj.append(snap)
             if not minus_A.can_append(good_building_traj, trusted=True):
                 break
-        assert_equal(len(good_building_traj), len(good_minus_traj))
+        assert len(good_building_traj) == len(good_minus_traj)
         # test that the can_append works for the bad traj
         bad_building_traj = paths.Trajectory([])
         for (i, snap) in enumerate(bad_minus_traj):
             bad_building_traj.append(snap)
             if not minus_A.can_append(bad_building_traj, trusted=True):
                 break
-        assert_equal(len(bad_building_traj), 3)
+        assert len(bad_building_traj) == 3
 
     def test_sampling_ensembles(self):
-        assert_equal(len(self.mstis.from_state[self.stateA].ensembles), 2)
-        assert_equal(len(self.mstis.from_state[self.stateB].ensembles), 2)
-        assert_equal(len(self.mstis.from_state[self.stateC].ensembles), 2)
+        assert len(self.mstis.from_state[self.stateA].ensembles) == 2
+        assert len(self.mstis.from_state[self.stateB].ensembles) == 2
+        assert len(self.mstis.from_state[self.stateC].ensembles) == 2
 
         # test that .sampling_ensembles is as expected
-        assert_equal(len(self.mstis.sampling_ensembles), 6)
+        assert len(self.mstis.sampling_ensembles) == 6
         all_sampling_ens = sum(
             [self.mstis.from_state[state].ensembles
              for state in [self.stateA, self.stateB, self.stateC]],
             []
         )
-        assert_equal(set(self.mstis.sampling_ensembles),
-                     set(all_sampling_ens))
+        assert set(self.mstis.sampling_ensembles) == set(all_sampling_ens)
 
         # test that sampling ensembles has cv_max set
-        assert_equal(len(paths.InterfaceSet._cv_max_dict), 1)
+        assert len(paths.InterfaceSet._cv_max_dict) == 1
         cv_max = list(paths.InterfaceSet._cv_max_dict.values())[0]
         for transition in self.mstis.sampling_transitions:
-            assert_equal(transition.interfaces.cv_max, cv_max)
+            assert transition.interfaces.cv_max == cv_max
         for ens in self.mstis.sampling_ensembles:
-            assert_equal(ens.cv_max, cv_max)
+            assert ens.cv_max == cv_max
 
     def test_autonaming(self):
-        assert_equal(self.stateA.name, "A")
-        assert_equal(self.stateB.name, "B")
-        assert_equal(self.stateC.name, "C")
+        assert self.stateA.name == "A"
+        assert self.stateB.name == "B"
+        assert self.stateC.name == "C"
 
         # check that (1) given names stay unchanged; (2) code knows to skip
         # over any default names that have been assigned (i.e., it renames
@@ -235,14 +231,14 @@ def test_autonaming(self):
             (self.stateB, ifacesB),
             (self.stateC, ifacesC)
         ])
-        assert_equal(self.stateA.name, "B")
-        assert_equal(self.stateB.name, "A")
-        assert_equal(self.stateC.name, "C")
+        assert self.stateA.name == "B"
+        assert self.stateB.name == "A"
+        assert self.stateC.name == "C"
 
 
 class TestMISTISNetwork(TestMultipleStateTIS):
-    def setup(self):
-        super(TestMISTISNetwork, self).setup()
+    def setup_method(self):
+        super(TestMISTISNetwork, self).setup_method()
 
         ifacesA = self.ifacesA[:-1]
         ifacesB = self.ifacesB[:-1]
@@ -260,36 +256,35 @@ def setup(self):
         )
 
     def test_initialization(self):
-        assert_equal(len(self.mistis.sampling_transitions), 3)
-        assert_equal(len(self.mistis.input_transitions), 3)
-        assert_equal(len(self.mistis.transitions), 3)
+        assert len(self.mistis.sampling_transitions) == 3
+        assert len(self.mistis.input_transitions) == 3
+        assert len(self.mistis.transitions) == 3
         transitions = self.mistis.transitions
-        assert_equal(len(transitions[self.stateA, self.stateB].ensembles), 2)
-        assert_equal(len(transitions[self.stateB, self.stateA].ensembles), 2)
-        assert_equal(len(transitions[self.stateA, self.stateC].ensembles), 3)
+        assert len(transitions[self.stateA, self.stateB].ensembles) == 2
+        assert len(transitions[self.stateB, self.stateA].ensembles) == 2
+        assert len(transitions[self.stateA, self.stateC].ensembles) == 3
         # TODO: add more checks here
 
     def test_sampling_ensembles(self):
-        assert_equal(len(self.mistis.sampling_ensembles), 7)
+        assert len(self.mistis.sampling_ensembles) == 7
         all_sampling_ens = sum(
             [t.ensembles for t in self.mistis.sampling_transitions], []
         )
-        assert_equal(set(self.mistis.sampling_ensembles),
-                     set(all_sampling_ens))
+        assert set(self.mistis.sampling_ensembles) == set(all_sampling_ens)
         # test that sampling ensembles has cv_max set
-        assert_equal(len(paths.InterfaceSet._cv_max_dict), 1)
+        assert len(paths.InterfaceSet._cv_max_dict) == 1
         cv_max = list(paths.InterfaceSet._cv_max_dict.values())[0]
         for transition in self.mistis.sampling_transitions:
-            assert_equal(transition.interfaces.cv_max, cv_max)
+            assert transition.interfaces.cv_max == cv_max
         for ens in self.mistis.sampling_ensembles:
-            assert_equal(ens.cv_max, cv_max)
+            assert ens.cv_max == cv_max
 
     def test_ms_outers(self):
         ms_outer_ens = self.mistis.ms_outers[0]
         for traj_label in ['AB', 'BA']:
-            assert_equal(ms_outer_ens(self.traj[traj_label]), True)
+            assert ms_outer_ens(self.traj[traj_label])
         for traj_label in ['CB', 'CA']:
-            assert_equal(ms_outer_ens(self.traj[traj_label]), False)
+            assert not ms_outer_ens(self.traj[traj_label])
 
     def test_minus_ensembles(self):
         good_traj_seq = [-0.51, -0.49, -0.40, -0.52, -0.48, -0.51]  # AXXAXA
@@ -314,37 +309,37 @@ def test_set_fluxes(self):
         self.mistis.set_fluxes(flux_dict)
         for (A, B) in self.mistis.transitions:
             if A == self.stateA:
-                assert_equal(self.mistis.transitions[(A, B)]._flux, 2.0)
+                assert self.mistis.transitions[(A, B)]._flux == 2.0
             elif A == self.stateB:
-                assert_equal(self.mistis.transitions[(A, B)]._flux, 4.0)
+                assert self.mistis.transitions[(A, B)]._flux == 4.0
 
     def test_trajectories_nonstrict(self):
         fromA = [trans for trans in self.mistis.sampling_transitions
                  if trans.stateA == self.stateA]
         fromB = [trans for trans in self.mistis.sampling_transitions
                  if trans.stateA == self.stateB]
-        assert_equal(len(fromA), 2)
-        assert_equal(len(fromB), 1)
+        assert len(fromA) == 2
+        assert len(fromB) == 1
         fromA_0 = fromA[0].ensembles[0]
         fromA_1 = fromA[1].ensembles[0]
         fromB_0 = fromB[0].ensembles[0]
-        assert_equal(fromA_0(self.traj['AA']), True)
-        assert_equal(fromA_0(self.traj['AB']), True)
-        assert_equal(fromA_0(self.traj['AC']), True)
-        assert_equal(fromA_0(self.traj['BB']), False)
-        assert_equal(fromA_0(self.traj['CB']), False)
-        assert_equal(fromA_0(self.traj['ABC']), False)
-        assert_equal(fromA_1(self.traj['AA']), True)
-        assert_equal(fromA_1(self.traj['AB']), True)
-        assert_equal(fromA_1(self.traj['AC']), True)
-        assert_equal(fromA_1(self.traj['CB']), False)
-        assert_equal(fromA_1(self.traj['CB']), False)
-        assert_equal(fromA_1(self.traj['ABC']), False)
-        assert_equal(fromB_0(self.traj['BA']), True)
-        assert_equal(fromB_0(self.traj['BB']), True)
-        assert_equal(fromB_0(self.traj['BB']), True)
-        assert_equal(fromB_0(self.traj['AB']), False)
-        assert_equal(fromB_0(self.traj['CB']), False)
+        assert fromA_0(self.traj['AA'])
+        assert fromA_0(self.traj['AB'])
+        assert fromA_0(self.traj['AC'])
+        assert not fromA_0(self.traj['BB'])
+        assert not fromA_0(self.traj['CB'])
+        assert not fromA_0(self.traj['ABC'])
+        assert fromA_1(self.traj['AA'])
+        assert fromA_1(self.traj['AB'])
+        assert fromA_1(self.traj['AC'])
+        assert not fromA_1(self.traj['CB'])
+        assert not fromA_1(self.traj['CB'])
+        assert not fromA_1(self.traj['ABC'])
+        assert fromB_0(self.traj['BA'])
+        assert fromB_0(self.traj['BB'])
+        assert fromB_0(self.traj['BB'])
+        assert not fromB_0(self.traj['AB'])
+        assert not fromB_0(self.traj['CB'])
 
     def test_trajectories_strict(self):
         strict = MISTISNetwork([
@@ -364,23 +359,23 @@ def test_trajectories_strict(self):
         ensAB = transAB.ensembles[0]
         ensAC = transAC.ensembles[0]
         ensBA = transBA.ensembles[0]
-        assert_equal(ensAB(self.traj['AA']), True)
-        assert_equal(ensAB(self.traj['AB']), True)
-        assert_equal(ensAB(self.traj['AC']), False)
-        assert_equal(ensAB(self.traj['BB']), False)
-        assert_equal(ensAB(self.traj['BC']), False)
-        assert_equal(ensAB(self.traj['ABC']), False)
-        assert_equal(ensAC(self.traj['AA']), True)
-        assert_equal(ensAC(self.traj['AC']), True)
-        assert_equal(ensAC(self.traj['AB']), False)
-        assert_equal(ensAC(self.traj['BB']), False)
-        assert_equal(ensAC(self.traj['BC']), False)
-        assert_equal(ensAC(self.traj['ABC']), False)
-        assert_equal(ensBA(self.traj['BB']), True)
-        assert_equal(ensBA(self.traj['BA']), True)
-        assert_equal(ensBA(self.traj['BC']), False)
-        assert_equal(ensBA(self.traj['AB']), False)
-        assert_equal(ensBA(self.traj['AC']), False)
+        assert ensAB(self.traj['AA'])
+        assert ensAB(self.traj['AB'])
+        assert not ensAB(self.traj['AC'])
+        assert not ensAB(self.traj['BB'])
+        assert not ensAB(self.traj['BC'])
+        assert not ensAB(self.traj['ABC'])
+        assert ensAC(self.traj['AA'])
+        assert ensAC(self.traj['AC'])
+        assert not ensAC(self.traj['AB'])
+        assert not ensAC(self.traj['BB'])
+        assert not ensAC(self.traj['BC'])
+        assert not ensAC(self.traj['ABC'])
+        assert ensBA(self.traj['BB'])
+        assert ensBA(self.traj['BA'])
+        assert not ensBA(self.traj['BC'])
+        assert not ensBA(self.traj['AB'])
+        assert not ensBA(self.traj['AC'])
 
     def test_storage(self):
         import os
@@ -397,9 +392,9 @@ def test_storage(self):
 
         storage_r = paths.AnalysisStorage(fname)
         reloaded = storage_r.networks[0]
-        assert_equal(reloaded.strict_sampling, False)
-        assert_equal(reloaded.sampling_transitions[0].ensembles[0],
-                     self.mistis.sampling_transitions[0].ensembles[0])
+        assert not reloaded.strict_sampling
+        assert (reloaded.sampling_transitions[0].ensembles[0]
+                == self.mistis.sampling_transitions[0].ensembles[0])
 
         storage_w.close()
         storage_r.close()
@@ -411,7 +406,7 @@ class TestTPSNetwork(object):
     NetworkType = TPSNetwork
     std_kwargs = {}
 
-    def setup(self):
+    def setup_method(self):
         from .test_helpers import CallIdentity
         xval = paths.FunctionCV("xval", lambda snap: snap.xyz[0][0])
         self.stateA = paths.CVDefinedVolume(xval, float("-inf"), -0.5)
@@ -471,29 +466,28 @@ def network3c(self):
 
     def test_initialization_2state(self):
         network2a = self.network2a
-        assert_equal(len(network2a.sampling_transitions), 1)
-        assert_equal(len(network2a.transitions), 1)
+        assert len(network2a.sampling_transitions) == 1
+        assert len(network2a.transitions) == 1
         network2b = self.network2b
-        assert_equal(len(network2b.sampling_transitions), 1)
-        assert_equal(len(network2b.transitions), 1)
+        assert len(network2b.sampling_transitions) == 1
+        assert len(network2b.transitions) == 1
         network2c = self.network2c
-        assert_equal(len(network2c.sampling_transitions), 1)
-        assert_equal(len(network2c.transitions), 1)
-        assert_equal(set(network2a.all_states), set(network2b.all_states))
-        assert_equal(set(network2b.all_states), set(network2c.all_states))
-        assert_equal(set(network2a.all_states),
-                     set([self.stateA, self.stateB]))
+        assert len(network2c.sampling_transitions) == 1
+        assert len(network2c.transitions) == 1
+        assert set(network2a.all_states) == set(network2b.all_states)
+        assert set(network2b.all_states) == set(network2c.all_states)
+        assert set(network2a.all_states) == set([self.stateA, self.stateB])
 
     def test_initialization_3state(self):
         network3a = self.network3a
-        assert_equal(len(network3a.sampling_transitions), 1)
-        assert_equal(len(network3a.transitions), 6)
+        assert len(network3a.sampling_transitions) == 1
+        assert len(network3a.transitions) == 6
         network3b = self.network3b
-        assert_equal(len(network3b.sampling_transitions), 1)
-        assert_equal(len(network3b.transitions), 6)
+        assert len(network3b.sampling_transitions) == 1
+        assert len(network3b.transitions) == 6
         network3c = self.network3c
-        assert_equal(len(network3c.sampling_transitions), 1)
-        assert_equal(len(network3c.transitions), 6)
+        assert len(network3c.sampling_transitions) == 1
+        assert len(network3c.transitions) == 6
 
     def test_storage(self):
         import os
@@ -511,8 +505,8 @@ def test_storage(self):
         network_a = self.NetworkType(initial_states=states,
                                      final_states=states,
                                      **self.std_kwargs)
-        assert_equal(len(network_a.sampling_transitions), 1)
-        assert_equal(len(network_a.transitions), 6)
+        assert len(network_a.sampling_transitions) == 1
+        assert len(network_a.transitions) == 6
         storage_w = paths.storage.Storage(fname, "w")
         storage_w.snapshots.save(self.template)
         storage_w.save(network_a)
@@ -520,8 +514,8 @@ def test_storage(self):
 
         storage_r = paths.storage.AnalysisStorage(fname)
         network_b = storage_r.networks[0]
-        assert_equal(len(network_b.sampling_transitions), 1)
-        assert_equal(len(network_b.transitions), 6)
+        assert len(network_b.sampling_transitions) == 1
+        assert len(network_b.transitions) == 6
 
         if os.path.isfile(fname):
             os.remove(fname)
@@ -530,48 +524,48 @@ def test_allow_self_transitions_false(self):
         network = self.NetworkType.from_states_all_to_all(
             self.states, allow_self_transitions=False, **self.std_kwargs
         )
-        assert_equal(len(network.sampling_ensembles), 1)
+        assert len(network.sampling_ensembles) == 1
         ensemble = network.sampling_ensembles[0]
-        assert_equal(ensemble(self.traj['AA']), False)
-        assert_equal(ensemble(self.traj['AB']), True)
-        assert_equal(ensemble(self.traj['BA']), True)
-        assert_equal(ensemble(self.traj['BC']), True)
-        assert_equal(ensemble(self.traj['CA']), True)
-        assert_equal(ensemble(self.traj['BB']), False)
-        assert_equal(ensemble(self.traj['CC']), False)
+        assert not ensemble(self.traj['AA'])
+        assert ensemble(self.traj['AB'])
+        assert ensemble(self.traj['BA'])
+        assert ensemble(self.traj['BC'])
+        assert ensemble(self.traj['CA'])
+        assert not ensemble(self.traj['BB'])
+        assert not ensemble(self.traj['CC'])
 
     def test_allow_self_transitions_true(self):
         network = self.NetworkType.from_states_all_to_all(
             self.states, allow_self_transitions=True, **self.std_kwargs
         )
-        assert_equal(len(network.sampling_ensembles), 1)
+        assert len(network.sampling_ensembles) == 1
         ensemble = network.sampling_ensembles[0]
-        assert_equal(ensemble(self.traj['AA']), True)
-        assert_equal(ensemble(self.traj['AB']), True)
-        assert_equal(ensemble(self.traj['BA']), True)
-        assert_equal(ensemble(self.traj['BC']), True)
-        assert_equal(ensemble(self.traj['CA']), True)
-        assert_equal(ensemble(self.traj['BB']), True)
-        assert_equal(ensemble(self.traj['CC']), True)
+        assert ensemble(self.traj['AA'])
+        assert ensemble(self.traj['AB'])
+        assert ensemble(self.traj['BA'])
+        assert ensemble(self.traj['BC'])
+        assert ensemble(self.traj['CA'])
+        assert ensemble(self.traj['BB'])
+        assert ensemble(self.traj['CC'])
 
     def test_allow_self_transitions_false_ABX(self):
         network = self.NetworkType.from_states_all_to_all(
             self.states, allow_self_transitions=False, **self.std_kwargs
         )
-        assert_equal(len(network.sampling_ensembles), 1)
+        assert len(network.sampling_ensembles) == 1
         ensemble = network.sampling_ensembles[0]
-        assert_equal(ensemble(self.traj['AB0']), False)
-        assert_equal(ensemble(self.traj['ABA']), False)
+        assert not ensemble(self.traj['AB0'])
+        assert not ensemble(self.traj['ABA'])
 
     def test_allow_self_transitions_true_ABX(self):
         # special case that differs between fixed and flexible
         network = self.NetworkType.from_states_all_to_all(
             self.states, allow_self_transitions=True, **self.std_kwargs
         )
-        assert_equal(len(network.sampling_ensembles), 1)
+        assert len(network.sampling_ensembles) == 1
         ensemble = network.sampling_ensembles[0]
-        assert_equal(ensemble(self.traj['AB0']), False)
-        assert_equal(ensemble(self.traj['ABA']), False)
+        assert not ensemble(self.traj['AB0'])
+        assert not ensemble(self.traj['ABA'])
 
 
 class TestFixedLengthTPSNetwork(TestTPSNetwork):
@@ -581,14 +575,14 @@ class TestFixedLengthTPSNetwork(TestTPSNetwork):
     def test_lengths(self):
         for network in [self.network2a, self.network2b, self.network2c,
                         self.network3a, self.network3b, self.network3c]:
-            assert_equal(network.sampling_transitions[0].length, 4)
-            assert_equal(list(network.transitions.values())[0].length, 4)
+            assert network.sampling_transitions[0].length == 4
+            assert list(network.transitions.values())[0].length == 4
 
     def test_allow_self_transitions_true_ABX(self):
         network = self.NetworkType.from_states_all_to_all(
             self.states, allow_self_transitions=True, **self.std_kwargs
         )
-        assert_equal(len(network.sampling_ensembles), 1)
+        assert len(network.sampling_ensembles) == 1
         ensemble = network.sampling_ensembles[0]
-        assert_equal(ensemble(self.traj['AB0']), False)
-        assert_equal(ensemble(self.traj['ABA']), True)
+        assert not ensemble(self.traj['AB0'])
+        assert ensemble(self.traj['ABA'])
diff --git a/openpathsampling/tests/test_openmm_engine.py b/openpathsampling/tests/test_openmm_engine.py
index d3a32ad6b..177594779 100644
--- a/openpathsampling/tests/test_openmm_engine.py
+++ b/openpathsampling/tests/test_openmm_engine.py
@@ -9,8 +9,6 @@
 from builtins import object
 from past.utils import old_div
 import numpy as np
-from nose.tools import (assert_equal)
-from nose.plugins.skip import SkipTest
 
 from openpathsampling.integration_tools import openmm as mm
 
@@ -40,7 +38,7 @@
 def setup_module():
     global topology, template, system, nan_causing_template
     if not (u and mm and app and md):
-        raise SkipTest
+        pytest.skip("Missing requirements")
     template = peng.snapshot_from_pdb(data_filename("ala_small_traj.pdb"))
     topology = peng.to_openmm_topology(template)
 
@@ -73,7 +71,7 @@ def setup_module():
 
 
 class TestOpenMMEngine(object):
-    def setup(self):
+    def setup_method(self):
 
         # OpenMM Integrator
         integrator = mm.LangevinIntegrator(
@@ -102,12 +100,12 @@ def setup(self):
         context.setPositions(template.coordinates)
         context.setVelocities(u.Quantity(zero_array, old_div(u.nanometers, u.picoseconds)))
 
-    def teardown(self):
+    def teardown_method(self):
         pass
 
     def test_sanity(self):
-        assert_equal(self.engine.n_steps_per_frame, 2)
-        assert_equal(self.engine.n_frames_max, 5)
+        assert self.engine.n_steps_per_frame == 2
+        assert self.engine.n_frames_max == 5
         # TODO: add more sanity checks
         pass  # not quite a SkipTest, but a reminder to add more
 
@@ -126,7 +124,7 @@ def test_snapshot_set(self):
         testvel = []
         testpos = []
         for i in range(len(pdb_pos)):
-            testpos.append(list(np.array(pdb_pos[i]) + 
+            testpos.append(list(np.array(pdb_pos[i]) +
                                 np.array([1.0, 1.0, 1.0]))
                           )
             testvel.append([0.1*i, 0.1*i, 0.1*i])
@@ -161,8 +159,8 @@ def test_generate_next_frame(self):
         new_pos = old_div(new_snap.coordinates, u.nanometers)
         old_vel = old_div(snap0.velocities, (old_div(u.nanometers, u.picoseconds)))
         new_vel = old_div(new_snap.velocities, (old_div(u.nanometers, u.picoseconds)))
-        assert_equal(old_pos.shape, new_pos.shape)
-        assert_equal(old_vel.shape, new_vel.shape)
+        assert old_pos.shape == new_pos.shape
+        assert old_vel.shape == new_vel.shape
         assert_not_equal_array_array(old_pos, new_pos)
         assert_not_equal_array_array(old_vel, new_vel)
 
@@ -171,12 +169,12 @@ def test_generate(self):
             _ = self.engine.generate(self.engine.current_snapshot, [true_func])
         except dyn.EngineMaxLengthError as e:
             traj = e.last_trajectory
-            assert_equal(len(traj), self.engine.n_frames_max)
+            assert len(traj) == self.engine.n_frames_max
         else:
             raise RuntimeError('Did not have correct MaxLengthError')
 
     def test_snapshot_timestep(self):
-        assert_equal(self.engine.snapshot_timestep, 4 * u.femtoseconds)
+        assert self.engine.snapshot_timestep == 4 * u.femtoseconds
 
     @raises_with_message_like(paths.engines.EngineMaxLengthError,
                               "Hit maximal length")
@@ -224,15 +222,15 @@ def test_nan_rejected(self):
         change = mover.move(init_samp)
 
         assert (isinstance(change, paths.RejectedNaNSampleMoveChange))
-        assert_equal(change.details.rejection_reason, 'nan')
+        assert change.details.rejection_reason == 'nan'
         # since we shoot, we start with a shorter trajectory
         assert(len(change.samples[0].trajectory) < len(init_traj))
 
         newsamp = init_samp.apply_samples(change)
-        assert_equal(len(newsamp), 1)
+        assert len(newsamp) == 1
 
         # make sure there is no change!
-        assert_equal(init_samp[0].trajectory, init_traj)
+        assert init_samp[0].trajectory == init_traj
 
     def test_max_length_rejected(self):
         stateA = paths.EmptyVolume()  # will run indefinitely
@@ -256,15 +254,14 @@ def test_max_length_rejected(self):
         change = mover.move(init_samp)
 
         assert(isinstance(change, paths.RejectedMaxLengthSampleMoveChange))
-        assert_equal(change.details.rejection_reason, 'max_length')
-        assert_equal(
-            len(change.samples[0].trajectory), self.engine.n_frames_max)
+        assert change.details.rejection_reason == 'max_length'
+        assert len(change.samples[0].trajectory) == self.engine.n_frames_max
 
         newsamp = init_samp.apply_samples(change)
-        assert_equal(len(newsamp), 1)
+        assert len(newsamp) == 1
 
         # make sure there is no change!
-        assert_equal(init_samp[0].trajectory, init_traj)
+        assert init_samp[0].trajectory == init_traj
 
     @pytest.mark.parametrize('has_constraints', [True, False])
     def test_has_constraints(self, has_constraints):
diff --git a/openpathsampling/tests/test_openmm_snapshot.py b/openpathsampling/tests/test_openmm_snapshot.py
index a58c0875d..1afae80fc 100644
--- a/openpathsampling/tests/test_openmm_snapshot.py
+++ b/openpathsampling/tests/test_openmm_snapshot.py
@@ -3,9 +3,6 @@
 from builtins import object
 import openpathsampling.engines.openmm as omm_engine
 import openpathsampling as paths
-from nose.tools import (assert_equal, assert_almost_equal, assert_not_equal,
-                        assert_is_not, assert_is)
-from nose.plugins.skip import SkipTest
 from .test_helpers import data_filename, assert_close_unit, u
 
 import pytest
@@ -26,12 +23,12 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestOpenMMSnapshot(object):
-    def setup(self):
+    def setup_method(self):
         if not openmm:
-            raise SkipTest("OpenMM not installed")
+            pytest.skip("OpenMM not installed")
         if not omt:
-            raise SkipTest("OpenMMTools not installed; required for OpenMM "
-                           "tests.")
+            pytest.skip("OpenMMTools not installed; required for OpenMM "
+                        "tests.")
         self.test_system = omt.testsystems.AlanineDipeptideVacuum()
         self.template = omm_engine.snapshot_from_testsystem(self.test_system)
         self.engine = omm_engine.Engine(
@@ -49,7 +46,7 @@ def setup(self):
 
     def test_masses_from_file(self):
         masses = self.template.masses
-        assert_equal(len(masses), self.n_atoms)
+        assert len(masses) == self.n_atoms
 
     def test_masses_from_simulation(self):
         assert len(self.test_snap.masses) == self.n_atoms
@@ -86,19 +83,19 @@ def test_instantaneous_temperature_changes(self):
                                           [lambda t, foo: len(t) < 4])
         temp_1 = trajectory[1].instantaneous_temperature
         temp_2 = trajectory[2].instantaneous_temperature
-        assert_not_equal(temp_1, temp_2)
+        assert temp_1 != temp_2
 
     def test_mdtraj_trajectory(self):
         snap_1 = omm_engine.snapshot_from_testsystem(self.test_system,
                                                      periodic=False)
-        assert_is(snap_1.box_vectors, None)
+        assert snap_1.box_vectors is None
         traj_1 = snap_1.md
-        assert_equal(len(traj_1), 1)
-        assert_is_not(traj_1.xyz, None)
-        assert_is(traj_1.unitcell_vectors, None)
+        assert len(traj_1) == 1
+        assert traj_1.xyz is not None
+        assert traj_1.unitcell_vectors is None
 
         snap_2 = self.test_snap
         traj_2 = snap_2.md
-        assert_equal(len(traj_2), 1)
-        assert_is_not(traj_2.xyz, None)
-        assert_is_not(traj_2.unitcell_vectors, None)
+        assert len(traj_2) == 1
+        assert traj_2.xyz is not None
+        assert traj_2.unitcell_vectors is not None
diff --git a/openpathsampling/tests/test_part_in_b_tps.py b/openpathsampling/tests/test_part_in_b_tps.py
index 0462e6f50..9d918008d 100644
--- a/openpathsampling/tests/test_part_in_b_tps.py
+++ b/openpathsampling/tests/test_part_in_b_tps.py
@@ -1,6 +1,4 @@
 from __future__ import absolute_import
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises)
 import logging
 from openpathsampling.high_level.part_in_b_tps import *
 from .test_network import TestFixedLengthTPSNetwork
@@ -17,16 +15,16 @@ def test_allow_self_transitions_false_ABX(self):
         network = self.NetworkType.from_states_all_to_all(
             self.states, allow_self_transitions=False, **self.std_kwargs
         )
-        assert_equal(len(network.sampling_ensembles), 1)
+        assert len(network.sampling_ensembles) == 1
         ensemble = network.sampling_ensembles[0]
-        assert_equal(ensemble(self.traj['AB0']), True)
-        assert_equal(ensemble(self.traj['ABA']), True)
+        assert ensemble(self.traj['AB0'])
+        assert ensemble(self.traj['ABA'])
 
     def test_allow_self_transitions_true_ABX(self):
         network = self.NetworkType.from_states_all_to_all(
             self.states, allow_self_transitions=True, **self.std_kwargs
         )
-        assert_equal(len(network.sampling_ensembles), 1)
+        assert len(network.sampling_ensembles) == 1
         ensemble = network.sampling_ensembles[0]
-        assert_equal(ensemble(self.traj['AB0']), True)
-        assert_equal(ensemble(self.traj['ABA']), True)
+        assert ensemble(self.traj['AB0'])
+        assert ensemble(self.traj['ABA'])
diff --git a/openpathsampling/tests/test_path_histogram.py b/openpathsampling/tests/test_path_histogram.py
index 044aa87ba..39c27cfb1 100644
--- a/openpathsampling/tests/test_path_histogram.py
+++ b/openpathsampling/tests/test_path_histogram.py
@@ -6,9 +6,6 @@
 
 import pytest
 
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal,
-                        raises)
-from nose.plugins.skip import Skip, SkipTest
 from .test_helpers import (
     true_func, assert_equal_array_array, make_1d_traj, data_filename,
     assert_items_almost_equal
@@ -26,14 +23,14 @@
 from collections import Counter
 
 class PathHistogramTester(object):
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         self.trajectory = [(0.1, 0.3), (2.1, 3.1), (1.7, 1.4),
                            (1.6, 0.6), (0.1, 1.4), (2.2, 3.3)]
         self.diag = [(0.25, 0.25), (2.25, 2.25)]
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
     def test_nopertraj(self):
@@ -67,15 +64,15 @@ def test_same_cell(self):
                              bin_widths=(0.5, 0.5),
                              interpolate=self.Interpolator, per_traj=False)
         hist.add_trajectory(traj)
-        assert_equal(len(list(hist._histogram.keys())), 2)
-        assert_equal(hist._histogram[(0,0)], 3)
-        assert_equal(hist._histogram[(0,1)], 1)
+        assert len(list(hist._histogram.keys())) == 2
+        assert hist._histogram[(0,0)] == 3
+        assert hist._histogram[(0,1)] == 1
 
 
 class TestPathHistogramNoInterpolate(PathHistogramTester):
     Interpolator = NoInterpolation
-    def setup(self):
-        super(TestPathHistogramNoInterpolate, self).setup()
+    def setup_method(self):
+        super(TestPathHistogramNoInterpolate, self).setup_method()
         self.expected_bins = [(0, 0), (4, 6), (3, 2),
                               (3, 1), (0, 2), (4, 6)]
 
@@ -89,8 +86,8 @@ def test_diag(self):
 
 class TestPathHistogramSubdivideInterpolate(PathHistogramTester):
     Interpolator = SubdivideInterpolation
-    def setup(self):
-        super(TestPathHistogramSubdivideInterpolate, self).setup()
+    def setup_method(self):
+        super(TestPathHistogramSubdivideInterpolate, self).setup_method()
         self.expected_bins = [
             (0, 0),  # initial
             (0, 1), (1, 1), (1, 2), (1, 3), (2, 3), (2, 4), (3, 4),
@@ -105,8 +102,8 @@ def setup(self):
 
 class TestPathHistogramBesenhamLikeInterpolate(PathHistogramTester):
     Interpolator = BresenhamLikeInterpolation
-    def setup(self):
-        super(TestPathHistogramBesenhamLikeInterpolate, self).setup()
+    def setup_method(self):
+        super(TestPathHistogramBesenhamLikeInterpolate, self).setup_method()
         # NOTE: the (4, 5) in the 1->2 transition is exactly on bin edge;
         # left bin edge is inclusive
         self.expected_bins = [
@@ -121,8 +118,8 @@ def setup(self):
 
 class TestPathHistogramBesenhamInterpolate(PathHistogramTester):
     Interpolator = BresenhamInterpolation
-    def setup(self):
-        super(TestPathHistogramBesenhamInterpolate, self).setup()
+    def setup_method(self):
+        super(TestPathHistogramBesenhamInterpolate, self).setup_method()
         self.expected_bins = [
             (0, 0),  # initial
             (1, 1), (1, 2), (2, 3), (3, 4), (3, 5), (4, 6),  # 0->1
@@ -134,14 +131,14 @@ def setup(self):
 
 class TestPathHistogram(object):
     # tests of fundamental things in PathHistogram, not interpolators
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         self.trajectory = [(0.1, 0.3), (2.1, 3.1), (1.7, 1.4),
                            (1.6, 0.6), (0.1, 1.4), (2.2, 3.3)]
         self.diag = [(0.25, 0.25), (2.25, 2.25)]
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
     def test_add_with_weight(self):
@@ -154,13 +151,13 @@ def test_add_with_weight(self):
         for val in [(0,1), (0,2), (0,3), (1,2), (1,3), (1,4), (2,1), (2,3),
                     (2,4), (2,5), (3,1), (3,2), (3,4), (3,5), (3,6), (4,5),
                     (4,6)]:
-            assert_equal(hist._histogram[val], 1.0)
+            assert hist._histogram[val] == 1.0
         for val in [(2,2), (4,4)]:
-            assert_equal(hist._histogram[val], 2.0)
+            assert hist._histogram[val] == 2.0
         for val in [(0,0), (1,1), (3,3)]:
-            assert_equal(hist._histogram[val], 3.0)
+            assert hist._histogram[val] == 3.0
         for val in [(0,4), (0,5), (0.6), (0,7), (-1,0)]:
-            assert_equal(hist._histogram[val], 0.0)
+            assert hist._histogram[val] == 0.0
 
     def test_add_data_to_histograms(self):
         hist = PathHistogram(left_bin_edges=(0.0, 0.0),
@@ -172,13 +169,13 @@ def test_add_data_to_histograms(self):
         for val in [(0,1), (0,2), (0,3), (1,2), (1,3), (1,4), (2,1), (2,3),
                     (2,4), (2,5), (3,1), (3,2), (3,4), (3,5), (3,6), (4,5),
                     (4,6)]:
-            assert_equal(counter[val], 1.0)
+            assert counter[val] == 1.0
         for val in [(2,2), (4,4)]:
-            assert_equal(counter[val], 2.0)
+            assert counter[val] == 2.0
         for val in [(0,0), (1,1), (3,3)]:
-            assert_equal(counter[val], 3.0)
+            assert counter[val] == 3.0
         for val in [(0,4), (0,5), (0.6), (0,7), (-1,0)]:
-            assert_equal(counter[val], 0.0)
+            assert counter[val] == 0.0
 
     def test_add_data_to_histograms_no_weight(self):
         hist = PathHistogram(left_bin_edges=(0.0, 0.0),
@@ -189,17 +186,17 @@ def test_add_data_to_histograms_no_weight(self):
         for val in [(0,1), (0,2), (0,3), (1,2), (1,3), (1,4), (2,1), (2,3),
                     (2,4), (2,5), (3,1), (3,2), (3,4), (3,5), (3,6), (4,5),
                     (4,6)]:
-            assert_equal(counter[val], 1.0)
+            assert counter[val] == 1.0
         for val in [(2,2), (4,4)]:
-            assert_equal(counter[val], 1.0)
+            assert counter[val] == 1.0
         for val in [(0,0), (1,1), (3,3)]:
-            assert_equal(counter[val], 2.0)
+            assert counter[val] == 2.0
         for val in [(0,4), (0,5), (0.6), (0,7), (-1,0)]:
-            assert_equal(counter[val], 0.0)
+            assert counter[val] == 0.0
 
 
 class TestPathDensityHistogram(object):
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         id_cv = paths.FunctionCV("Id",
@@ -218,42 +215,42 @@ def setup(self):
         self.traj2 = make_1d_traj([0.6, 0.7])
         # [(2, 1, 0), (2, 1, 1)]
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
     def test_histogram_no_weights(self):
         hist = PathDensityHistogram(self.cvs, self.left_bin_edges,
                                     self.bin_widths)
         counter = hist.histogram([self.traj1, self.traj2])
-        assert_equal(len(counter), 5)
+        assert len(counter) == 5
         for bin_label in [(0,0,0), (2,0,0), (1,0,0), (2,1,1)]:
-            assert_equal(counter[bin_label], 1.0)
-        assert_equal(counter[(2,1,0)], 2.0)
+            assert counter[bin_label] == 1.0
+        assert counter[(2,1,0)] == 2.0
         for bin_label in [(1,1,1), (2,2,2), (-1,0,0), (0,-1,0)]:
-            assert_equal(counter[bin_label], 0.0)
+            assert counter[bin_label] == 0.0
 
     def test_histogram_with_weights(self):
         hist = PathDensityHistogram(self.cvs, self.left_bin_edges,
                                     self.bin_widths)
-        counter = hist.histogram([self.traj1, self.traj2], 
+        counter = hist.histogram([self.traj1, self.traj2],
                                  weights=[1.0, 2.0])
-        assert_equal(len(counter), 5)
+        assert len(counter) == 5
         for bin_label in [(0,0,0), (2,0,0), (1,0,0)]:
-            assert_equal(counter[bin_label], 1.0)
-        assert_equal(counter[(2,1,1)], 2.0)
-        assert_equal(counter[(2,1,0)], 3.0)
+            assert counter[bin_label] == 1.0
+        assert counter[(2,1,1)] == 2.0
+        assert counter[(2,1,0)] == 3.0
         for bin_label in [(1,1,1), (2,2,2), (-1,0,0), (0,-1,0)]:
-            assert_equal(counter[bin_label], 0.0)
+            assert counter[bin_label] == 0.0
 
     def test_histogram_single_traj(self):
         hist = PathDensityHistogram(self.cvs, self.left_bin_edges,
                                     self.bin_widths)
         counter = hist.histogram(self.traj1)
-        assert_equal(len(counter), 4)
+        assert len(counter) == 4
         for bin_label in [(0,0,0), (2,0,0), (1,0,0), (2,1,0)]:
-            assert_equal(counter[bin_label], 1.0)
+            assert counter[bin_label] == 1.0
         for bin_label in [(1,1,1), (2,2,2), (-1,0,0), (0,-1,0)]:
-            assert_equal(counter[bin_label], 0.0)
+            assert counter[bin_label] == 0.0
 
     def test_map_to_float_bins_trajectory(self):
         hist = PathDensityHistogram(self.cvs, self.left_bin_edges,
diff --git a/openpathsampling/tests/test_pathmover.py b/openpathsampling/tests/test_pathmover.py
index 2bfec8956..afda5fcbd 100644
--- a/openpathsampling/tests/test_pathmover.py
+++ b/openpathsampling/tests/test_pathmover.py
@@ -38,7 +38,7 @@
 # logging.getLogger('openpathsampling.initialization').propagate = False
 
 class TestMakeListOfPairs(object):
-    def setup(self):
+    def setup_method(self):
         self.correct = [[0, 1], [2, 3], [4, 5]]
 
     def test_not_iterable_type_error(self):
@@ -85,7 +85,7 @@ def assert_choice_of(result, choices):
 
 
 class TestPathMover(object):
-    def setup(self):
+    def setup_method(self):
         self.l1 = LengthEnsemble(1)
         self.l2 = LengthEnsemble(2)
         self.l3 = LengthEnsemble(3)
@@ -125,7 +125,7 @@ def test_is_canonical(self):
 
 
 class TestShootingMover(object):
-    def setup(self):
+    def setup_method(self):
         self.dyn = CalvinistDynamics([-0.1, 0.1, 0.3, 0.5, 0.7,
                                       -0.1, 0.2, 0.4, 0.6, 0.8])
         op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
@@ -508,7 +508,7 @@ def test_to_dict_from_dict(self):
 
 
 class TestPathReversalMover(object):
-    def setup(self):
+    def setup_method(self):
         op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
 
         volA = CVDefinedVolume(op, -100, 0.0)
@@ -564,7 +564,7 @@ def test_BA_path(self):
 
 
 class TestReplicaIDChangeMover(object):
-    def setup(self):
+    def setup_method(self):
         pass
 
     def test_replica_in_sample_set(self):
@@ -575,7 +575,7 @@ def test_replica_not_in_sample_set(self):
 
 
 class TestReplicaExchangeMover(object):
-    def setup(self):
+    def setup_method(self):
         op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
 
         state1 = CVDefinedVolume(op, -100, 0.0)
@@ -658,7 +658,7 @@ def test_repex_ens_acc(self):
 
 
 class TestRandomChoiceMover(object):
-    def setup(self):
+    def setup_method(self):
         traj = Trajectory([-0.5, 0.7, 1.1])
         op = CallIdentity()
         volA = CVDefinedVolume(op, -100, 0.0)
@@ -718,7 +718,7 @@ def test_restricted_by_ensemble(self):
 
 
 class TestRandomAllowedChoiceMover(object):
-    def setup(self):
+    def setup_method(self):
         self.dyn = CalvinistDynamics([-0.1, 0.1, 0.3, 0.5, 0.7,
                                       -0.1, 0.2, 0.4, 0.6, 0.8,
                                       ])
@@ -820,7 +820,7 @@ def test_move_multiple_replicas_weighted_ensembles(self):
 
 
 class TestSequentialMover(object):
-    def setup(self):
+    def setup_method(self):
         traj = Trajectory([-0.5, 0.7, 1.1])
         op = CallIdentity()
         volA = CVDefinedVolume(op, -100, 0.0)
@@ -1033,7 +1033,7 @@ def test_restricted_by_ensemble(self):
 
 
 class SubtrajectorySelectTester(object):
-    def setup(self):
+    def setup_method(self):
         op = CallIdentity()
         vol = paths.CVDefinedVolume(op, -0.5, 0.5)
         inX = paths.AllInXEnsemble(vol)
@@ -1175,7 +1175,7 @@ def test_move(self):
 
 
 class TestMinusMover(object):
-    def setup(self):
+    def setup_method(self):
         op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
         volA = CVDefinedVolume(op, -100, 0.0)
         volB = CVDefinedVolume(op, 1.0, 100)
@@ -1409,7 +1409,7 @@ def test_extension_fails(self):
 
 
 class TestSingleReplicaMinusMover(object):
-    def setup(self):
+    def setup_method(self):
         op = FunctionCV("myid", f=lambda snap: snap.coordinates[0][0])
         volA = CVDefinedVolume(op, -100, 0.0)
         volB = CVDefinedVolume(op, 1.0, 100)
diff --git a/openpathsampling/tests/test_pathsimulator.py b/openpathsampling/tests/test_pathsimulator.py
index d38321478..1721d0503 100644
--- a/openpathsampling/tests/test_pathsimulator.py
+++ b/openpathsampling/tests/test_pathsimulator.py
@@ -4,12 +4,12 @@
 from builtins import range
 from past.utils import old_div
 from builtins import object
+
+import pytest
 from .test_helpers import (raises_with_message_like, data_filename,
                            CalvinistDynamics, make_1d_traj,
                            assert_items_equal)
-from nose.tools import (assert_equal, assert_not_equal, raises,
-                        assert_almost_equal, assert_true, assert_greater)
-# from nose.plugins.skip import SkipTest
+from numpy.testing import assert_almost_equal
 
 from openpathsampling.pathsimulators import *
 import openpathsampling as paths
@@ -32,7 +32,7 @@ def test_abstract_volume(self):
 
 
 class TestFullBootstrapping(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV("Id", lambda snap: snap.xyz[0][0])
         cv_neg = paths.FunctionCV("Neg", lambda snap: -snap.xyz[0][0])
@@ -65,7 +65,6 @@ def setup(self):
             snapshot=self.snapA
         )
 
-    @raises(RuntimeError)
     def test_initial_max_length(self):
         engine = CalvinistDynamics([-0.5, -0.4, -0.3, -0.2, -0.1, 0.1, -0.1])
         bootstrap_AB_maxlength = paths.FullBootstrapping(
@@ -75,16 +74,17 @@ def test_initial_max_length(self):
             engine=engine
         )
         bootstrap_AB_maxlength.output_stream = open(os.devnull, "w")
-        bootstrap_AB_maxlength.run(build_attempts=1)
+        with pytest.raises(RuntimeError):
+            bootstrap_AB_maxlength.run(build_attempts=1)
 
     def test_first_traj_ensemble(self):
         traj_starts_in = make_1d_traj([-0.2, -0.1, 0.1, -0.1])
         traj_starts_out = make_1d_traj([0.1, -0.1, 0.1, -0.1])
         traj_not_good = make_1d_traj([0.1, -0.1, 0.1])
         first_traj_ens = self.noforbid_noextra_AB.first_traj_ensemble
-        assert_equal(first_traj_ens(traj_starts_in), True)
-        assert_equal(first_traj_ens(traj_starts_out), True)
-        assert_equal(first_traj_ens(traj_not_good), False)
+        assert first_traj_ens(traj_starts_in)
+        assert first_traj_ens(traj_starts_out)
+        assert not first_traj_ens(traj_not_good)
 
     def test_sampling_ensembles(self):
         traj1 = make_1d_traj([-0.2, -0.1, 0.1, -0.1])
@@ -92,16 +92,16 @@ def test_sampling_ensembles(self):
         traj3 = make_1d_traj([-0.1, 0.1, 0.3, -0.1])
         traj4 = make_1d_traj([0.1, 0.3, 0.1])
         all_ensembles = self.noforbid_noextra_AB.all_ensembles
-        assert_equal(len(all_ensembles), 3)
+        assert len(all_ensembles) == 3
         for ens in all_ensembles:
-            assert_equal(ens(traj1), False)
-            assert_equal(ens(traj4), False)
-        assert_equal(all_ensembles[0](traj2), True)
-        assert_equal(all_ensembles[0](traj3), True)
-        assert_equal(all_ensembles[1](traj2), False)
-        assert_equal(all_ensembles[1](traj3), True)
-        assert_equal(all_ensembles[2](traj2), False)
-        assert_equal(all_ensembles[2](traj3), False)
+            assert not ens(traj1)
+            assert not ens(traj4)
+        assert all_ensembles[0](traj2)
+        assert all_ensembles[0](traj3)
+        assert not all_ensembles[1](traj2)
+        assert all_ensembles[1](traj3)
+        assert not all_ensembles[2](traj2)
+        assert not all_ensembles[2](traj3)
 
     def test_run_already_satisfied(self):
         engine = CalvinistDynamics([-0.5, 0.8, -0.1])
@@ -112,7 +112,7 @@ def test_run_already_satisfied(self):
         )
         bootstrap.output_stream = open(os.devnull, "w")
         gs = bootstrap.run()
-        assert_equal(len(gs), 3)
+        assert len(gs) == 3
 
     def test_run_extra_interfaces(self):
         engine = CalvinistDynamics([-0.5, 0.8, -0.1])
@@ -124,7 +124,7 @@ def test_run_extra_interfaces(self):
         )
         bootstrap.output_stream = open(os.devnull, "w")
         gs = bootstrap.run()
-        assert_equal(len(gs), 4)
+        assert len(gs) == 4
 
     def test_run_forbidden_states(self):
         engine = CalvinistDynamics([-0.5, 0.3, 3.2, -0.1, 0.8, -0.1])
@@ -136,7 +136,7 @@ def test_run_forbidden_states(self):
         )
         bootstrap1.output_stream = open(os.devnull, "w")
         gs1 = bootstrap1.run()
-        assert_equal(len(gs1), 3)
+        assert len(gs1) == 3
         assert_items_equal(self.cv(gs1[0]), [-0.5, 0.3, 3.2, -0.1])
         # now with setting forbidden_states
         bootstrap2 = FullBootstrapping(
@@ -152,7 +152,6 @@ def test_run_forbidden_states(self):
         except RuntimeError:
             pass
 
-    @raises(RuntimeError)
     def test_too_much_bootstrapping(self):
         engine = CalvinistDynamics([-0.5, 0.2, -0.1])
         bootstrap = FullBootstrapping(
@@ -161,7 +160,8 @@ def test_too_much_bootstrapping(self):
             engine=engine,
         )
         bootstrap.output_stream = open(os.devnull, "w")
-        bootstrap.run(max_ensemble_rounds=1)
+        with pytest.raises(RuntimeError):
+            bootstrap.run(max_ensemble_rounds=1)
 
 
 class TestShootFromSnapshotsSimulation(object):
@@ -169,7 +169,7 @@ class TestShootFromSnapshotsSimulation(object):
     # for CommittorSimulation. This is just an additional test to show that
     # using different ensembles from the ones used for the committor will
     # also work.
-    def setup(self):
+    def setup_method(self):
         # As a test system, let's use 1D motion on a flat potential. If the
         # velocity is positive, you right the state on the right. If it is
         # negative, you hit the state on the left.
@@ -204,7 +204,7 @@ def setup(self):
         )
         self.simulation.output_stream = open(os.devnull, "w")
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
             os.remove(self.filename)
         paths.EngineMover.default_engine = None
@@ -215,24 +215,24 @@ def test_run_arbitrary_ensemble(self):
         self.storage.close()
         analysis = paths.Storage(self.filename, 'r')
         sim = analysis.pathsimulators[0]
-        assert_equal(len(analysis.steps), 10)
+        assert len(analysis.steps) == 10
         length_to_submover = {5: [], 3: []}
         for step in analysis.steps:
             step.active.sanity_check()
-            assert_equal(len(step.active), 1)
+            assert len(step.active) == 1
             active_sample = step.active[0]
             change = step.change
-            assert_equal(change.mover, sim.mover)
+            assert change.mover == sim.mover
             # KeyError here indicates problem with lengths generated
             length_to_submover[len(active_sample)] += change.subchange.mover
 
         for k in length_to_submover:
             # allow 0 or 1  because maybe we made no trials with submover
-            assert_true(len(set(length_to_submover[k])) <= 1)
+            assert len(set(length_to_submover[k])) <= 1
 
 
 class TestCommittorSimulation(object):
-    def setup(self):
+    def setup_method(self):
         # As a test system, let's use 1D motion on a flat potential. If the
         # velocity is positive, you right the state on the right. If it is
         # negative, you hit the state on the left.
@@ -268,7 +268,7 @@ def setup(self):
                                               initial_snapshots=self.snap0)
         self.simulation.output_stream = open(os.devnull, 'w')
 
-    def teardown(self):
+    def teardown_method(self):
         self.storage.close()
         if os.path.isfile(self.filename):
             os.remove(self.filename)
@@ -276,8 +276,8 @@ def teardown(self):
 
     def test_initialization(self):
         sim = self.simulation  # convenience
-        assert_equal(len(sim.initial_snapshots), 1)
-        assert_true(isinstance(sim.mover, paths.RandomChoiceMover))
+        assert len(sim.initial_snapshots) == 1
+        assert isinstance(sim.mover, paths.RandomChoiceMover)
 
     def test_storage(self):
         self.storage.tag['simulation'] = self.simulation
@@ -295,31 +295,31 @@ def test_storage(self):
 
     def test_committor_run(self):
         self.simulation.run(n_per_snapshot=20)
-        assert_equal(len(self.simulation.storage.steps), 20)
+        assert len(self.simulation.storage.steps) == 20
         counts = {'fwd': 0, 'bkwd': 0}
         for step in self.simulation.storage.steps:
             step.active.sanity_check()  # traj is in ensemble
             traj = step.active[0].trajectory
             traj_str = traj.summarize_by_volumes_str(self.state_labels)
             if traj_str == "None-Right":
-                assert_equal(step.change.canonical.mover,
-                             self.simulation.forward_mover)
-                assert_equal(step.active[0].ensemble,
-                             self.simulation.forward_ensemble)
+                assert (step.change.canonical.mover
+                        == self.simulation.forward_mover)
+                assert (step.active[0].ensemble
+                        == self.simulation.forward_ensemble)
                 counts['fwd'] += 1
             elif traj_str == "Left-None":
-                assert_equal(step.change.canonical.mover,
-                             self.simulation.backward_mover)
-                assert_equal(step.active[0].ensemble,
-                             self.simulation.backward_ensemble)
+                assert (step.change.canonical.mover
+                        == self.simulation.backward_mover)
+                assert (step.active[0].ensemble
+                        == self.simulation.backward_ensemble)
                 counts['bkwd'] += 1
             else:
                 raise AssertionError(
                     str(traj_str) + "is neither 'None-Right' nor 'Left-None'"
                 )
-        assert_true(counts['fwd'] > 0)
-        assert_true(counts['bkwd'] > 0)
-        assert_equal(counts['fwd'] + counts['bkwd'], 20)
+        assert counts['fwd'] > 0
+        assert counts['bkwd'] > 0
+        assert counts['fwd'] + counts['bkwd'] == 20
 
     def test_forward_only_committor(self):
         sim = CommittorSimulation(storage=self.storage,
@@ -330,17 +330,14 @@ def test_forward_only_committor(self):
                                   direction=1)
         sim.output_stream = open(os.devnull, 'w')
         sim.run(n_per_snapshot=10)
-        assert_equal(len(sim.storage.steps), 10)
+        assert len(sim.storage.steps) == 10
         for step in self.simulation.storage.steps:
             s = step.active[0]
             step.active.sanity_check()  # traj is in ensemble
-            assert_equal(
-                s.trajectory.summarize_by_volumes_str(self.state_labels),
-                "None-Right"
-            )
-            assert_equal(s.ensemble, sim.forward_ensemble)
-            assert_equal(step.change.canonical.mover,
-                         sim.forward_mover)
+            assert (s.trajectory.summarize_by_volumes_str(self.state_labels)
+                    == "None-Right")
+            assert s.ensemble == sim.forward_ensemble
+            assert step.change.canonical.mover == sim.forward_mover
 
     def test_backward_only_committor(self):
         sim = CommittorSimulation(storage=self.storage,
@@ -351,17 +348,14 @@ def test_backward_only_committor(self):
                                   direction=-1)
         sim.output_stream = open(os.devnull, 'w')
         sim.run(n_per_snapshot=10)
-        assert_equal(len(sim.storage.steps), 10)
+        assert len(sim.storage.steps) == 10
         for step in self.simulation.storage.steps:
             s = step.active[0]
             step.active.sanity_check()  # traj is in ensemble
-            assert_equal(
-                s.trajectory.summarize_by_volumes_str(self.state_labels),
-                "Left-None"
-            )
-            assert_equal(s.ensemble, sim.backward_ensemble)
-            assert_equal(step.change.canonical.mover,
-                         sim.backward_mover)
+            assert (s.trajectory.summarize_by_volumes_str(self.state_labels)
+                    == "Left-None")
+            assert s.ensemble == sim.backward_ensemble
+            assert step.change.canonical.mover == sim.backward_mover
 
     def test_multiple_initial_snapshots(self):
         snap1 = toys.Snapshot(coordinates=np.array([[0.1]]),
@@ -374,7 +368,7 @@ def test_multiple_initial_snapshots(self):
                                   initial_snapshots=[self.snap0, snap1])
         sim.output_stream = open(os.devnull, 'w')
         sim.run(10)
-        assert_equal(len(self.storage.steps), 20)
+        assert len(self.storage.steps) == 20
         snap0_coords = self.snap0.coordinates.tolist()
         snap1_coords = snap1.coordinates.tolist()
         count = {self.snap0: 0, snap1: 0}
@@ -388,7 +382,7 @@ def test_multiple_initial_snapshots(self):
                 msg = "Shooting snapshot matches neither test snapshot"
                 raise AssertionError(msg)
             count[mysnap] += 1
-        assert_equal(count, {self.snap0: 10, snap1: 10})
+        assert count == {self.snap0: 10, snap1: 10}
 
     def test_randomized_committor(self):
         # this shows that we get both states even with forward-only
@@ -402,7 +396,7 @@ def test_randomized_committor(self):
                                   direction=1)
         sim.output_stream = open(os.devnull, 'w')
         sim.run(50)
-        assert_equal(len(sim.storage.steps), 50)
+        assert len(sim.storage.steps) == 50
         counts = {'None-Right': 0,
                   'Left-None': 0,
                   'None-Left': 0,
@@ -417,15 +411,15 @@ def test_randomized_committor(self):
                 msg = "Got trajectory described as '{0}', length {1}"
                 # this might be okay if it is 'None', length 100000
                 raise AssertionError(msg.format(traj_str, len(traj)))
-        assert_equal(counts['Left-None'], 0)
-        assert_equal(counts['Right-None'], 0)
-        assert_true(counts['None-Left'] > 0)
-        assert_true(counts['None-Right'] > 0)
-        assert_equal(sum(counts.values()), 50)
+        assert counts['Left-None'] == 0
+        assert counts['Right-None'] == 0
+        assert counts['None-Left'] > 0
+        assert counts['None-Right'] > 0
+        assert sum(counts.values()) == 50
 
 
 class TestReactiveFluxSimulation(object):
-    def setup(self):
+    def setup_method(self):
         # PES is one-dimensional linear slope (y(x) = x)
         pes = toys.LinearSlope(m=[-1.0], c=[0.0])
         # one particle with mass 1.0
@@ -484,69 +478,63 @@ def setup(self):
                               rc=rc)
         self.simulation.output_stream = open(os.devnull, 'w')
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
             os.remove(self.filename)
         paths.EngineMover.default_engine = None
 
     def test_initialization(self):
         sim = self.simulation
-        assert_equal(len(sim.initial_snapshots), 3)
-        assert_true(isinstance(sim.mover, paths.ConditionalSequentialMover))
+        assert len(sim.initial_snapshots) == 3
+        assert isinstance(sim.mover, paths.ConditionalSequentialMover)
 
     def test_simulation_run(self):
         self.simulation.run(n_per_snapshot=1)
-        assert_equal(len(self.simulation.storage.steps), 3)
+        assert len(self.simulation.storage.steps) == 3
 
         # snapshot 0, fails at backward shot (falls back to dividing surface)
         step = self.simulation.storage.steps[0]
         # last mover should be backward_mover of simulation
-        assert_equal(step.change.canonical.mover,
-                     self.simulation.backward_mover)
+        assert step.change.canonical.mover == self.simulation.backward_mover
         # active ensemble should be starting ensemble
-        assert_equal(step.active[0].ensemble,
-                     self.simulation.starting_ensemble)
+        assert step.active[0].ensemble == self.simulation.starting_ensemble
         # analyze trajectory, last step should be in 'None', the rest in 'ToA'
         traj = step.change.trials[0].trajectory
         traj_summary = traj.summarize_by_volumes(self.state_labels)
-        assert_equal(traj_summary[0], ('None', 1))
-        assert_equal(traj_summary[1][0], 'ToA')
-        assert_greater(traj_summary[1][1], 1)
+        assert traj_summary[0] == ('None', 1)
+        assert traj_summary[1][0] == 'ToA'
+        assert traj_summary[1][1] > 1
 
         # snapshot 1, fails at backward shot (wrong direction)
         step = self.simulation.storage.steps[1]
         # last mover should be backward_mover of simulation
-        assert_equal(step.change.canonical.mover,
-                     self.simulation.backward_mover)
+        assert step.change.canonical.mover == self.simulation.backward_mover
         # active ensemble should be starting ensemble
-        assert_equal(step.active[0].ensemble,
-                     self.simulation.starting_ensemble)
+        assert step.active[0].ensemble == self.simulation.starting_ensemble
         # analyze trajectory, backwards trajectory reaches immediately 'None'
         traj = step.change.trials[0].trajectory
         traj_summary = traj.summarize_by_volumes(self.state_labels)
-        assert_equal(traj_summary[0], ('None', 2))
+        assert traj_summary[0] == ('None', 2)
 
         # snapshot 2, is accepted
         step = self.simulation.storage.steps[2]
         # last mover should be forward_mover of simulation
-        assert_equal(step.change.canonical.mover,
-                     self.simulation.forward_mover)
+        assert step.change.canonical.mover == self.simulation.forward_mover
         # active ensemble should not be starting ensemble
-        assert_not_equal(step.active[0].ensemble,
-                     self.simulation.starting_ensemble)
+        assert step.active[0].ensemble != self.simulation.starting_ensemble
         # analyze active trajectory, trajectory should start in 'A', end in 'B'
         traj = step.active[0].trajectory
         traj_summary = traj.summarize_by_volumes(self.state_labels)
-        assert_equal(traj_summary[0], ('A', 1))
-        assert_equal(traj_summary[1][0], 'ToA')
-        assert_greater(traj_summary[1][1], 1)
-        assert_equal(traj_summary[2][0], 'None')
-        assert_greater(traj_summary[2][1], 1)
-        assert_equal(traj_summary[3], ('B', 1))
+        assert traj_summary[0] == ('A', 1)
+        assert traj_summary[1][0] == 'ToA'
+        assert traj_summary[1][1] > 1
+        assert traj_summary[2][0] == 'None'
+        assert traj_summary[2][1] > 1
+        assert traj_summary[3] == ('B', 1)
 
 
 class TestDirectSimulation(object):
-    def setup(self):
+    def setup_method(self):
         pes = toys.HarmonicOscillator(A=[1.0], omega=[1.0], x0=[0.0])
         topology = toys.Topology(n_spatial=1, masses=[1.0], pes=pes)
         integrator = toys.LeapfrogVerletIntegrator(0.1)
@@ -574,17 +562,16 @@ def setup(self):
 
     def test_run(self):
         self.sim.run(200)
-        assert_true(len(self.sim.transition_count) > 1)
-        assert_true(len(self.sim.flux_events[self.flux_pairs[0]]) > 1)
+        assert len(self.sim.transition_count) > 1
+        assert len(self.sim.flux_events[self.flux_pairs[0]]) > 1
 
     def test_results(self):
         self.sim.run(200)
         results = self.sim.results
-        assert_equal(len(results), 2)
-        assert_equal(set(results.keys()),
-                     set(['transition_count', 'flux_events']))
-        assert_equal(results['transition_count'], self.sim.transition_count)
-        assert_equal(results['flux_events'], self.sim.flux_events)
+        assert len(results) == 2
+        assert set(results.keys()) == set(['transition_count', 'flux_events'])
+        assert results['transition_count'] == self.sim.transition_count
+        assert results['flux_events'] == self.sim.flux_events
 
     def test_load_results(self):
         left_interface = paths.CVDefinedVolume(self.cv, -0.3, float("inf"))
@@ -600,8 +587,8 @@ def test_load_results(self):
         results = {'transition_count': fake_transition_count,
                    'flux_events': fake_flux_events}
         self.sim.load_results(results)
-        assert_equal(self.sim.transition_count, fake_transition_count)
-        assert_equal(self.sim.flux_events, fake_flux_events)
+        assert self.sim.transition_count == fake_transition_count
+        assert self.sim.flux_events == fake_flux_events
 
     def test_transitions(self):
         # set fake data
@@ -609,16 +596,14 @@ def test_transitions(self):
             (self.center, 1), (self.outside, 4), (self.center, 7),
             (self.extra, 10), (self.center, 12), (self.outside, 14)
         ]
-        assert_equal(self.sim.n_transitions,
-                     {(self.center, self.outside): 2,
-                      (self.outside, self.center): 1,
-                      (self.center, self.extra): 1,
-                      (self.extra, self.center): 1})
-        assert_equal(self.sim.transitions,
-                     {(self.center, self.outside): [3, 2],
-                      (self.outside, self.center): [3],
-                      (self.center, self.extra): [3],
-                      (self.extra, self.center): [2]})
+        assert self.sim.n_transitions == {(self.center, self.outside): 2,
+                                          (self.outside, self.center): 1,
+                                          (self.center, self.extra): 1,
+                                          (self.extra, self.center): 1}
+        assert self.sim.transitions == {(self.center, self.outside): [3, 2],
+                                        (self.outside, self.center): [3],
+                                        (self.center, self.extra): [3],
+                                        (self.extra, self.center): [2]}
 
     def test_rate_matrix(self):
         self.sim.states += [self.extra]
@@ -644,7 +629,7 @@ def test_rate_matrix(self):
         for i in range(len(self.sim.states)):
             for j in range(len(self.sim.states)):
                 if np.isnan(test_matrix[i][j]):
-                    assert_true(np.isnan(rate_matrix[i][j]))
+                    assert np.isnan(rate_matrix[i][j])
                 else:
                     assert_almost_equal(rate_matrix[i][j],
                                         test_matrix[i][j])
@@ -665,7 +650,7 @@ def test_fluxes(self):
         sim.flux_events = fake_flux_events
         n_flux_events = {(self.center, right_interface): 3,
                          (self.center, left_interface): 2}
-        assert_equal(sim.n_flux_events, n_flux_events)
+        assert sim.n_flux_events == n_flux_events
         time_step = sim.engine.snapshot_timestep
         expected_fluxes = {(self.center, right_interface):
                            1.0 / (((15-3) + (23-15) + (48-23))/3.0) / time_step,
@@ -730,11 +715,11 @@ def test_multiple_cv_flux(self):
             (state, alpha): [(4, 2), (6, 4), (22, 19)],
             (state, beta): [(9, 6), (12, 9), (22, 17)]
         }
-        assert_equal(len(sim.flux_events), 2)
-        assert_equal(sim.flux_events[(state, alpha)],
-                     expected_flux_events[(state, alpha)])
-        assert_equal(sim.flux_events[(state, beta)],
-                     expected_flux_events[(state, beta)])
+        assert len(sim.flux_events) == 2
+        assert (sim.flux_events[(state, alpha)]
+                == expected_flux_events[(state, alpha)])
+        assert (sim.flux_events[(state, beta)]
+                == expected_flux_events[(state, beta)])
 
     def test_simple_flux(self):
         state = self.center
@@ -754,9 +739,9 @@ def test_simple_flux(self):
                                initial_snapshot=init[0])
         sim.run(len(predetermined) - 1)
         expected_flux_events = {(state, interface): [(10, 2)]}
-        assert_equal(len(sim.flux_events), 1)
-        assert_equal(sim.flux_events[(state, interface)],
-                     expected_flux_events[(state, interface)])
+        assert len(sim.flux_events) == 1
+        assert (sim.flux_events[(state, interface)]
+                == expected_flux_events[(state, interface)])
 
     def test_sim_with_storage(self):
         tmpfile = data_filename("direct_sim_test.nc")
@@ -772,15 +757,15 @@ def test_sim_with_storage(self):
         sim.run(200)
         storage.close()
         read_store = paths.AnalysisStorage(tmpfile)
-        assert_equal(len(read_store.trajectories), 1)
+        assert len(read_store.trajectories) == 1
         traj = read_store.trajectories[0]
-        assert_equal(len(traj), 201)
+        assert len(traj) == 201
         read_store.close()
         os.remove(tmpfile)
 
 
 class TestPathSampling(object):
-    def setup(self):
+    def setup_method(self):
         paths.InterfaceSet._reset()
         self.cv = paths.FunctionCV("x", lambda x: x.xyz[0][0])
         self.state_A = paths.CVDefinedVolume(self.cv, float("-inf"), 0.0)
diff --git a/openpathsampling/tests/test_plumed_wrapper.py b/openpathsampling/tests/test_plumed_wrapper.py
index 94703283a..ae3407f20 100755
--- a/openpathsampling/tests/test_plumed_wrapper.py
+++ b/openpathsampling/tests/test_plumed_wrapper.py
@@ -35,7 +35,7 @@
 
 
 class TestPLUMED(object):
-    def setup(self):
+    def setup_method(self):
         if not HAS_PLUMED:
             pytest.skip("PLUMED module not installed.")
         if not HAS_OPENMM:
@@ -55,7 +55,7 @@ def setup(self):
         if os.path.isfile("test.nc"):
             os.remove("test.nc")
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile("test.nc"):
             os.remove("test.nc")
         root = os.listdir(".")
diff --git a/openpathsampling/tests/test_progress.py b/openpathsampling/tests/test_progress.py
index a47f428df..20d197738 100644
--- a/openpathsampling/tests/test_progress.py
+++ b/openpathsampling/tests/test_progress.py
@@ -14,7 +14,7 @@ def test_silent_progress(capsys):
 
 
 class TestTqdmPartial(object):
-    def setup(self):
+    def setup_method(self):
         _ = pytest.importorskip('tqdm')
         self.tqdm_partial = TqdmPartial(desc="foo")
 
@@ -40,7 +40,7 @@ def test_call(self, capsys):
 
 
 class TestSimpleProgress(object):
-    def setup(self):
+    def setup_method(self):
         self.progress = SimpleProgress()
 
     def test_progress_getter(self):
diff --git a/openpathsampling/tests/test_pyemma_cv.py b/openpathsampling/tests/test_pyemma_cv.py
index 1e3db7fd6..02ab7f2b1 100644
--- a/openpathsampling/tests/test_pyemma_cv.py
+++ b/openpathsampling/tests/test_pyemma_cv.py
@@ -10,7 +10,7 @@
 
 
 class TestPyEMMAFeaturizerCV(object):
-    def setup(self):
+    def setup_method(self):
         self.md_trajectory = mdtraj.load(data_filename("ala_small_traj.pdb"))
         self.ops_trajectory = trajectory_from_mdtraj(self.md_trajectory)
         # output of f.pairs(f.select_Backbone()) in the pyemma_generator
@@ -27,7 +27,7 @@ def pyemma_generator(f):
         self.storage = paths.Storage('delete.nc', 'w')
         self.fname = self.storage.filename
 
-    def teardown(self):
+    def teardown_method(self):
         fname = self.fname
         if self.storage.isopen():
             self.storage.close()
diff --git a/openpathsampling/tests/test_range_logic.py b/openpathsampling/tests/test_range_logic.py
index 2a1a59a03..f1d0afec1 100644
--- a/openpathsampling/tests/test_range_logic.py
+++ b/openpathsampling/tests/test_range_logic.py
@@ -1,86 +1,84 @@
 
 from builtins import object
-from nose.tools import assert_equal, assert_not_equal, raises
-from nose.plugins.skip import Skip, SkipTest
 from openpathsampling.range_logic import *
 
 class TestRangeLogic(object):
     def test_range_and(self):
-        assert_equal(range_and(1, 3, 2, 4), [(2, 3)])
-        assert_equal(range_and(2, 4, 1, 3), [(2, 3)])
-        assert_equal(range_and(1, 2, 3, 4), None)
-        assert_equal(range_and(3, 4, 1, 2), None)
-        assert_equal(range_and(1, 4, 2, 3), [(2, 3)])
-        assert_equal(range_and(2, 3, 1, 4), [(2, 3)])
-        assert_equal(range_and(1, 2, 1, 2), 1)
+        assert range_and(1, 3, 2, 4) == [(2, 3)]
+        assert range_and(2, 4, 1, 3) == [(2, 3)]
+        assert range_and(1, 2, 3, 4) is None
+        assert range_and(3, 4, 1, 2) is None
+        assert range_and(1, 4, 2, 3) == [(2, 3)]
+        assert range_and(2, 3, 1, 4) == [(2, 3)]
+        assert range_and(1, 2, 1, 2) == 1
 
     def test_range_or(self):
-        assert_equal(range_or(1, 3, 2, 4), [(1, 4)])
-        assert_equal(range_or(2, 4, 1, 3), [(1, 4)])
-        assert_equal(range_or(1, 2, 3, 4), [(1, 2), (3, 4)])
-        assert_equal(range_or(3, 4, 1, 2), [(3, 4), (1, 2)])
-        assert_equal(range_or(1, 4, 2, 3), [(1, 4)])
-        assert_equal(range_or(2, 3, 1, 4), [(1, 4)])
-        assert_equal(range_or(1, 2, 1, 2), 1)
+        assert range_or(1, 3, 2, 4) == [(1, 4)]
+        assert range_or(2, 4, 1, 3) == [(1, 4)]
+        assert range_or(1, 2, 3, 4) == [(1, 2), (3, 4)]
+        assert range_or(3, 4, 1, 2) == [(3, 4), (1, 2)]
+        assert range_or(1, 4, 2, 3) == [(1, 4)]
+        assert range_or(2, 3, 1, 4) == [(1, 4)]
+        assert range_or(1, 2, 1, 2) == 1
 
 
     def test_range_sub(self):
-        assert_equal(range_sub(1, 3, 2, 4), [(1, 2)])
-        assert_equal(range_sub(2, 4, 1, 3), [(3, 4)])
-        assert_equal(range_sub(1, 2, 3, 4), 1)
-        assert_equal(range_sub(3, 4, 1, 2), 1)
-        assert_equal(range_sub(1, 4, 2, 3), [(1, 2), (3, 4)])
-        assert_equal(range_sub(2, 3, 1, 4), None)
-        assert_equal(range_sub(1, 2, 1, 2), None)
-        assert_equal(range_sub(0.1, 0.4, 0.1, 0.3), [(0.3, 0.4)])
+        assert range_sub(1, 3, 2, 4) == [(1, 2)]
+        assert range_sub(2, 4, 1, 3) == [(3, 4)]
+        assert range_sub(1, 2, 3, 4) == 1
+        assert range_sub(3, 4, 1, 2) == 1
+        assert range_sub(1, 4, 2, 3) == [(1, 2), (3, 4)]
+        assert range_sub(2, 3, 1, 4) is None
+        assert range_sub(1, 2, 1, 2) is None
+        assert range_sub(0.1, 0.4, 0.1, 0.3) == [(0.3, 0.4)]
 
 
 class TestPeriodicRangeLogic(object):
 
     def test_periodic_order(self):
         # orders without wrapping
-        assert_equal(periodic_ordering(1, 2, 3, 4), [0, 1, 2, 3])
-        assert_equal(periodic_ordering(1, 3, 2, 4), [0, 2, 1, 3])
-        assert_equal(periodic_ordering(4, 3, 2, 1), [0, 3, 2, 1])
-        assert_equal(periodic_ordering(1, 2, 1, 2), [0, 2, 1, 3])
-        assert_equal(periodic_ordering(2, 4, 1, 3), [1, 3, 0, 2])
-        assert_equal(periodic_ordering(1, 2, 4, 3), [1, 2, 0, 3])
+        assert periodic_ordering(1, 2, 3, 4) == [0, 1, 2, 3]
+        assert periodic_ordering(1, 3, 2, 4) == [0, 2, 1, 3]
+        assert periodic_ordering(4, 3, 2, 1) == [0, 3, 2, 1]
+        assert periodic_ordering(1, 2, 1, 2) == [0, 2, 1, 3]
+        assert periodic_ordering(2, 4, 1, 3) == [1, 3, 0, 2]
+        assert periodic_ordering(1, 2, 4, 3) == [1, 2, 0, 3]
 
     def test_periodic_and(self):
-        assert_equal(periodic_range_and(0.1, 0.3, 0.2, 0.4), [(0.2, 0.3)])
-        assert_equal(periodic_range_and(0.2, 0.4, 0.1, 0.3), [(0.2, 0.3)])
-        assert_equal(periodic_range_and(1, 2, 3, 4), None)
-        assert_equal(periodic_range_and(3, 4, 1, 2), None)
-        assert_equal(periodic_range_and(1, 4, 2, 3), [(2, 3)])
-        assert_equal(periodic_range_and(2, 3, 1, 4), [(2, 3)])
-        assert_equal(periodic_range_and(1, 2, 1, 2), 1)
-        assert_equal(periodic_range_and(1, 2, 2, 1), None)
-        assert_equal(periodic_range_and(2, 1, 1, 4), [(2, 4)])
-        assert_equal(periodic_range_and(0.1, 0.4, 0.3, 0.2), 
-                     [(0.1, 0.2), (0.3, 0.4)])
+        assert periodic_range_and(0.1, 0.3, 0.2, 0.4) == [(0.2, 0.3)]
+        assert periodic_range_and(0.2, 0.4, 0.1, 0.3) == [(0.2, 0.3)]
+        assert periodic_range_and(1, 2, 3, 4) is None
+        assert periodic_range_and(3, 4, 1, 2) is None
+        assert periodic_range_and(1, 4, 2, 3) == [(2, 3)]
+        assert periodic_range_and(2, 3, 1, 4) == [(2, 3)]
+        assert periodic_range_and(1, 2, 1, 2) == 1
+        assert periodic_range_and(1, 2, 2, 1) is None
+        assert periodic_range_and(2, 1, 1, 4) == [(2, 4)]
+        assert (periodic_range_and(0.1, 0.4, 0.3, 0.2)
+                == [(0.1, 0.2), (0.3, 0.4)])
 
     def test_periodic_or(self):
-        assert_equal(periodic_range_or(0.1, 0.3, 0.2, 0.4), [(0.1, 0.4)])
-        assert_equal(periodic_range_or(0.2, 0.4, 0.1, 0.3), [(0.1, 0.4)])
-        assert_equal(periodic_range_or(1, 2, 3, 4), [(1, 2), (3, 4)])
-        assert_equal(periodic_range_or(3, 4, 1, 2), [(3, 4), (1, 2)])
-        assert_equal(periodic_range_or(1, 4, 2, 3), [(1, 4)])
-        assert_equal(periodic_range_or(2, 3, 1, 4), [(1, 4)])
-        assert_equal(periodic_range_or(1, 2, 1, 2), 1)
-        assert_equal(periodic_range_or(1, 2, 2, 1), -1)
-        assert_equal(periodic_range_or(0.1, 0.4, 0.3, 0.2), -1)
-        assert_equal(periodic_range_or(2, 1, 1, 4), -1)
+        assert periodic_range_or(0.1, 0.3, 0.2, 0.4) == [(0.1, 0.4)]
+        assert periodic_range_or(0.2, 0.4, 0.1, 0.3) == [(0.1, 0.4)]
+        assert periodic_range_or(1, 2, 3, 4) == [(1, 2), (3, 4)]
+        assert periodic_range_or(3, 4, 1, 2) == [(3, 4), (1, 2)]
+        assert periodic_range_or(1, 4, 2, 3) == [(1, 4)]
+        assert periodic_range_or(2, 3, 1, 4) == [(1, 4)]
+        assert periodic_range_or(1, 2, 1, 2) == 1
+        assert periodic_range_or(1, 2, 2, 1) == -1
+        assert periodic_range_or(0.1, 0.4, 0.3, 0.2) == -1
+        assert periodic_range_or(2, 1, 1, 4) == -1
 
 
     def test_periodic_sub(self):
-        assert_equal(periodic_range_sub(0.1, 0.3, 0.2, 0.4), [(0.1, 0.2)])
-        assert_equal(periodic_range_sub(0.2, 0.4, 0.1, 0.3), [(0.3, 0.4)])
-        assert_equal(periodic_range_sub(1, 2, 3, 4), 1)
-        assert_equal(periodic_range_sub(3, 4, 1, 2), 1)
-        assert_equal(periodic_range_sub(1, 4, 2, 3), [(1, 2), (3, 4)])
-        assert_equal(periodic_range_sub(2, 3, 1, 4), None)
-        assert_equal(periodic_range_sub(1, 2, 1, 2), None)
-        assert_equal(periodic_range_sub(1, 2, 2, 1), 1)
-        assert_equal(periodic_range_sub(2, 1, 1, 4), [(4, 1)])
-        assert_equal(periodic_range_sub(0.1, 0.4, 0.3, 0.2), [(0.2, 0.3)])
-        assert_equal(periodic_range_sub(0.1, 0.4, 0.1, 0.3), [(0.3, 0.4)])
+        assert periodic_range_sub(0.1, 0.3, 0.2, 0.4) == [(0.1, 0.2)]
+        assert periodic_range_sub(0.2, 0.4, 0.1, 0.3) == [(0.3, 0.4)]
+        assert periodic_range_sub(1, 2, 3, 4) == 1
+        assert periodic_range_sub(3, 4, 1, 2) == 1
+        assert periodic_range_sub(1, 4, 2, 3) == [(1, 2), (3, 4)]
+        assert periodic_range_sub(2, 3, 1, 4) is None
+        assert periodic_range_sub(1, 2, 1, 2) is None
+        assert periodic_range_sub(1, 2, 2, 1) == 1
+        assert periodic_range_sub(2, 1, 1, 4) == [(4, 1)]
+        assert periodic_range_sub(0.1, 0.4, 0.3, 0.2) == [(0.2, 0.3)]
+        assert periodic_range_sub(0.1, 0.4, 0.1, 0.3) == [(0.3, 0.4)]
diff --git a/openpathsampling/tests/test_reactive_flux_analysis.py b/openpathsampling/tests/test_reactive_flux_analysis.py
index 454593687..60d0c7821 100644
--- a/openpathsampling/tests/test_reactive_flux_analysis.py
+++ b/openpathsampling/tests/test_reactive_flux_analysis.py
@@ -4,11 +4,8 @@
 from builtins import range
 from past.utils import old_div
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, raises,
-                        assert_almost_equal, assert_true, assert_in,
-                        assert_raises)
-from nose.plugins.skip import SkipTest
-from numpy.testing import assert_array_almost_equal
+import pytest
+from numpy.testing import assert_array_almost_equal, assert_almost_equal
 from .test_helpers import (make_1d_traj, data_filename, assert_items_equal,
                            assert_same_items)
 
@@ -30,7 +27,7 @@
 
 
 class TestReactiveFluxAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         import openpathsampling.engines.toy as toys
         # PES is one-dimensional linear slope (y(x) = -x)
         pes = toys.LinearSlope(m=[-1.0], c=[0.0])
@@ -131,15 +128,15 @@ def setup(self):
         self.simulation.run(n_per_snapshot=1)
         self.analysis = None
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
             self.storage.close()
             os.remove(self.filename)
         paths.EngineMover.default_engine = None
 
     def gradient(self, snapshot):
-        assert_equal(len(snapshot.xyz), 1)
-        assert_equal(len(snapshot.xyz[0]), 1)
+        assert len(snapshot.xyz) == 1
+        assert len(snapshot.xyz[0]) == 1
         return np.array([[1.0]])
 
     def test_analysis(self):
@@ -149,11 +146,11 @@ def test_analysis(self):
 
         # check wrong analyze_single_step() argument
         empty_list = self.analysis.analyze_single_step(3.1415)
-        assert_equal(empty_list, [])
+        assert empty_list == []
 
         # dictionary with three entries returned (mind: trajectories start from
         # x = -0.001, 0.0, 0.001).
-        assert_equal(len(self.analysis), 3)
+        assert len(self.analysis) == 3
 
         # get total and per-snapshot flux.
         flux, flux_dict = self.analysis.flux()
@@ -162,18 +159,18 @@ def test_analysis(self):
         for snapshot in flux_dict.keys():
             if snapshot.xyz[0][0] == -0.001:
                 assert_almost_equal(flux_dict[snapshot], 4.0 / 3.0)
-                assert_equal(self.analysis[snapshot]["accepted"], 1)
-                assert_equal(self.analysis[snapshot]["rejected"], 2)
+                assert self.analysis[snapshot]["accepted"] == 1
+                assert self.analysis[snapshot]["rejected"] == 2
                 assert_almost_equal(self.analysis[snapshot]["sumflux"], 4.0)
             elif snapshot.xyz[0][0] == 0.0:
                 assert_almost_equal(flux_dict[snapshot], 7.0 / 4.0)
-                assert_equal(self.analysis[snapshot]["accepted"], 2)
-                assert_equal(self.analysis[snapshot]["rejected"], 2)
+                assert self.analysis[snapshot]["accepted"] == 2
+                assert self.analysis[snapshot]["rejected"] == 2
                 assert_almost_equal(self.analysis[snapshot]["sumflux"], 7.0)
             elif snapshot.xyz[0][0] == 0.001:
                 assert_almost_equal(flux_dict[snapshot], 19.0 / 5.0)
-                assert_equal(self.analysis[snapshot]["accepted"], 3)
-                assert_equal(self.analysis[snapshot]["rejected"], 2)
+                assert self.analysis[snapshot]["accepted"] == 3
+                assert self.analysis[snapshot]["rejected"] == 2
                 assert_almost_equal(self.analysis[snapshot]["sumflux"], 19.0)
 
         # check total flux.
@@ -203,13 +200,11 @@ def test_analysis(self):
         # check failure of 3D histogram
         hash3D = lambda snap: (snap.xyz[0][0], snap.xyz[0][0], snap.xyz[0][0])
         bins3D = [-0.0015, 0.0005, 0.0015]
-        assert_raises(RuntimeError,
-                      self.analysis.flux_histogram,
-                      hash3D,
-                      bins3D)
-
-        assert_raises(NotImplementedError, self.analysis.committor)
-        assert_raises(NotImplementedError,
-                      self.analysis.committor_histogram,
-                      hash1D,
-                      bins1D)
+        with pytest.raises(RuntimeError):
+            self.analysis.flux_histogram(hash3D, bins3D)
+
+        with pytest.raises(NotImplementedError):
+            self.analysis.committor()
+
+        with pytest.raises(NotImplementedError):
+            self.analysis.committor_histogram(hash1D, bins1D)
diff --git a/openpathsampling/tests/test_resampling_statistics.py b/openpathsampling/tests/test_resampling_statistics.py
index 2ef37d927..8bac9f588 100644
--- a/openpathsampling/tests/test_resampling_statistics.py
+++ b/openpathsampling/tests/test_resampling_statistics.py
@@ -1,6 +1,5 @@
 import pandas as pd
 from pandas.testing import assert_frame_equal
-from nose.tools import assert_equal
 from .test_helpers import assert_items_equal
 import openpathsampling as paths
 
@@ -13,7 +12,7 @@
 
 class TestResamplingStatistics(object):
     # NOTE: we test the mean_df and std_df functions within this
-    def setup(self):
+    def setup_method(self):
         # order of the column/index labels should not matter
         self.list_AB = ['A', 'B']
         self.list_BA = ['B', 'A']
@@ -86,17 +85,17 @@ def test_percentile(self):
 
 
 class TestBlockResampling(object):
-    def setup(self):
+    def setup_method(self):
         self.samples = list(range(100))
 
     def test_default_initialization(self):
         resampler = paths.numerics.BlockResampling(self.samples)
-        assert_equal(resampler.n_total_samples, 100)
-        assert_equal(resampler.n_blocks, 20)
-        assert_equal(resampler.n_per_block, 5)
-        assert_equal(len(resampler.blocks), 20)
-        assert_equal(resampler.n_resampled, 100)
-        assert_equal(resampler.unassigned, [])
+        assert resampler.n_total_samples == 100
+        assert resampler.n_blocks == 20
+        assert resampler.n_per_block == 5
+        assert len(resampler.blocks) == 20
+        assert resampler.n_resampled == 100
+        assert resampler.unassigned == []
         assert_items_equal(resampler.blocks[0], list(range(0, 5)))
         assert_items_equal(resampler.blocks[1], list(range(5, 10)))
         assert_items_equal(resampler.blocks[10], list(range(50, 55)))
@@ -104,34 +103,34 @@ def test_default_initialization(self):
 
     def test_n_blocks_initialization(self):
         resampler = paths.numerics.BlockResampling(self.samples, n_blocks=10)
-        assert_equal(resampler.n_total_samples, 100)
-        assert_equal(resampler.n_blocks, 10)
-        assert_equal(resampler.n_per_block, 10)
-        assert_equal(len(resampler.blocks), 10)
-        assert_equal(resampler.n_resampled, 100)
-        assert_equal(resampler.unassigned, [])
+        assert resampler.n_total_samples == 100
+        assert resampler.n_blocks == 10
+        assert resampler.n_per_block == 10
+        assert len(resampler.blocks) == 10
+        assert resampler.n_resampled == 100
+        assert resampler.unassigned == []
         assert_items_equal(resampler.blocks[0], list(range(0, 10)))
         assert_items_equal(resampler.blocks[1], list(range(10, 20)))
         assert_items_equal(resampler.blocks[5], list(range(50, 60)))
         assert_items_equal(resampler.blocks[-1], list(range(90, 100)))
 
         resampler_2 = paths.numerics.BlockResampling(self.samples, n_blocks=8)
-        assert_equal(resampler_2.n_total_samples, 100)
-        assert_equal(resampler_2.n_blocks, 8)
-        assert_equal(resampler_2.n_per_block, 12)
-        assert_equal(resampler_2.n_resampled, 96)
+        assert resampler_2.n_total_samples == 100
+        assert resampler_2.n_blocks == 8
+        assert resampler_2.n_per_block == 12
+        assert resampler_2.n_resampled == 96
         assert_items_equal(resampler_2.blocks[-1], list(range(84, 96)))
         assert_items_equal(resampler_2.unassigned, list(range(96, 100)))
 
     def test_n_per_block_initialization(self):
         resampler = paths.numerics.BlockResampling(self.samples,
                                                    n_per_block=10)
-        assert_equal(resampler.n_total_samples, 100)
-        assert_equal(resampler.n_blocks, 10)
-        assert_equal(resampler.n_per_block, 10)
-        assert_equal(len(resampler.blocks), 10)
-        assert_equal(resampler.n_resampled, 100)
-        assert_equal(resampler.unassigned, [])
+        assert resampler.n_total_samples == 100
+        assert resampler.n_blocks == 10
+        assert resampler.n_per_block == 10
+        assert len(resampler.blocks) == 10
+        assert resampler.n_resampled == 100
+        assert resampler.unassigned == []
         assert_items_equal(resampler.blocks[0], list(range(0, 10)))
         assert_items_equal(resampler.blocks[1], list(range(10, 20)))
         assert_items_equal(resampler.blocks[5], list(range(50, 60)))
@@ -139,20 +138,20 @@ def test_n_per_block_initialization(self):
 
         resampler_2 = paths.numerics.BlockResampling(self.samples,
                                                      n_per_block=8)
-        assert_equal(resampler_2.n_total_samples, 100)
-        assert_equal(resampler_2.n_blocks, 12)
-        assert_equal(resampler_2.n_resampled, 96)
+        assert resampler_2.n_total_samples == 100
+        assert resampler_2.n_blocks == 12
+        assert resampler_2.n_resampled == 96
         assert_items_equal(resampler_2.blocks[-1], list(range(88, 96)))
         assert_items_equal(resampler_2.unassigned, list(range(96, 100)))
 
     def test_n_blocks_and_n_per_block_initialization(self):
         resampler = paths.numerics.BlockResampling(self.samples, n_blocks=9,
                                                    n_per_block=10)
-        assert_equal(resampler.n_total_samples, 100)
-        assert_equal(resampler.n_blocks, 9)
-        assert_equal(resampler.n_per_block, 10)
-        assert_equal(len(resampler.blocks), 9)
-        assert_equal(resampler.n_resampled, 90)
+        assert resampler.n_total_samples == 100
+        assert resampler.n_blocks == 9
+        assert resampler.n_per_block == 10
+        assert len(resampler.blocks) == 9
+        assert resampler.n_resampled == 90
         assert_items_equal(resampler.unassigned, list(range(90, 100)))
         assert_items_equal(resampler.blocks[0], list(range(0, 10)))
         assert_items_equal(resampler.blocks[1], list(range(10, 20)))
diff --git a/openpathsampling/tests/test_sample.py b/openpathsampling/tests/test_sample.py
index 6966037eb..5b98e4f0a 100644
--- a/openpathsampling/tests/test_sample.py
+++ b/openpathsampling/tests/test_sample.py
@@ -4,8 +4,7 @@
 
 from builtins import range
 from builtins import object
-from nose.plugins.skip import SkipTest
-from nose.tools import (assert_equal, raises, assert_true, assert_false)
+import pytest
 from .test_helpers import assert_items_equal, assert_same_items
 
 from openpathsampling.engines.trajectory import Trajectory
@@ -15,7 +14,7 @@
 
 
 class TestSampleSet(object):
-    def setup(self):
+    def setup_method(self):
         self.ensA = LengthEnsemble(1)
         self.ensB = LengthEnsemble(2)
         traj0A = Trajectory([0.5])
@@ -30,8 +29,7 @@ def setup(self):
 
     def test_equality(self):
         testset2 = SampleSet([self.s0A, self.s1A, self.s2B])
-        assert_true(self.testset == testset2)
-        assert_false(self.testset != testset2)
+        assert self.testset == testset2
 
     def test_initialization(self):
         self.testset.consistency_check()
@@ -39,24 +37,24 @@ def test_initialization(self):
     def test_iter(self):
         samps = [self.s0A, self.s1A, self.s2B]
         for samp in self.testset:
-            assert_equal(samp in samps, True)
+            assert samp in samps
 
     def test_contains(self):
         samps = [self.s0A, self.s1A, self.s2B]
         for samp in samps:
-            assert_equal(samp in self.testset, True)
+            assert samp in self.testset
 
     def test_len(self):
-        assert_equal(len(self.testset), 3)
+        assert len(self.testset) == 3
 
     def test_getitem_ensemble(self):
-        assert_equal(self.testset[self.ensB], self.s2B)
+        assert self.testset[self.ensB] == self.s2B
         # TODO: add test that we pick at random for ensA
 
     def test_getitem_replica(self):
-        assert_equal(self.testset[0], self.s0A)
-        assert_equal(self.testset[1], self.s1A)
-        assert_equal(self.testset[2], self.s2B)
+        assert self.testset[0] == self.s0A
+        assert self.testset[1] == self.s1A
+        assert self.testset[2] == self.s2B
         # TODO: add test that we pick at random
 
     def test_setitem_ensemble(self):
@@ -65,14 +63,14 @@ def test_setitem_ensemble(self):
         s3C = Sample(replica=3, trajectory=traj3C, ensemble=ensC)
         self.testset[ensC] = s3C
         self.testset.consistency_check()
-        assert_equal(len(self.testset), 4)
+        assert len(self.testset) == 4
 
     def test_setitem_replace_ensemble(self):
         self.testset[self.ensB] = self.s2B_
-        assert_equal(self.s2B in self.testset, False)
-        assert_equal(self.s2B_ in self.testset, True)
-        assert_equal(self.testset[2], self.s2B_)
-        assert_equal(self.testset[self.ensB], self.s2B_)
+        assert self.s2B not in self.testset
+        assert self.s2B_ in self.testset
+        assert self.testset[2] == self.s2B_
+        assert self.testset[self.ensB] == self.s2B_
         # TODO add test that we replace at random
 
     def test_setitem_replica(self):
@@ -81,32 +79,32 @@ def test_setitem_replica(self):
         s3C = Sample(replica=3, trajectory=traj3C, ensemble=ensC)
         self.testset[3] = s3C
         self.testset.consistency_check()
-        assert_equal(len(self.testset), 4)
+        assert len(self.testset) == 4
 
     def test_setitem_replace_replica(self):
         self.testset[2] = self.s2B_
-        assert_equal(self.s2B in self.testset, False)
-        assert_equal(self.s2B_ in self.testset, True)
-        assert_equal(self.testset[2], self.s2B_)
+        assert self.s2B not in self.testset
+        assert self.s2B_ in self.testset
+        assert self.testset[2] == self.s2B_
         # TODO add test that we replace at random
 
-    @raises(SampleKeyError)
     def test_illegal_assign_ensemble(self):
-        self.testset[self.ensA] = self.s2B_
+        with pytest.raises(SampleKeyError):
+            self.testset[self.ensA] = self.s2B_
 
-    @raises(SampleKeyError)
     def test_illegal_assign_replica(self):
-        self.testset[0] = self.s2B_
+        with pytest.raises(SampleKeyError):
+            self.testset[0] = self.s2B_
 
     def test_setitem_itemexists_ensemble(self):
         # exact sample is already there
         testset2 = SampleSet([self.s0A, self.s1A, self.s2B])
         # if we accidentally hit the statistical code, repeating 100 times
         # reduces odds of getting the same replacement each time
-        for i in range(100): 
+        for i in range(100):
             self.testset[self.ensA] = self.s0A
             self.testset.consistency_check()
-            assert_equal(len(self.testset), 3)
+            assert len(self.testset) == 3
             assert_items_equal(self.testset, testset2)
 
     def test_setitem_itemexists_replica(self):
@@ -114,14 +112,14 @@ def test_setitem_itemexists_replica(self):
         testset2 = SampleSet([self.s0A, self.s1A, self.s2B, self.s2B_])
         testset[2] = self.s2B
         testset.consistency_check()
-        assert_equal(len(testset), 4)
+        assert len(testset) == 4
         assert_items_equal(testset, testset2)
 
     def test_additem(self):
         testset2 = SampleSet([self.s0A, self.s1A, self.s2B, self.s2B_])
         self.testset.append(self.s2B_)
-        assert_equal(self.s2B_ in self.testset, True)
-        assert_equal(len(self.testset), 4)
+        assert self.s2B_ in self.testset
+        assert len(self.testset) == 4
         self.testset.consistency_check()
         assert_items_equal(self.testset, testset2)
 
@@ -130,41 +128,41 @@ def test_additem_itemexists(self):
         testset2 = SampleSet([self.s0A, self.s1A, self.s2B])
         self.testset.append(self.s2B)
         self.testset.consistency_check()
-        assert_equal(len(self.testset), 3)
+        assert len(self.testset) == 3
         assert_items_equal(self.testset, testset2)
 
     def test_del_sample(self):
         del self.testset[self.s2B]
-        assert_equal(len(self.testset), 2)
-        assert_equal(self.ensB in list(self.testset.ensemble_dict.keys()), False)
-        assert_equal(self.ensA in list(self.testset.ensemble_dict.keys()), True)
-        assert_equal(2 in list(self.testset.replica_dict.keys()), False)
-        assert_equal(0 in list(self.testset.replica_dict.keys()), True)
+        assert len(self.testset) == 2
+        assert self.ensB not in list(self.testset.ensemble_dict.keys())
+        assert self.ensA in list(self.testset.ensemble_dict.keys())
+        assert 2 not in list(self.testset.replica_dict.keys())
+        assert 0 in list(self.testset.replica_dict.keys())
 
     def test_del_ensemble(self):
-        raise SkipTest
+        pytest.skip()
 
     def test_del_replica(self):
-        raise SkipTest
+        pytest.skip()
 
     def test_apply_samples(self):
-        raise SkipTest
+        pytest.skip()
 
     def test_extend(self):
         testset = SampleSet([self.s0A])
         testset.extend([self.s1A])
-        assert_equal(len(testset), 2)
-        assert_equal(self.s1A in testset, True)
+        assert len(testset) == 2
+        assert self.s1A in testset
         testset.consistency_check()
 
         testset.extend(SampleSet([self.s2B]))
-        assert_equal(len(testset), 3)
-        assert_equal(self.s2B in testset, True)
+        assert len(testset) == 3
+        assert self.s2B in testset
         testset.consistency_check()
 
         testset.extend(self.s2B_)
-        assert_equal(len(testset), 4)
-        assert_equal(self.s2B_ in testset, True)
+        assert len(testset) == 4
+        assert self.s2B_ in testset
         testset.consistency_check()
 
     def test_replica_list(self):
@@ -186,33 +184,33 @@ def test_all_from_replica(self):
         self.testset.append(self.s2B_)
         assert_items_equal(self.testset.all_from_replica(2), [self.s2B, self.s2B_])
 
-    @raises(AssertionError)
     def test_consistency_fail_size_ensdict(self):
         del self.testset.ensemble_dict[self.ensB]
-        self.testset.consistency_check()
+        with pytest.raises(AssertionError):
+            self.testset.consistency_check()
 
-    @raises(AssertionError)
     def test_consistency_fail_size_repdict(self):
         del self.testset.replica_dict[0]
-        self.testset.consistency_check()
+        with pytest.raises(AssertionError):
+            self.testset.consistency_check()
 
     def test_consistency_fail_sample_in_ensdict(self):
-        raise SkipTest
+        pytest.skip()
 
     def test_consistency_fail_sample_in_repdict(self):
-        raise SkipTest
+        pytest.skip()
 
     def test_consistency_fail_duplicate_samples(self):
-        raise SkipTest
+        pytest.skip()
 
     def test_sanity(self):
         self.testset.sanity_check()
 
-    @raises(AssertionError)
     def test_sanity_insane(self):
         traj0A = self.s0A.trajectory
         ensB = self.s2B.ensemble
         bad_samp = Sample(replica=0, trajectory=traj0A, ensemble=ensB)
         testset = SampleSet([bad_samp])
-        testset.sanity_check()
+        with pytest.raises(AssertionError):
+            testset.sanity_check()
 
diff --git a/openpathsampling/tests/test_shared.py b/openpathsampling/tests/test_shared.py
index 8e9422110..b210e7b1c 100644
--- a/openpathsampling/tests/test_shared.py
+++ b/openpathsampling/tests/test_shared.py
@@ -10,7 +10,7 @@
 
 
 class TestStaticContainerStore(object):
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile("test.nc"):
             os.remove("test.nc")
 
diff --git a/openpathsampling/tests/test_shooting.py b/openpathsampling/tests/test_shooting.py
index 1c65d56cf..9c352a5df 100644
--- a/openpathsampling/tests/test_shooting.py
+++ b/openpathsampling/tests/test_shooting.py
@@ -14,7 +14,7 @@
 
 
 class SelectorTest(object):
-    def setup(self):
+    def setup_method(self):
         self.mytraj = make_1d_traj(coordinates=[-0.5, 0.1, 0.2, 0.3, 0.5],
                                    velocities=[1.0, 1.0, 1.0, 1.0, 1.0])
         self.dyn = CalvinistDynamics([-0.5, -0.4, -0.3, -0.2, -0.1,
@@ -69,8 +69,8 @@ def test_pick(self):
 
 
 class TestGaussianBiasSelector(SelectorTest):
-    def setup(self):
-        super(TestGaussianBiasSelector, self).setup()
+    def setup_method(self):
+        super(TestGaussianBiasSelector, self).setup_method()
         self.cv = paths.FunctionCV("Id", lambda x: x.xyz[0][0])
         self.sel = GaussianBiasSelector(self.cv, alpha=2.0, l_0=0.25)
         self.f = [
@@ -102,8 +102,8 @@ def test_probability(self, frame):
 
 
 class TestBiasedSelector(SelectorTest):
-    def setup(self):
-        super(TestBiasedSelector, self).setup()
+    def setup_method(self):
+        super(TestBiasedSelector, self).setup_method()
         self.f = {
             'gaussian': [
                 0.32465246735834974,  # = exp(-2.0*(-0.5-0.25)**2)
@@ -202,7 +202,7 @@ def test_f(self):
 
 
 class TestConstrainedSelector(SelectorTest):
-    def setup(self):
+    def setup_method(self):
         cvx = paths.FunctionCV('ID', lambda snap: snap.xyz[0][0])
         vol = paths.CVDefinedVolume(cvx, float('-inf'), 0)
         self.sel = InterfaceConstrainedSelector(vol)
diff --git a/openpathsampling/tests/test_shooting_point_analysis.py b/openpathsampling/tests/test_shooting_point_analysis.py
index ba999edad..fb2fb5e71 100644
--- a/openpathsampling/tests/test_shooting_point_analysis.py
+++ b/openpathsampling/tests/test_shooting_point_analysis.py
@@ -26,7 +26,7 @@
 
 
 class TestTransformedDict(object):
-    def setup(self):
+    def setup_method(self):
         self.untransformed = {(0, 1): "a", (1, 2): "b", (2, 3): "c"}
         self.transformed = {0: "a", 1: "b", 2: "c"}
         self.hash_function = lambda x: x[0]
@@ -72,7 +72,7 @@ def test_rehash(self):
 
 
 class TestSnapshotByCoordinateDict(object):
-    def setup(self):
+    def setup_method(self):
         self.empty_dict = SnapshotByCoordinateDict()
         coords_A = np.array([[0.0, 0.0]])
         coords_B = np.array([[1.0, 1.0]])
@@ -96,7 +96,7 @@ def test_get_set(self):
 
 
 class TestShootingPointAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False
         # taken from the TestCommittorSimulation
@@ -145,7 +145,7 @@ def setup(self):
         self.analyzer = ShootingPointAnalysis(self.storage.steps,
                                               [self.left, self.right])
 
-    def teardown(self):
+    def teardown_method(self):
         import os
         paths.progress.HAS_TQDM = self.HAS_TQDM
         self.storage.close()
diff --git a/openpathsampling/tests/test_snapshot.py b/openpathsampling/tests/test_snapshot.py
index 98fb1b2b8..b641bb9b7 100644
--- a/openpathsampling/tests/test_snapshot.py
+++ b/openpathsampling/tests/test_snapshot.py
@@ -5,9 +5,6 @@
 import pytest
 
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, assert_is, raises,
-                        assert_true)
-from nose.plugins.skip import Skip, SkipTest
 from numpy.testing import assert_allclose
 from .test_helpers import CallIdentity, raises_with_message_like, assert_close_unit
 
@@ -58,9 +55,9 @@ def test_copy_none(self):
                                     velocities=np.array([[0.0, 0.0, 0.0],
                                                          [0.0, 0.0, 0.0]]))
         new_snap = snap.copy()
-        assert_true(new_snap is not snap)
-        assert_true(new_snap.coordinates is not snap.coordinates)
+        assert new_snap is not snap
+        assert new_snap.coordinates is not snap.coordinates
         assert_allclose(new_snap.coordinates, snap.coordinates)
-        assert_true(new_snap.box_vectors is snap.box_vectors)
-        assert_true(new_snap.box_vectors is None)
-        assert_true(new_snap.engine is snap.engine)
+        assert new_snap.box_vectors is snap.box_vectors
+        assert new_snap.box_vectors is None
+        assert new_snap.engine is snap.engine
diff --git a/openpathsampling/tests/test_snapshot_modifier.py b/openpathsampling/tests/test_snapshot_modifier.py
index 4a25d0eda..89582891e 100644
--- a/openpathsampling/tests/test_snapshot_modifier.py
+++ b/openpathsampling/tests/test_snapshot_modifier.py
@@ -31,7 +31,7 @@
 
 
 class TestSnapshotModifier(object):
-    def setup(self):
+    def setup_method(self):
         # TODO OPS 2.0: This subclass is only here for python 2.7 should be
         # replaced with SnapshotModifier
         class DummyMod(SnapshotModifier):
@@ -100,8 +100,8 @@ def test_apply_to_subset(self):
 
 
 class TestNoModification(TestSnapshotModifier):
-    def setup(self):
-        super(TestNoModification, self).setup()
+    def setup_method(self):
+        super(TestNoModification, self).setup_method()
         self.modifier = NoModification()
 
     def test_call(self):
@@ -133,7 +133,7 @@ def test_probability_ratio(self):
 
 
 class TestRandomizeVelocities(object):
-    def setup(self):
+    def setup_method(self):
         # TODO: check against several possibilities, including various
         # combinations of shapes of velocities and masses.
         topology_2x3D = paths.engines.toy.Topology(
@@ -271,7 +271,7 @@ def test_probability_ratio(self):
 
 
 class TestGeneralizedDirectionModifier(object):
-    def setup(self):
+    def setup_method(self):
         import openpathsampling.engines.toy as toys
         # applies one delta_v to all atoms
         self.toy_modifier_all = GeneralizedDirectionModifier(1.5)
@@ -462,7 +462,7 @@ def test_probability_ratio(self):
 
 
 class TestVelocityDirectionModifier(object):
-    def setup(self):
+    def setup_method(self):
         import openpathsampling.engines.toy as toys
         self.toy_modifier = VelocityDirectionModifier(
             delta_v=[1.0, 2.0],
@@ -573,7 +573,7 @@ def test_call_with_linear_momentum_fix(self):
 
 
 class TestSingleAtomVelocityDirectionModifier(object):
-    def setup(self):
+    def setup_method(self):
         import openpathsampling.engines.toy as toys
         self.toy_modifier = SingleAtomVelocityDirectionModifier(
             delta_v=[1.0, 2.0],
diff --git a/openpathsampling/tests/test_spring_shooting.py b/openpathsampling/tests/test_spring_shooting.py
index 5aa7c2f1f..461914701 100644
--- a/openpathsampling/tests/test_spring_shooting.py
+++ b/openpathsampling/tests/test_spring_shooting.py
@@ -23,7 +23,7 @@ def __init__(self, details):
 
 
 class SelectorTest(object):
-    def setup(self):
+    def setup_method(self):
         self.mytraj = make_1d_traj(coordinates=[-0.5, 0.1, 0.2, 0.3, 0.5],
                                    velocities=[1.0, 1.0, 1.0, 1.0, 1.0])
         self.dyn = CalvinistDynamics([-0.5, -0.4, -0.3, -0.2, -0.1,
@@ -38,20 +38,17 @@ def setup(self):
 
 class TestSpringShootingSelector(SelectorTest):
 
-    @staticmethod
-    def test_neg_delta():
+    def test_neg_delta(self):
         with pytest.raises(ValueError):
             SpringShootingSelector(delta_max=-1,
                                    k_spring=0.1)
 
-    @staticmethod
-    def test_0_delta():
+    def test_0_delta(self):
         with pytest.raises(ValueError):
             SpringShootingSelector(delta_max=0,
                                    k_spring=0.1)
 
-    @staticmethod
-    def test_pos_delta():
+    def test_pos_delta(self):
         delta_max = 1
         sel = SpringShootingSelector(delta_max=delta_max,
                                      k_spring=0.1)
@@ -59,14 +56,12 @@ def test_pos_delta():
         assert len(sel._fw_prob_list) == 2*delta_max+1
         assert len(sel._bw_prob_list) == 2*delta_max+1
 
-    @staticmethod
-    def test_neg_k():
+    def test_neg_k(self):
         with pytest.raises(ValueError):
             SpringShootingSelector(delta_max=1,
                                    k_spring=-1)
 
-    @staticmethod
-    def test_0_k():
+    def test_0_k(self):
         sel = SpringShootingSelector(delta_max=1,
                                      k_spring=0)
         ref_list = [1.0, 1.0, 1.0]
@@ -74,22 +69,19 @@ def test_0_k():
         assert sel._fw_prob_list == ref_list
         assert sel._bw_prob_list == ref_list
 
-    @staticmethod
-    def test_pos_k():
+    def test_pos_k(self):
         sel = SpringShootingSelector(delta_max=1,
                                      k_spring=1)
         assert sel.k_spring == 1
 
-    @staticmethod
-    def test_sanity_breaking_fw():
+    def test_sanity_breaking_fw(self):
         sel = SpringShootingSelector(delta_max=1,
                                      k_spring=1)
         sel._fw_prob_list = [0, 0, 0]
         with pytest.raises(RuntimeError):
             sel.check_sanity()
 
-    @staticmethod
-    def test_sanity_breaking_bw():
+    def test_sanity_breaking_bw(self):
         sel = SpringShootingSelector(delta_max=1,
                                      k_spring=1)
         sel._bw_prob_list = [0, 0, 0]
@@ -97,16 +89,14 @@ def test_sanity_breaking_bw():
         with pytest.raises(RuntimeError):
             sel.check_sanity()
 
-    @staticmethod
-    def test_sanity_breaking_total():
+    def test_sanity_breaking_total(self):
         sel = SpringShootingSelector(delta_max=1,
                                      k_spring=1)
         sel._total_bias = sum([0, 0, 0])
         with pytest.raises(RuntimeError):
             sel.check_sanity()
 
-    @staticmethod
-    def test_probability_ratio():
+    def test_probability_ratio(self):
         sel = SpringShootingSelector(delta_max=1,
                                      k_spring=1)
         sel._acceptable_snapshot = True
@@ -141,8 +131,7 @@ def test_sanity_breaking_total_pick(self):
         with pytest.raises(RuntimeError):
             sel.pick(trajectory=self.mytraj, direction='forward')
 
-    @staticmethod
-    def test_initial_guess():
+    def test_initial_guess(self):
         sel = SpringShootingSelector(delta_max=1, k_spring=1, initial_guess=12)
         assert sel.trial_snapshot == 12
         assert sel.previous_snapshot == 12
@@ -272,16 +261,14 @@ def test_illegal_backward_pick(self):
         assert sel.previous_snapshot == self.initial_guess
         assert sel._acceptable_snapshot is False
 
-    @staticmethod
-    def test_failed_loading():
+    def test_failed_loading(self):
         sel = SpringShootingSelector(delta_max=1, k_spring=0, initial_guess=3)
         details = Details(foo='bar')
         step = FakeStep(details)
         with pytest.raises(RuntimeError):
             sel.restart_from_step(step)
 
-    @staticmethod
-    def test_correct_loading():
+    def test_correct_loading(self):
         sel = SpringShootingSelector(delta_max=1, k_spring=0, initial_guess=3)
         details = Details(initial_trajectory='foo',
                           last_accepted_shooting_index='bar',
@@ -308,8 +295,8 @@ def test_deprecation(self):
 
 
 class MoverTest(SelectorTest):
-    def setup(self):
-        super(MoverTest, self).setup()
+    def setup_method(self):
+        super(MoverTest, self).setup_method()
         sel = SpringShootingSelector(delta_max=1, k_spring=0)
         sel._fw_prob_list = [1.0, 0.0, 0.0]
         sel._bw_prob_list = [0.0, 0.0, 1.0]
@@ -413,8 +400,7 @@ def test_dict_cycle(self):
 
 
 class TestSpringShootingStrategy(MoveStrategyTestSetup):
-    @staticmethod
-    def test_init():
+    def test_init(self):
         strategy = SpringShootingStrategy(delta_max=1, k_spring=0.0,
                                           initial_guess=3)
         assert strategy.delta_max == 1
diff --git a/openpathsampling/tests/test_sshooting_analysis.py b/openpathsampling/tests/test_sshooting_analysis.py
index e94973c2a..93dcb59ac 100644
--- a/openpathsampling/tests/test_sshooting_analysis.py
+++ b/openpathsampling/tests/test_sshooting_analysis.py
@@ -19,7 +19,7 @@
 
 
 class TestSShootingAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         # PES is one-dimensional negative slope (y(x) = -x)
         pes = toys.LinearSlope(m=[-1.0], c=[0.0])
         topology = toys.Topology(n_spatial=1, masses=[1.0], pes=pes)
@@ -83,7 +83,7 @@ def setup(self):
                                                   self.state_B,
                                                   self.state_S])
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
             os.remove(self.filename)
         paths.EngineMover.default_engine = None
diff --git a/openpathsampling/tests/test_sshooting_simulator.py b/openpathsampling/tests/test_sshooting_simulator.py
index 4a62140de..ab334f75b 100644
--- a/openpathsampling/tests/test_sshooting_simulator.py
+++ b/openpathsampling/tests/test_sshooting_simulator.py
@@ -6,7 +6,7 @@
 import os
 
 class TestSShootingSimulation(object):
-    def setup(self):
+    def setup_method(self):
         # PES is one-dimensional zero function (y(x) = 0)
         pes = toys.LinearSlope(m=[0.0], c=[0.0])
         topology = toys.Topology(n_spatial=1, masses=[1.0], pes=pes)
@@ -57,7 +57,7 @@ def setup(self):
         )
         self.simulation.output_stream = open(os.devnull, 'w')
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
             os.remove(self.filename)
         paths.EngineMover.default_engine = None
diff --git a/openpathsampling/tests/test_storage.py b/openpathsampling/tests/test_storage.py
index 992c5f039..9c0819704 100644
--- a/openpathsampling/tests/test_storage.py
+++ b/openpathsampling/tests/test_storage.py
@@ -10,8 +10,6 @@
 
 import pytest
 
-from nose.tools import (assert_equal)
-
 import openpathsampling as paths
 
 import openpathsampling.engines.openmm as peng
@@ -22,13 +20,12 @@
 from .test_helpers import (data_filename, md, compare_snapshot)
 
 import numpy as np
-from nose.plugins.skip import SkipTest
 
 
 class TestStorage(object):
-    def setup(self):
+    def setup_method(self):
         if not md:
-            raise SkipTest("mdtraj not installed")
+            pytest.skip("mdtraj not installed")
         self.mdtraj = md.load(data_filename("ala_small_traj.pdb"))
         _ = pytest.importorskip('simtk.unit')
         self.traj = peng.trajectory_from_mdtraj(
@@ -55,7 +52,7 @@ def setup(self):
             engine=self.engine
         )
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile(self.filename):
             os.remove(self.filename)
 
@@ -68,7 +65,7 @@ def test_create_storage(self):
         store.close()
 
     def test_stored_topology(self):
-        raise SkipTest
+        pytest.skip()
         store = Storage(
             filename=self.filename,
             mode='w')
@@ -80,10 +77,8 @@ def test_stored_topology(self):
         # check if path topologies have the same JSON string
         # this also tests the simplifier for topologies
 
-        assert_equal(
-            self.simplifier.to_json(self.template_snapshot.topology),
-            self.simplifier.to_json(loaded_topology)
-        )
+        assert (self.simplifier.to_json(self.template_snapshot.topology)
+                == self.simplifier.to_json(loaded_topology))
 
         store.close()
 
diff --git a/openpathsampling/tests/test_tis_analysis.py b/openpathsampling/tests/test_tis_analysis.py
index e96f36bec..1667b3ba7 100644
--- a/openpathsampling/tests/test_tis_analysis.py
+++ b/openpathsampling/tests/test_tis_analysis.py
@@ -1,7 +1,7 @@
 import itertools
 import random
 import pytest
-from nose.tools import assert_equal, assert_almost_equal, raises
+from numpy.testing import assert_almost_equal
 from .test_helpers import (make_1d_traj, MoverWithSignature, RandomMDEngine,
                            assert_frame_equal, assert_items_equal)
 
@@ -36,10 +36,10 @@ def make_tis_traj_fixed_steps(n_steps, step_size=0.1, reverse=False):
 class TestMultiEnsembleSamplingAnalyzer(object):
     # this only has to check the error generation; everything else gets
     # covered by tests of subclasses
-    @raises(RuntimeError)
     def test_no_ensembles(self):
         histogrammer = MultiEnsembleSamplingAnalyzer()
-        histogrammer.calculate([])
+        with pytest.raises(RuntimeError):
+            histogrammer.calculate([])
 
 
 class TISAnalysisTester(object):
@@ -100,7 +100,7 @@ def sampling_ensembles_for_transition(self, network, state_A, state_B):
         sampling_AB = network.analysis_to_sampling[analysis_AB][0]
         return sampling_AB.ensembles
 
-    def setup(self):
+    def setup_method(self):
         # set up the trajectories, ensembles, etc. for this test
         self.HAS_TQDM = paths.progress.HAS_TQDM
         paths.progress.HAS_TQDM = False  # turn of progress bars
@@ -161,7 +161,7 @@ def setup(self):
         )
 
 
-    def teardown(self):
+    def teardown_method(self):
         paths.progress.HAS_TQDM = self.HAS_TQDM
 
 
@@ -181,23 +181,19 @@ def _check_network_results(self, network, weighted_trajs):
                    (2, 0): 0, (2, 1): 0, (2, 2): 2, (2, 3): 2}
 
         for ((ens, traj), result) in results.items():
-            assert_equal(
-                weighted_trajs[ensembles_AB[ens]][self.trajs_AB[traj]],
-                result
-            )
-            assert_equal(
-                weighted_trajs[ensembles_BA[ens]][self.trajs_BA[traj]],
-                result
-            )
+            assert (weighted_trajs[ensembles_AB[ens]][self.trajs_AB[traj]]
+                    == result)
+            assert (weighted_trajs[ensembles_BA[ens]][self.trajs_BA[traj]]
+                    == result)
 
     def test_steps_to_weighted_trajectories(self):
-        assert_equal(len(self.mistis_weighted_trajectories),
-                     len(self.mistis.sampling_ensembles))
+        assert (len(self.mistis_weighted_trajectories)
+                == len(self.mistis.sampling_ensembles))
         self._check_network_results(self.mistis,
                                     self.mistis_weighted_trajectories)
 
-        assert_equal(len(self.mstis_weighted_trajectories),
-                     len(self.mstis.sampling_ensembles))
+        assert (len(self.mstis_weighted_trajectories)
+                == len(self.mstis.sampling_ensembles))
         self._check_network_results(self.mstis,
                                     self.mstis_weighted_trajectories)
 
@@ -205,8 +201,8 @@ def test_steps_to_weighted_trajectories(self):
 class TestFluxToPandas(TISAnalysisTester):
     # includes tests for default_flux_sort and flux_matrix_pd
     # as a class to simplify setup of flux objects
-    def setup(self):
-        super(TestFluxToPandas, self).setup()
+    def setup_method(self):
+        super(TestFluxToPandas, self).setup_method()
         interfaces_A = self.mstis.from_state[self.state_A].interfaces
         interfaces_B = self.mstis.from_state[self.state_B].interfaces
         pairs_A = list(itertools.product([self.state_A], interfaces_A))
@@ -249,14 +245,14 @@ def test_flux_matrix_pd_None(self):
         for idx in self.indices:
             assert_almost_equal(series[idx], self.expected_series[idx])
 
-    @raises(KeyError)
     def test_flux_matrix_pd_unknown_str(self):
-        flux_matrix_pd(self.fluxes, sort_method="foo")
+        with pytest.raises(KeyError):
+            flux_matrix_pd(self.fluxes, sort_method="foo")
 
 
 class TestDictFlux(TISAnalysisTester):
-    def setup(self):
-        super(TestDictFlux, self).setup()
+    def setup_method(self):
+        super(TestDictFlux, self).setup_method()
         self.innermost_interface_A = \
             self.sampling_ensembles_for_transition(self.mistis,
                                                    self.state_A,
@@ -271,45 +267,38 @@ def setup(self):
         self.flux_method = DictFlux(self.flux_dict)
 
     def test_calculate(self):
-        assert_equal(self.flux_method.calculate(self.mistis_steps),
-                     self.flux_dict)
+        assert self.flux_method.calculate(self.mistis_steps) == self.flux_dict
 
     def test_from_weighted_trajectories(self):
-        assert_equal(
-            self.flux_method.from_weighted_trajectories(self.mistis_steps),
-            self.flux_dict
-        )
+        fd = self.flux_method.from_weighted_trajectories(self.mistis_steps)
+        assert fd == self.flux_dict
 
     def test_intermediates(self):
-        assert_equal(self.flux_method.intermediates(self.mistis_steps), [])
+        assert self.flux_method.intermediates(self.mistis_steps) == []
 
     def test_calculate_from_intermediates(self):
         intermediates = self.flux_method.intermediates(self.mistis_steps)
-        assert_equal(
-            self.flux_method.calculate_from_intermediates(*intermediates),
-            self.flux_dict
-        )
+        fd = self.flux_method.calculate_from_intermediates(*intermediates)
+        assert fd == self.flux_dict
 
     def test_combine_results(self):
         my_result = self.flux_method.calculate(self.mistis_steps)
         same_result = {(self.state_A, self.innermost_interface_A): 1.0,
                        (self.state_B, self.innermost_interface_B): 1.0}
-        assert_equal(
-            self.flux_method.combine_results(my_result, same_result),
-            my_result
-        )
+        res = self.flux_method.combine_results(my_result, same_result)
+        assert res == my_result
 
-    @raises(RuntimeError)
     def test_bad_combine_results(self):
         my_result = self.flux_method.calculate(self.mistis_steps)
         bad_result = {(self.state_A, self.innermost_interface_A): 2.0,
                       (self.state_B, self.innermost_interface_B): 2.0}
-        self.flux_method.combine_results(my_result, bad_result)
+        with pytest.raises(RuntimeError):
+            self.flux_method.combine_results(my_result, bad_result)
 
 
 class TestMinusMoveFlux(TISAnalysisTester):
-    def setup(self):
-        super(TestMinusMoveFlux, self).setup()
+    def setup_method(self):
+        super(TestMinusMoveFlux, self).setup_method()
 
         a = 0.1  # just a number to simplify the trajectory-making
         minus_move_descriptions = [
@@ -345,8 +334,8 @@ def _make_fake_minus_steps(self, scheme, descriptions):
         minus_ensemble_to_mover = {m.minus_ensemble: m
                                    for m in scheme.movers['minus']}
 
-        assert_equal(set(minus_ensemble_to_mover.keys()),
-                     set(network.minus_ensembles))
+        assert (set(minus_ensemble_to_mover.keys())
+                == set(network.minus_ensembles))
         steps = []
         mccycle = 0
         for minus_traj in descriptions:
@@ -354,7 +343,7 @@ def _make_fake_minus_steps(self, scheme, descriptions):
                 replica = -1 - i
                 adjustment = state_adjustment[minus_ensemble.state_vol]
                 traj = make_1d_traj([adjustment(s) for s in minus_traj])
-                assert_equal(minus_ensemble(traj), True)
+                assert minus_ensemble(traj)
                 samp = paths.Sample(trajectory=traj,
                                     ensemble=minus_ensemble,
                                     replica=replica)
@@ -373,14 +362,14 @@ def _make_fake_minus_steps(self, scheme, descriptions):
                                           change=change))
 
                 mccycle += 1
-        assert_equal(len(steps), 4)
+        assert len(steps) == 4
         return steps
 
     def test_get_minus_steps(self):
         all_mistis_steps = self.mistis_steps + self.mistis_minus_steps
         mistis_minus_steps = \
             self.mistis_minus_flux._get_minus_steps(all_mistis_steps)
-        assert_equal(len(mistis_minus_steps), len(self.mistis_minus_steps))
+        assert len(mistis_minus_steps) == len(self.mistis_minus_steps)
         assert_items_equal(mistis_minus_steps, self.mistis_minus_steps)
         # this could be repeated for MSTIS, but why?
 
@@ -399,7 +388,6 @@ def test_calculate(self):
         for flux in mstis_flux.values():  # all values are the same
             assert_almost_equal(flux, expected_flux)
 
-    @raises(ValueError)
     def test_bad_network(self):
         # raises error if more than one transition shares a minus ensemble
         # (flux cannot be calculated with multiple interface set minus move)
@@ -416,7 +404,8 @@ def test_bad_network(self):
         ])
         scheme = paths.DefaultScheme(bad_mistis)
         scheme.build_move_decision_tree()
-        MinusMoveFlux(scheme)
+        with pytest.raises(ValueError):
+            MinusMoveFlux(scheme)
 
 
 class TestPathLengthHistogrammer(TISAnalysisTester):
@@ -433,15 +422,15 @@ def _check_network_results(self, network, hists):
                                                               self.state_A)
         for (key, dct) in results.items():
             hist_dct_AB = hists[ensembles_AB[key]]._histogram
-            assert_equal(dict(hist_dct_AB), dct)
+            assert dict(hist_dct_AB) == dct
             hist_dct_BA = hists[ensembles_BA[key]]._histogram
-            assert_equal(dict(hist_dct_BA), dct)
+            assert dict(hist_dct_BA) == dct
 
     def test_calculate(self):
         default_histogrammer = \
                 PathLengthHistogrammer(self.mistis.sampling_ensembles)
-        assert_equal(default_histogrammer.hist_parameters,
-                     {'bin_width': 5, 'bin_range': (0, 1000)})
+        assert (default_histogrammer.hist_parameters
+                == {'bin_width': 5, 'bin_range': (0, 1000)})
 
         mistis_histogrammer = PathLengthHistogrammer(
             ensembles=self.mistis.sampling_ensembles,
@@ -503,7 +492,6 @@ def test_calculate(self):
         mstis_BA_hists = mstis_BA_histogrammer.calculate(self.mstis_steps)
         self._check_transition_results(mstis_BA, mstis_BA_hists)
 
-    @raises(RuntimeError)
     def test_calculate_no_max_lambda(self):
         mistis_AB = self.mistis.transitions[(self.state_A, self.state_B)]
         modified_transition = paths.TISTransition(
@@ -512,10 +500,11 @@ def test_calculate_no_max_lambda(self):
             interfaces=mistis_AB.interfaces.volumes,
             orderparameter=mistis_AB.orderparameter
         )
-        FullHistogramMaxLambdas(
-            transition=modified_transition,
-            hist_parameters={'bin_width': 0.1, 'bin_range': (-0.1, 1.1)}
-        )
+        with pytest.raises(RuntimeError):
+            FullHistogramMaxLambdas(
+                transition=modified_transition,
+                hist_parameters={'bin_width': 0.1, 'bin_range': (-0.1, 1.1)}
+            )
 
 
 class TestConditionalTransitionProbability(TISAnalysisTester):
@@ -532,17 +521,17 @@ def _check_network_results(self, network, ctp_results):
             dct_AB = ctp_results[ensembles_AB[ens_num]]
             result = results[ens_num]
             if result != 0.0:
-                assert_equal(dct_AB[self.state_B], result)
+                assert dct_AB[self.state_B] == result
             if result != 1.0:
-                assert_equal(dct_AB[self.state_A], 1.0-result)
+                assert dct_AB[self.state_A] == 1.0 - result
 
         for ens_num in range(len(ensembles_BA)):
             dct_BA = ctp_results[ensembles_BA[ens_num]]
             result = results[ens_num]
             if result != 0.0:
-                assert_equal(dct_BA[self.state_A], result)
+                assert dct_BA[self.state_A] == result
             if result != 1.0:
-                assert_equal(dct_BA[self.state_B], 1.0-result)
+                assert dct_BA[self.state_B] == 1.0 - result
 
     def test_calculate(self):
         mistis_ctp_calc = ConditionalTransitionProbability(
@@ -674,8 +663,8 @@ def test_missing_ctp(self):
 
 
 class TestTransitionDictResults(TISAnalysisTester):
-    def setup(self):
-        super(TestTransitionDictResults, self).setup()
+    def setup_method(self):
+        super(TestTransitionDictResults, self).setup_method()
         results_dict = {(self.state_A, self.state_B): 1,
                         (self.state_B, self.state_A): 2}
         self.mistis_transition_dict = TransitionDictResults(
@@ -690,46 +679,38 @@ def setup(self):
         )
 
     def test_iter(self):
-        assert_equal(set(pair for pair in self.mistis_transition_dict),
-                     set(pair for pair in self.mstis_transition_dict))
+        assert (set(pair for pair in self.mistis_transition_dict)
+                == set(pair for pair in self.mstis_transition_dict))
 
     def test_get_by_pair(self):
-        assert_equal(
-            self.mstis_transition_dict[(self.state_A, self.state_B)], 1
-        )
-        assert_equal(
-            self.mstis_transition_dict[(self.state_A, self.state_B)],
-            self.mistis_transition_dict[(self.state_A, self.state_B)]
-        )
-        assert_equal(
-            self.mstis_transition_dict[(self.state_B, self.state_A)], 2
-        )
-        assert_equal(
-            self.mstis_transition_dict[(self.state_B, self.state_A)],
-            self.mistis_transition_dict[(self.state_B, self.state_A)]
-        )
+        assert self.mstis_transition_dict[(self.state_A, self.state_B)] == 1
+        assert (self.mstis_transition_dict[(self.state_A, self.state_B)]
+                == self.mistis_transition_dict[(self.state_A, self.state_B)])
+        assert self.mstis_transition_dict[(self.state_B, self.state_A)] == 2
+        assert (self.mstis_transition_dict[(self.state_B, self.state_A)]
+                == self.mistis_transition_dict[(self.state_B, self.state_A)])
 
-    @raises(KeyError)
     def test_get_bad_pair(self):
-        self.mistis_transition_dict[(self.state_A, self.state_A)]
+        with pytest.raises(KeyError):
+            self.mistis_transition_dict[(self.state_A, self.state_A)]
 
     def test_get_by_transition(self):
         mistis_AB = self.mistis.transitions[(self.state_A, self.state_B)]
         mstis_AB = self.mstis.transitions[(self.state_A, self.state_B)]
-        assert_equal(self.mistis_transition_dict[mistis_AB], 1)
-        assert_equal(self.mistis_transition_dict[mistis_AB],
-                     self.mstis_transition_dict[mstis_AB])
+        assert self.mistis_transition_dict[mistis_AB] == 1
+        assert (self.mistis_transition_dict[mistis_AB]
+                == self.mstis_transition_dict[mstis_AB])
 
     def test_get_by_sampling_transition(self):
         from_A = self.mstis.from_state[self.state_A]
         from_B = self.mstis.from_state[self.state_B]
-        assert_equal(self.mstis_transition_dict[from_A], 1)
-        assert_equal(self.mstis_transition_dict[from_B], 2)
+        assert self.mstis_transition_dict[from_A] == 1
+        assert self.mstis_transition_dict[from_B] == 2
 
-    @raises(KeyError)
     def test_bad_get_sampling_transition(self):
         sampling_trans = self.mistis.sampling_transitions[0]
-        self.mistis_transition_dict[sampling_trans]
+        with pytest.raises(KeyError):
+            self.mistis_transition_dict[sampling_trans]
 
     def test_to_pandas(self):
         result = [[float("nan"), 1], [2, float("nan")]]
@@ -775,8 +756,8 @@ def _make_tis_analysis(self, network):
         )
         return tis_analysis
 
-    def setup(self):
-        super(TestTISAnalysis, self).setup()
+    def setup_method(self):
+        super(TestTISAnalysis, self).setup_method()
         self.mistis_analysis = self._make_tis_analysis(self.mistis)
         self.mistis_analysis.calculate(self.mistis_steps)
         self.mstis_analysis = self._make_tis_analysis(self.mstis)
@@ -785,31 +766,29 @@ def setup(self):
     def test_bad_access_cached_results(self):
         no_results = self._make_tis_analysis(self.mistis)
         _ = self.mistis_analysis._access_cached_result('rate')
-        # use a try/except here instead of @raises so that we also test that
-        # the calculated version (previous line) works as expected
-        try:
+        with pytest.raises(AttributeError):
             no_results._access_cached_result('rate')
-        except AttributeError:
-            pass  # this is the expected test result
 
     def test_flux_matrix(self):
-        assert_equal(self.mistis_analysis.flux_matrix,
-                     {(t.stateA, t.interfaces[0]): 0.1
-                      for t in self.mistis.sampling_transitions})
-        assert_equal(self.mstis_analysis.flux_matrix,
-                     {(t.stateA, t.interfaces[0]): 0.1
-                      for t in self.mstis.sampling_transitions})
+        assert self.mistis_analysis.flux_matrix== {
+            (t.stateA, t.interfaces[0]): 0.1
+            for t in self.mistis.sampling_transitions
+        }
+        assert self.mstis_analysis.flux_matrix == {
+            (t.stateA, t.interfaces[0]): 0.1
+            for t in self.mstis.sampling_transitions
+        }
 
     def test_flux(self):
         for transition in self.mistis.sampling_transitions:
             state = transition.stateA
             innermost = transition.interfaces[0]
-            assert_equal(self.mistis_analysis.flux(state, innermost), 0.1)
+            assert self.mistis_analysis.flux(state, innermost) == 0.1
 
         for transition in self.mstis.sampling_transitions:
             state = transition.stateA
             innermost = transition.interfaces[0]
-            assert_equal(self.mstis_analysis.flux(state, innermost), 0.1)
+            assert self.mstis_analysis.flux(state, innermost) == 0.1
 
     def test_flux_through_state(self):
         flux_dict = {(t.stateA, t.interfaces[0]): 0.1
@@ -838,21 +817,21 @@ def test_flux_through_state(self):
         )
         tis.calculate(self.mistis_steps)
         trans_AB = self.mistis.transitions[(self.state_A, self.state_B)]
-        assert_equal(tis.flux(self.state_A, trans_AB.interfaces[0]), 0.1)
-        assert_equal(tis.flux(self.state_A), 0.5)
+        assert tis.flux(self.state_A, trans_AB.interfaces[0]) == 0.1
+        assert tis.flux(self.state_A) == 0.5
 
     def test_state_fluxes(self):
         for transition in self.mistis.sampling_transitions:
             state = transition.stateA
             innermost = transition.interfaces[0]
-            assert_equal(self.mistis_analysis.state_fluxes(state),
-                         {(state, innermost): 0.1})
+            assert (self.mistis_analysis.state_fluxes(state)
+                    == {(state, innermost): 0.1})
 
         for transition in self.mstis.sampling_transitions:
             state = transition.stateA
             innermost = transition.interfaces[0]
-            assert_equal(self.mstis_analysis.state_fluxes(state),
-                         {(state, innermost): 0.1})
+            assert (self.mstis_analysis.state_fluxes(state)
+                    == {(state, innermost): 0.1})
 
     def test_transition_probability_matrix(self):
         pairs = [(self.state_A, self.state_B), (self.state_B, self.state_A)]
@@ -951,23 +930,23 @@ def test_conditional_transition_probability(self):
                                        index=mistis_interfaces,
                                        columns=states)
         mistis_ctp = self.mistis_analysis.conditional_transition_probability
-        assert_equal(set(states), set(mistis_ctp.columns))
-        assert_equal(set(mistis_interfaces), set(mistis_ctp.index))
+        assert set(states) == set(mistis_ctp.columns)
+        assert set(mistis_interfaces) == set(mistis_ctp.index)
         for iface in mistis_interfaces:
             for state in states:
-                assert_equal(expected_mistis.loc[(iface, state)],
-                             mistis_ctp.loc[(iface, state)])
+                assert (expected_mistis.loc[(iface, state)]
+                        == mistis_ctp.loc[(iface, state)])
 
         expected_mstis = pd.DataFrame(data=expected_data,
                                       index=mstis_interfaces,
                                       columns=states)
         mstis_ctp = self.mstis_analysis.conditional_transition_probability
-        assert_equal(set(states), set(mstis_ctp.columns))
-        assert_equal(set(mstis_interfaces), set(mstis_ctp.index))
+        assert set(states) == set(mstis_ctp.columns)
+        assert set(mstis_interfaces) == set(mstis_ctp.index)
         for iface in mstis_interfaces:
             for state in states:
-                assert_equal(expected_mstis.loc[(iface, state)],
-                             mstis_ctp.loc[(iface, state)])
+                assert (expected_mstis.loc[(iface, state)]
+                        == mstis_ctp.loc[(iface, state)])
 
     def test_total_crossing_probability(self):
         results = {0.0: 1.0, 0.1: 0.5, 0.2: 0.25, 0.3: 0.125,
@@ -985,24 +964,24 @@ def test_total_crossing_probability(self):
             for x in results:
                 assert_almost_equal(results[x], tcp(x))
 
-    @raises(TypeError)
     def test_bad_no_flux(self):
         network = self.mistis
-        StandardTISAnalysis(
-            network=network,
-            max_lambda_calcs={t: {'bin_width': 0.1,
-                                  'bin_range': (-0.1, 1.1)}
-                              for t in network.sampling_transitions}
-        )
+        with pytest.raises(TypeError):
+            StandardTISAnalysis(
+                network=network,
+                max_lambda_calcs={t: {'bin_width': 0.1,
+                                      'bin_range': (-0.1, 1.1)}
+                                  for t in network.sampling_transitions}
+            )
 
-    @raises(RuntimeError)
     def test_bad_max_lambda_calcs(self):
         network = self.mistis
-        StandardTISAnalysis(
-            network=network,
-            flux_method=DictFlux({(t.stateA, t.interfaces[0]): 0.1
-                                  for t in network.sampling_transitions})
-        )
+        with pytest.raises(RuntimeError):
+            StandardTISAnalysis(
+                network=network,
+                flux_method=DictFlux({(t.stateA, t.interfaces[0]): 0.1
+                                      for t in network.sampling_transitions})
+            )
 
     def test_init_ensemble_histogrammer_max_lambda(self):
         network = self.mistis
@@ -1052,7 +1031,7 @@ def test_with_minus_move_flux(self):
 
             for seq in [seq_1, seq_2]:
                 traj = make_1d_traj(seq)
-                assert_equal(minus_ens(traj), True)
+                assert minus_ens(traj)
                 samp = paths.Sample(trajectory=traj,
                                     ensemble=minus_ens,
                                     replica=replica[state])
diff --git a/openpathsampling/tests/test_tools.py b/openpathsampling/tests/test_tools.py
index 8932b474e..b39e77aab 100644
--- a/openpathsampling/tests/test_tools.py
+++ b/openpathsampling/tests/test_tools.py
@@ -1,5 +1,4 @@
 import pytest
-from nose.tools import assert_equal
 from openpathsampling.tools import *
 
 import tempfile
@@ -15,42 +14,35 @@
 
 def test_pretty_print_seconds():
     # test the basics with full reporting
-    assert_equal(pretty_print_seconds(93784),
-                 "1 day 2 hours 3 minutes 4 seconds")
-    assert_equal(pretty_print_seconds(179990),
-                 "2 days 1 hour 59 minutes 50 seconds")
-    assert_equal(pretty_print_seconds(31),
-                 "31 seconds")
+    assert pretty_print_seconds(93784) == "1 day 2 hours 3 minutes 4 seconds"
+    assert (pretty_print_seconds(179990)
+            == "2 days 1 hour 59 minutes 50 seconds")
+    assert pretty_print_seconds(31) == "31 seconds"
 
     # test positive n_labels (including testing rounding)
-    assert_equal(pretty_print_seconds(93784, n_labels=2),
-                 "1 day 2 hours")
-    assert_equal(pretty_print_seconds(179990, n_labels=2),
-                 "2 days 2 hours")
-    assert_equal(pretty_print_seconds(179990, n_labels=3),
-                 "2 days 2 hours 0 minutes")
-    assert_equal(pretty_print_seconds(31, n_labels=2),
-                 "31 seconds")
+    assert pretty_print_seconds(93784, n_labels=2) == "1 day 2 hours"
+    assert pretty_print_seconds(179990, n_labels=2) == "2 days 2 hours"
+    assert (pretty_print_seconds(179990, n_labels=3)
+            == "2 days 2 hours 0 minutes")
+    assert pretty_print_seconds(31, n_labels=2) == "31 seconds"
 
     # test negative n_labels
-    assert_equal(pretty_print_seconds(93784, n_labels=-1),
-                 "1.09 days")
-    assert_equal(pretty_print_seconds(93784, n_labels=-2),
-                 "1 day 2.05 hours")
-    assert_equal(pretty_print_seconds(31, n_labels=-2),
-                 "31 seconds")
+    assert pretty_print_seconds(93784, n_labels=-1) == "1.09 days"
+    assert pretty_print_seconds(93784, n_labels=-2) == "1 day 2.05 hours"
+    assert pretty_print_seconds(31, n_labels=-2) == "31 seconds"
 
     # test separators
-    assert_equal(pretty_print_seconds(93784, separator=", "),
-                 "1 day, 2 hours, 3 minutes, 4 seconds")
+    assert (pretty_print_seconds(93784, separator=", ")
+            == "1 day, 2 hours, 3 minutes, 4 seconds")
 
 def test_progress_string():
-    assert_equal(progress_string(0, 100, 10),
-                 "Starting simulation...\nWorking on first step\n")
-    assert_equal(progress_string(1, 11, 9378.4),
-                 "Running for 2 hours 36 minutes 18 seconds - "
-                 + "9378.40 seconds per step\n"
-                 + "Estimated time remaining: 1 day 2.05 hours\n")
+    assert (progress_string(0, 100, 10)
+            == "Starting simulation...\nWorking on first step\n")
+    assert progress_string(1, 11, 9378.4) == (
+        "Running for 2 hours 36 minutes 18 seconds - "
+        + "9378.40 seconds per step\n"
+        + "Estimated time remaining: 1 day 2.05 hours\n"
+    )
 
 
 def test_ensure_file_dne():
diff --git a/openpathsampling/tests/test_toy_dynamics.py b/openpathsampling/tests/test_toy_dynamics.py
index 41fa16c47..5eab1fbf5 100644
--- a/openpathsampling/tests/test_toy_dynamics.py
+++ b/openpathsampling/tests/test_toy_dynamics.py
@@ -9,9 +9,8 @@
 from builtins import object
 import os
 
-from nose.tools import (assert_equal, assert_not_equal, assert_almost_equal)
-
-from nose.plugins.skip import SkipTest
+import pytest
+from numpy.testing import assert_almost_equal
 
 import openpathsampling as paths
 import openpathsampling.engines.toy as toy
@@ -43,7 +42,7 @@ def setup_module():
 # === TESTS FOR TOY POTENTIAL ENERGY SURFACES =============================
 
 class TestHarmonicOscillator(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -65,7 +64,7 @@ def test_dVdx(self):
 
 
 class TestGaussian(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -82,7 +81,7 @@ def test_dVdx(self):
 
 
 class TestOuterWalls(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -99,7 +98,7 @@ def test_dVdx(self):
 
 
 class TestLinearSlope(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -108,11 +107,11 @@ def test_V(self):
         assert_almost_equal(linear.V(self), 2.0375)
 
     def test_dVdx(self):
-        assert_equal(linear.dVdx(self), [1.5, 0.75])
+        assert linear.dVdx(self) == [1.5, 0.75]
 
 
 class TestDoubleWell(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -133,7 +132,7 @@ def test_serialization_cycle(self):
 
 
 class TestCombinations(object):
-    def setup(self):
+    def setup_method(self):
         self.positions = init_pos
         self.velocities = init_vel
         self.mass = sys_mass
@@ -142,7 +141,7 @@ def setup(self):
 
     def test_V(self):
         assert_almost_equal(self.simpletest.V(self), 2*2.37918851445)
-        assert_almost_equal(self.fullertest.V(self), 
+        assert_almost_equal(self.fullertest.V(self),
                             2.37918851445 + 1.080382 - 2.0375)
 
     def test_dVdx(self):
@@ -161,7 +160,7 @@ def test_kinetic_energy(self):
 
 class Test_convert_fcn(object):
     def test_convert_to_3Ndim(self):
-        raise SkipTest
+        pytest.skip()
 
         assert_equal_array_array(toy.convert_to_3Ndim([1.0, 2.0]),
                                  np.array([[1.0, 2.0, 0.0]]))
@@ -171,7 +170,7 @@ def test_convert_to_3Ndim(self):
                                  np.array([[1.0, 2.0, 3.0], [4.0, 0.0, 0.0]]))
 
 class TestToyEngine(object):
-    def setup(self):
+    def setup_method(self):
         pes = linear
         integ = toy.LeapfrogVerletIntegrator(dt=0.002)
         topology = toy.Topology(
@@ -196,17 +195,17 @@ def setup(self):
         sim.n_steps_per_frame = 10
         self.sim = sim
 
-    def teardown(self):
+    def teardown_method(self):
         if os.path.isfile('toy_tmp.nc'):
             os.remove('toy_tmp.nc')
 
     def test_sanity(self):
         assert_items_equal(self.sim._mass, sys_mass)
         assert_items_equal(self.sim._minv, [old_div(1.0,m_i) for m_i in sys_mass])
-        assert_equal(self.sim.n_steps_per_frame, 10)
+        assert self.sim.n_steps_per_frame == 10
 
     def test_snapshot_timestep(self):
-        assert_equal(self.sim.snapshot_timestep, 0.02)
+        assert self.sim.snapshot_timestep == 0.02
 
     def test_snapshot_get(self):
         snapshot = self.sim.current_snapshot
@@ -239,7 +238,7 @@ def test_generate(self):
             traj = self.sim.generate(self.sim.current_snapshot, [true_func])
         except paths.engines.EngineMaxLengthError as e:
             traj = e.last_trajectory
-            assert_equal(len(traj), self.sim.n_frames_max)
+            assert len(traj) == self.sim.n_frames_max
         else:
             raise RuntimeError('Did not raise MaxLength Error')
 
@@ -250,13 +249,13 @@ def test_generate_n_frames(self):
         traj1 = self.sim.generate(self.sim.current_snapshot, [ens.can_append])
         self.sim.current_snapshot = orig
         traj2 = [orig] + self.sim.generate_n_frames(3)
-        assert_equal(len(traj1), len(traj2))
+        assert len(traj1) == len(traj2)
         for (s1, s2) in zip(traj1, traj2):
             # snapshots are not the same object
-            assert_not_equal(s1, s2) 
+            assert s1 != s2
             # however, they have the same values stored in them
-            assert_equal(len(s1.coordinates), 1)
-            assert_equal(len(s1.coordinates[0]), 2)
+            assert len(s1.coordinates) == 1
+            assert len(s1.coordinates[0]) == 2
             assert_items_equal(s1.coordinates[0], s2.coordinates[0])
             assert_items_equal(s1.velocities[0], s2.velocities[0])
 
@@ -273,7 +272,7 @@ def test_has_constraints(self):
 # === TESTS FOR TOY INTEGRATORS ===========================================
 
 class TestLeapfrogVerletIntegrator(object):
-    def setup(self):
+    def setup_method(self):
         pes = linear
         integ = toy.LeapfrogVerletIntegrator(dt=0.002)
         topology = toy.Topology(
@@ -305,8 +304,8 @@ def test_momentum_update(self):
         # velocities = init_vel - pes.dVdx(init_pos)/m*dt
         #            = [0.6, 0.5] - [1.5, 0.75]/[1.5, 1.5] * 0.002
         #            = [0.598, 0.499]
-        assert_equal(self.sim.velocities[0], 0.598)
-        assert_equal(self.sim.velocities[1], 0.499)
+        assert self.sim.velocities[0] == 0.598
+        assert self.sim.velocities[1] == 0.499
 
     def test_position_update(self):
         self.sim.integ._position_update(self.sim, 0.002)
@@ -326,7 +325,7 @@ class TestLangevinBAOABIntegrator(object):
     '''Testing for correctness is hard, since this is a stochastic
     calculation. However, we can at least run tests to make sure nothing
     crashes.'''
-    def setup(self):
+    def setup_method(self):
         pes = linear
         integ = toy.LangevinBAOABIntegrator(dt=0.002, temperature=0.5,
                                             gamma=1.0)
@@ -357,14 +356,14 @@ def setup(self):
     def test_OU_update(self):
         # we can't actually test for correct values, but we *can* test that
         # some things *don't* happen
-        assert_equal(self.sim.velocities[0], init_vel[0])
-        assert_equal(self.sim.velocities[1], init_vel[1])
+        assert self.sim.velocities[0] == init_vel[0]
+        assert self.sim.velocities[1] == init_vel[1]
         self.sim.integ._OU_update(self.sim, 0.002)
-        assert_not_equal(self.sim.velocities[0], init_vel[0])
-        assert_not_equal(self.sim.velocities[1], init_vel[1])
+        assert self.sim.velocities[0] != init_vel[0]
+        assert self.sim.velocities[1] != init_vel[1]
         # tests that the same random number wasn't used for both:
-        assert_not_equal(self.sim.velocities[0] - init_vel[0],
-                         self.sim.velocities[1] - init_vel[1])
+        assert (self.sim.velocities[0] - init_vel[0]
+                != self.sim.velocities[1] - init_vel[1])
 
     def test_step(self):
         self.sim.generate_next_frame()
@@ -373,7 +372,7 @@ def test_step(self):
 class TestOverdampedLangevinIntegrator(object):
     '''This is only a test if the integrator runs. Because of its stochastic
     nature we can not actually test its correctness easily.'''
-    def setup(self):
+    def setup_method(self):
         pes = linear
         integ = toy.OverdampedLangevinIntegrator(dt=0.001, temperature=4.0,
                                                  D=1.0)
diff --git a/openpathsampling/tests/test_toy_features.py b/openpathsampling/tests/test_toy_features.py
index c9ca1ae47..2e8f1afb5 100644
--- a/openpathsampling/tests/test_toy_features.py
+++ b/openpathsampling/tests/test_toy_features.py
@@ -2,8 +2,7 @@
 import openpathsampling.engines.toy as toys
 import numpy as np
 
-from nose.tools import assert_equal, assert_almost_equal
-from nose.plugins.skip import SkipTest
+from numpy.testing import assert_almost_equal
 import logging
 
 logging.getLogger('openpathsampling.initialization').setLevel(logging.CRITICAL)
@@ -12,7 +11,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestToySnapshotFeatures(object):
-    def setup(self):
+    def setup_method(self):
         integ = toys.LangevinBAOABIntegrator(dt=0.002,
                                              temperature=1.0,
                                              gamma=2.5)
@@ -37,8 +36,8 @@ def setup(self):
                                      engine=engine_6D)
 
     def test_n_degrees_of_freedom(self):
-        assert_equal(self.snap_2D.n_degrees_of_freedom, 2)
-        assert_equal(self.snap_6D.n_degrees_of_freedom, 6)
+        assert self.snap_2D.n_degrees_of_freedom == 2
+        assert self.snap_6D.n_degrees_of_freedom == 6
 
     def test_instantaneous_temperature(self):
         # KE = 0.5 * (1.0*2.0**2 + 1.0*4.0**2) = 10.0
diff --git a/openpathsampling/tests/test_trajectory.py b/openpathsampling/tests/test_trajectory.py
index 73c69a1aa..86f7f9b63 100644
--- a/openpathsampling/tests/test_trajectory.py
+++ b/openpathsampling/tests/test_trajectory.py
@@ -2,14 +2,9 @@
 from builtins import object
 import logging
 
-from nose.tools import (
-    assert_equal, assert_not_equal, raises
-)
-from nose.plugins.skip import SkipTest
 from .test_helpers import (CallIdentity, prepend_exception_message,
                            make_1d_traj, assert_items_equal)
 
-
 import openpathsampling as paths
 from .test_helpers import make_1d_traj
 
@@ -18,7 +13,7 @@
 logging.getLogger('openpathsampling.storage').setLevel(logging.CRITICAL)
 
 class TestSummarizeTrajectoryVolumes(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         vol2 = paths.CVDefinedVolume(op, -0.1, 0.7)
@@ -55,7 +50,7 @@ def test_summarize_trajectory_volumes_str(self):
             "A-B-I-X"
         )
         assert_items_equal(
-            self._make_traj("abix").summarize_by_volumes_str(voldict, " blah "), 
+            self._make_traj("abix").summarize_by_volumes_str(voldict, " blah "),
             "A blah B blah I blah X"
         )
 
@@ -84,7 +79,7 @@ def test_summarize_trajectory_volumes_with_nonevol(self):
         )
 
 class TestSubtrajectoryIndices(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         vol2 = paths.CVDefinedVolume(op, -0.1, 0.7)
@@ -119,22 +114,22 @@ def test_subtrajectory_indices(self):
         subtrajectoriesABA = ensemble_ABA.split(trajectory)
 
         # make sure we have the trajectories we expect
-        assert_equal(len(subtrajectoriesA), 3)
-        assert_equal(len(subtrajectoriesB), 2)
-        assert_equal(len(subtrajectoriesABA), 1)
+        assert len(subtrajectoriesA) == 3
+        assert len(subtrajectoriesB) == 2
+        assert len(subtrajectoriesABA) == 1
         # the following assertions check that the subtrajectories are the
         # ones that we expect; the numbers here are linked to the indices
         # we'll test next
-        assert_equal(subtrajectoriesA[0], trajectory[0:2])
-        assert_equal(subtrajectoriesA[1], trajectory[3:4])
-        assert_equal(subtrajectoriesA[2], trajectory[11:13])
-        assert_equal(subtrajectoriesB[0], trajectory[5:7])
-        assert_equal(subtrajectoriesB[1], trajectory[8:9])
-        assert_equal(subtrajectoriesABA[0], trajectory[3:12])
+        assert subtrajectoriesA[0] == trajectory[0:2]
+        assert subtrajectoriesA[1] == trajectory[3:4]
+        assert subtrajectoriesA[2] == trajectory[11:13]
+        assert subtrajectoriesB[0] == trajectory[5:7]
+        assert subtrajectoriesB[1] == trajectory[8:9]
+        assert subtrajectoriesABA[0] == trajectory[3:12]
         # now we run the subtrajectory_indices function and test it
         indicesA = trajectory.subtrajectory_indices(subtrajectoriesA)
         indicesB = trajectory.subtrajectory_indices(subtrajectoriesB)
         indicesABA = trajectory.subtrajectory_indices(subtrajectoriesABA)
-        assert_equal(indicesA, [[0, 1], [3], [11, 12]])
-        assert_equal(indicesB, [[5, 6], [8]])
-        assert_equal(indicesABA, [[3, 4, 5, 6, 7, 8, 9, 10, 11]])
+        assert indicesA == [[0, 1], [3], [11, 12]]
+        assert indicesB == [[5, 6], [8]]
+        assert indicesABA == [[3, 4, 5, 6, 7, 8, 9, 10, 11]]
diff --git a/openpathsampling/tests/test_trajectory_transition_analysis.py b/openpathsampling/tests/test_trajectory_transition_analysis.py
index f542af624..ebaf82a4d 100644
--- a/openpathsampling/tests/test_trajectory_transition_analysis.py
+++ b/openpathsampling/tests/test_trajectory_transition_analysis.py
@@ -3,9 +3,9 @@
 from builtins import zip
 from past.utils import old_div
 from builtins import object
-from nose.tools import (assert_equal, assert_not_equal, raises,
-                        assert_almost_equal)
-from nose.plugins.skip import SkipTest
+
+import pytest
+from numpy.testing import assert_almost_equal
 from .test_helpers import make_1d_traj
 
 import openpathsampling as paths
@@ -20,7 +20,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestTrajectorySegmentContainer(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         self.vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         self.vol3 = paths.CVDefinedVolume(op, 2.0, 2.5)
@@ -36,20 +36,20 @@ def setup(self):
                                                           dt=0.5)
 
     def test_list_behavior(self):
-        assert_equal(len(self.container), 3)
-        assert_equal(self.container[0], self.segments[0])
+        assert len(self.container) == 3
+        assert self.container[0] == self.segments[0]
         for (truth, beauty) in zip(self.container, self.segments):
-            assert_equal(truth, beauty)
-        assert_equal(self.segments[0] in self.container, True)
+            assert truth == beauty
+        assert self.segments[0] in self.container
 
-    @raises(TypeError)
     def test_segments_setitem_fails(self):
-        self.container[0] = self.trajectory
+        with pytest.raises(TypeError):
+            self.container[0] = self.trajectory
 
     def test_segments(self):
-        assert_equal(self.container[0], self.trajectory[0:2])
-        assert_equal(self.container[1], self.trajectory[6:8])
-        assert_equal(self.container[2], self.trajectory[9:12])
+        assert self.container[0] == self.trajectory[0:2]
+        assert self.container[1] == self.trajectory[6:8]
+        assert self.container[2] == self.trajectory[9:12]
 
     def test_from_trajectory_and_indices(self):
         container = \
@@ -58,39 +58,39 @@ def test_from_trajectory_and_indices(self):
                 indices=[(0, 2), (6, 8), (9, 12)],
                 dt=0.5
             )
-        assert_equal(container[0], self.trajectory[0:2])
-        assert_equal(container[1], self.trajectory[6:8])
-        assert_equal(container[2], self.trajectory[9:12])
+        assert container[0] == self.trajectory[0:2]
+        assert container[1] == self.trajectory[6:8]
+        assert container[2] == self.trajectory[9:12]
 
     def test_n_frames(self):
-        assert_equal(self.container.n_frames.tolist(), [2, 2, 3])
+        assert self.container.n_frames.tolist() == [2, 2, 3]
 
     def test_times(self):
-        assert_equal(self.container.times.tolist(), [1.0, 1.0, 1.5])
+        assert self.container.times.tolist() == [1.0, 1.0, 1.5]
 
-    @raises(RuntimeError)
     def test_times_without_dt(self):
         bad_container = paths.TrajectorySegmentContainer(self.segments)
-        bad_container.times
+        with pytest.raises(RuntimeError):
+            bad_container.times
 
     def test_add(self):
         ens_B = paths.AllInXEnsemble(self.vol3)
         segs_B = ens_B.split(self.trajectory)
         container_B = paths.TrajectorySegmentContainer(segs_B, dt=0.5)
-        assert_equal(len(container_B), 2)
+        assert len(container_B) == 2
         test_container = self.container + container_B
-        assert_equal(len(test_container), 5)
+        assert len(test_container) == 5
         for seg in self.container:
-            assert_equal(seg in test_container, True)
+            assert seg in test_container
         for seg in container_B:
-            assert_equal(seg in test_container, True)
+            assert seg in test_container
 
-    @raises(RuntimeError)
     def test_add_different_dt(self):
         ens_B = paths.AllInXEnsemble(self.vol3)
         segs_B = ens_B.split(self.trajectory)
         container_B = paths.TrajectorySegmentContainer(segs_B)
-        test_container = self.container + container_B
+        with pytest.raises(RuntimeError):
+            test_container = self.container + container_B
 
     def test_iadd(self):
         ens_B = paths.AllInXEnsemble(self.vol3)
@@ -98,12 +98,12 @@ def test_iadd(self):
         container_B = paths.TrajectorySegmentContainer(segs_B)
         container_B_id = id(container_B)
         container_B += self.container
-        assert_equal(len(container_B), 5)
-        assert_equal(container_B_id, id(container_B))
+        assert len(container_B) == 5
+        assert container_B_id == id(container_B)
 
 
 class TestTrajectoryTransitionAnalysis(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         vol2 = paths.CVDefinedVolume(op, -0.1, 0.7)
@@ -139,20 +139,20 @@ def _make_traj(self, traj_str):
                             velocities=[1.0]*len(sequence))
 
     def test_analyze_continuous_time(self):
-        resultA = self.analyzer.analyze_continuous_time(self.trajectory, 
+        resultA = self.analyzer.analyze_continuous_time(self.trajectory,
                                                         self.stateA)
-        assert_equal(resultA.n_frames.tolist(), [3, 1, 1, 1, 1, 1, 1])
+        assert resultA.n_frames.tolist() == [3, 1, 1, 1, 1, 1, 1]
         resultB = self.analyzer.analyze_continuous_time(self.trajectory,
                                                         self.stateB)
-        assert_equal(resultB.n_frames.tolist(), [1, 1, 1, 5])
+        assert resultB.n_frames.tolist() == [1, 1, 1, 5]
         assert_almost_equal(resultA.times.mean(), 9.0/7.0*0.1)
         assert_almost_equal(resultB.times.mean(), 8.0/4.0*0.1)
 
     def test_analyze_lifetime(self):
         resA = self.analyzer.analyze_lifetime(self.trajectory, self.stateA)
         resB = self.analyzer.analyze_lifetime(self.trajectory, self.stateB)
-        assert_equal(resA.n_frames.tolist(), [3, 1, 2])  # A->B
-        assert_equal(resB.n_frames.tolist(), [2, 1, 1, 11])  # B->A
+        assert resA.n_frames.tolist() == [3, 1, 2]  # A->B
+        assert resB.n_frames.tolist() == [2, 1, 1, 11]  # B->A
         assert_almost_equal(resA.times.mean(), 6.0/3.0*0.1)
         assert_almost_equal(resB.times.mean(), 15.0/4.0*0.1)
 
@@ -163,10 +163,10 @@ def test_analyze_transition_duration(self):
         resBA = self.analyzer.analyze_transition_duration(self.trajectory,
                                                           self.stateB,
                                                           self.stateA)
-        assert_equal(resAB.n_frames.tolist(), [2, 0, 0, 1])
-        assert_equal(resBA.n_frames.tolist(), [1, 0, 0, 6])
-        assert_equal(resAB.times.mean(), 3.0/4.0*0.1)
-        assert_equal(resBA.times.mean(), 7.0/4.0*0.1)
+        assert resAB.n_frames.tolist() == [2, 0, 0, 1]
+        assert resBA.n_frames.tolist() == [1, 0, 0, 6]
+        assert resAB.times.mean() == 3.0/4.0*0.1
+        assert resBA.times.mean() == 7.0/4.0*0.1
 
     def test_analyze_flux(self):
         # A: [{out: 1, in: 1}
@@ -178,34 +178,32 @@ def test_analyze_flux(self):
         core_traj = self._make_traj(flux_core_test_str)
         flux_segs_A = self.analyzer.analyze_flux(core_traj, self.stateA)
         # flux_segs_A = self.analyzer.flux_segments[self.stateA]
-        assert_equal(len(flux_segs_A['in']), 4)
-        assert_equal(flux_segs_A['in'][0], core_traj[2:3])
-        assert_equal(flux_segs_A['in'][1], core_traj[4:7])
-        assert_equal(flux_segs_A['in'][2], core_traj[9:10])
-        assert_equal(flux_segs_A['in'][3], core_traj[13:15])
-
-        assert_equal(len(flux_segs_A['out']), 4)
-        assert_equal(flux_segs_A['out'][0], core_traj[1:2])
-        assert_equal(flux_segs_A['out'][1], core_traj[3:4])
-        assert_equal(flux_segs_A['out'][2], core_traj[7:9])
-        assert_equal(flux_segs_A['out'][3], core_traj[15:17])
+        assert len(flux_segs_A['in']) == 4
+        assert flux_segs_A['in'][0] == core_traj[2:3]
+        assert flux_segs_A['in'][1] == core_traj[4:7]
+        assert flux_segs_A['in'][2] == core_traj[9:10]
+        assert flux_segs_A['in'][3] == core_traj[13:15]
+
+        assert len(flux_segs_A['out']) == 4
+        assert flux_segs_A['out'][0] == core_traj[1:2]
+        assert flux_segs_A['out'][1] == core_traj[3:4]
+        assert flux_segs_A['out'][2] == core_traj[7:9]
+        assert flux_segs_A['out'][3] == core_traj[15:17]
 
     def test_analyze_flux_with_interface(self):
         flux_iface_traj_str = "aixixaiaxiixiaxaixbxbixiaaixiai"
         # frame numbers        0    5    0    5    0    5    0
         # in (I) or out (O)      OOOIIIOOOOOIOII       IIIOO
-        assert_equal(len(flux_iface_traj_str), 31) # check I counted correctly
+        assert len(flux_iface_traj_str) == 31 # check I counted correctly
         flux_traj = self._make_traj(flux_iface_traj_str)
         self.analyzer.reset_analysis()
         flux_segs_A = self.analyzer.analyze_flux(flux_traj, self.stateA,
                                                  self.interfaceA0)
         # flux_segs_A = self.analyzer.flux_segments[self.stateA]
-        assert_equal(flux_segs_A['in'][:],
-                     [flux_traj[5:8], flux_traj[13:14], flux_traj[15:17],
-                      flux_traj[24:27]])
-        assert_equal(flux_segs_A['out'][:],
-                     [flux_traj[2:5], flux_traj[8:13], flux_traj[14:15],
-                      flux_traj[27:29]])
+        assert flux_segs_A['in'][:] == [flux_traj[5:8], flux_traj[13:14],
+                                        flux_traj[15:17], flux_traj[24:27]]
+        assert flux_segs_A['out'][:] == [flux_traj[2:5], flux_traj[8:13],
+                                         flux_traj[14:15], flux_traj[27:29]]
 
     def test_minus_flux(self):
         flux_iface_traj_str = "axxxaaaxxxa"
@@ -213,9 +211,8 @@ def test_minus_flux(self):
         self.analyzer.reset_analysis()
         flux_segs_A = self.analyzer.analyze_flux(flux_traj, self.stateA,
                                                  self.interfaceA0)
-        assert_equal(flux_segs_A['in'][:], [flux_traj[4:7]])
-        assert_equal(flux_segs_A['out'][:],
-                     [flux_traj[1:4], flux_traj[7:10]])
+        assert flux_segs_A['in'][:] == [flux_traj[4:7]]
+        assert flux_segs_A['out'][:] == [flux_traj[1:4], flux_traj[7:10]]
 
     def test_flux(self):
         flux_iface_traj_str = "aixixaiaxiixiaxaixbxbixiaaixiai"
@@ -230,14 +227,14 @@ def test_flux(self):
         assert_almost_equal(flux, old_div(1.0, (average_out + average_in)))
         self.analyzer.dt = None
 
-    @raises(RuntimeError)
     def test_flux_no_dt(self):
         analyzer = paths.TrajectoryTransitionAnalysis(self.transition)
         flux_iface_traj_str = "aixixaiaxiixiaxaixbxbixiaaixiai"
         flux_traj = self._make_traj(flux_iface_traj_str)
-        flux = analyzer.flux(trajectories=[flux_traj],
-                             state=self.stateA,
-                             interface=self.interfaceA0)
+        with pytest.raises(RuntimeError):
+            flux = analyzer.flux(trajectories=[flux_traj],
+                                 state=self.stateA,
+                                 interface=self.interfaceA0)
 
     def test_analyze(self):
         # only test that it runs -- correctness testing in the others
@@ -251,17 +248,17 @@ def test_analyze(self):
 
         self.analyzer.reset_analysis()
         self.analyzer.analyze([self.trajectory])
-        assert_equal(cont_frames[self.stateA].tolist(),
-                     self.analyzer.continuous_frames[self.stateA].tolist())
-        assert_equal(life_frames[self.stateA].tolist(),
-                     self.analyzer.lifetime_frames[self.stateA].tolist())
+        assert (cont_frames[self.stateA].tolist()
+                == self.analyzer.continuous_frames[self.stateA].tolist())
+        assert (life_frames[self.stateA].tolist()
+                == self.analyzer.lifetime_frames[self.stateA].tolist())
         A2B = (self.stateA, self.stateB)
-        assert_equal(trans_frames[A2B].tolist(),
-                     self.analyzer.transition_duration_frames[A2B].tolist())
-        assert_equal(cont_times[self.stateA].mean(),
-                     self.analyzer.continuous_times[self.stateA].mean())
-        assert_equal(life_times[self.stateA].mean(),
-                     self.analyzer.lifetimes[self.stateA].mean())
-        assert_equal(trans_times[A2B].mean(),
-                     self.analyzer.transition_duration[A2B].mean())
+        assert (trans_frames[A2B].tolist()
+                == self.analyzer.transition_duration_frames[A2B].tolist())
+        assert (cont_times[self.stateA].mean()
+                == self.analyzer.continuous_times[self.stateA].mean())
+        assert (life_times[self.stateA].mean()
+                == self.analyzer.lifetimes[self.stateA].mean())
+        assert (trans_times[A2B].mean()
+                == self.analyzer.transition_duration[A2B].mean())
 
diff --git a/openpathsampling/tests/test_transitions.py b/openpathsampling/tests/test_transitions.py
index b4d5ffff1..5f75dcff4 100644
--- a/openpathsampling/tests/test_transitions.py
+++ b/openpathsampling/tests/test_transitions.py
@@ -1,7 +1,5 @@
 from __future__ import absolute_import
 from builtins import object
-from nose.tools import assert_equal, assert_not_equal, raises
-from nose.plugins.skip import SkipTest
 from .test_helpers import (
     CallIdentity, prepend_exception_message, make_1d_traj, data_filename,
     assert_items_equal
@@ -18,7 +16,7 @@
 logging.getLogger('openpathsampling.netcdfplus').setLevel(logging.CRITICAL)
 
 class TestTISTransition(object):
-    def setup(self):
+    def setup_method(self):
         pass
 
     def test_initialization(self):
@@ -28,7 +26,7 @@ def test_ensemble_statistics(self):
         pass
 
 class TestFixedLengthTPSTransition(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         self.stateA = paths.CVDefinedVolume(op, 0.1, 0.5)
         self.stateB = paths.CVDefinedVolume(op, 2.0, 2.5)
@@ -48,12 +46,12 @@ def _make_traj(self, string):
         return make_1d_traj(coordinates=pretraj, velocities=[1.0]*len(pretraj))
 
     def test_init(self):
-        assert_equal(len(self.transition.ensembles), 1)
-        assert_equal(self.transition.length, 4)
-        assert_equal(self.transition.ensembles[0](self.good_traj), True)
-        assert_equal(self.transition.ensembles[0](self.short_traj), False)
-        assert_equal(self.transition.ensembles[0](self.long_traj), False)
-        assert_equal(self.transition.ensembles[0](self.bad_states_traj), False)
+        assert len(self.transition.ensembles) == 1
+        assert self.transition.length == 4
+        assert self.transition.ensembles[0](self.good_traj)
+        assert not self.transition.ensembles[0](self.short_traj)
+        assert not self.transition.ensembles[0](self.long_traj)
+        assert not self.transition.ensembles[0](self.bad_states_traj)
 
     def test_storage(self):
         import os
@@ -64,18 +62,18 @@ def test_storage(self):
         storage.snapshots.save(self.good_traj[0])
 
         storage.save(self.transition)
-        assert_equal(len(storage.transitions), 1)
+        assert len(storage.transitions) == 1
         storage.sync_all()
 
         storage_r = paths.storage.AnalysisStorage(filename)
         reloaded = storage_r.transitions[0]
-        assert_equal(self.transition.ensembles[0], reloaded.ensembles[0])
+        assert self.transition.ensembles[0] == reloaded.ensembles[0]
 
         if os.path.isfile(filename):
             os.remove(filename)
 
 class TestMinusSidesSummary(object):
-    def setup(self):
+    def setup_method(self):
         op = paths.FunctionCV("Id", lambda snap : snap.coordinates[0][0])
         vol1 = paths.CVDefinedVolume(op, 0.1, 0.5)
         vol2 = paths.CVDefinedVolume(op, -0.1, 0.7)
@@ -106,38 +104,38 @@ def _make_traj(self, string):
 
     def test_normal_minus(self):
         minus = paths.MinusInterfaceEnsemble(self.stateA, self.stateA)
-        assert_equal(minus_sides_summary(self.traj_axaxa, minus),
-                     {"in" : [5], "out" : [4]})
-        assert_not_equal(minus_sides_summary(self.traj_axaxa, minus),
-                         {"in" : [5], "out" : [3]})
-        assert_equal(minus_sides_summary(self.traj_aixiaixia, minus), 
-                     {"in" : [5], "out" : [6] })
-        assert_equal(minus_sides_summary(self.traj_aixixiaxia, minus), 
-                     {"in" : [5], "out" : [10] })
+        assert (minus_sides_summary(self.traj_axaxa, minus)
+                == {"in" : [5], "out" : [4]})
+        assert (minus_sides_summary(self.traj_axaxa, minus)
+                != {"in" : [5], "out" : [3]})
+        assert (minus_sides_summary(self.traj_aixiaixia, minus)
+                == {"in" : [5], "out" : [6] })
+        assert (minus_sides_summary(self.traj_aixixiaxia, minus)
+                == {"in" : [5], "out" : [10] })
 
     def test_minus_with_interstitial(self):
         minus = paths.MinusInterfaceEnsemble(self.stateA, self.innermost)
-        assert_equal(minus_sides_summary(self.traj_axaxa, minus),
-                     {"in" : [5], "out" : [4]})
-        assert_equal(minus_sides_summary(self.traj_aixiaixia, minus), 
-                     {"in" : [6], "out" : [4] })
-        assert_equal(minus_sides_summary(self.traj_aixixiaxia, minus), 
-                     {"in" : [5], "out" : [8] })
+        assert (minus_sides_summary(self.traj_axaxa, minus)
+                == {"in" : [5], "out" : [4]})
+        assert (minus_sides_summary(self.traj_aixiaixia, minus)
+                == {"in" : [6], "out" : [4] })
+        assert (minus_sides_summary(self.traj_aixixiaxia, minus)
+                == {"in" : [5], "out" : [8] })
 
     def test_minus_with_multiple_excursion(self):
         minus = paths.MinusInterfaceEnsemble(self.stateA, self.stateA,
                                              n_l=4)
-        assert_equal(minus_sides_summary(self.traj_axaxaxaxa, minus),
-                     {"in" : [2, 3, 4], "out" : [1, 2, 3]})
-        assert_equal(minus_sides_summary(self.traj_aixixiaixaxiaixia, minus),
-                     {"in" : [2, 1, 3], "out" : [7, 4, 3]})
+        assert (minus_sides_summary(self.traj_axaxaxaxa, minus)
+                == {"in" : [2, 3, 4], "out" : [1, 2, 3]})
+        assert (minus_sides_summary(self.traj_aixixiaixaxiaixia, minus)
+                == {"in" : [2, 1, 3], "out" : [7, 4, 3]})
 
     def test_minus_with_interstitial_and_multiple_excursion(self):
         minus = paths.MinusInterfaceEnsemble(self.stateA, self.innermost, n_l=4)
-        assert_equal(minus_sides_summary(self.traj_axaxaxaxa, minus),
-                     {"in" : [2, 3, 4], "out" : [1, 2, 3]})
-        assert_equal(minus_sides_summary(self.traj_aixixiaixaxiaixia, minus),
-                     {"in" : [4, 1, 5], "out" : [6, 2, 3]})
-        assert_not_equal(minus_sides_summary(self.traj_aixixiaixaxiaixia, minus),
-                     {"in" : [1, 4, 5], "out" : [6, 2, 3]})
+        assert (minus_sides_summary(self.traj_axaxaxaxa, minus)
+                == {"in" : [2, 3, 4], "out" : [1, 2, 3]})
+        assert (minus_sides_summary(self.traj_aixixiaixaxiaixia, minus)
+                == {"in" : [4, 1, 5], "out" : [6, 2, 3]})
+        assert (minus_sides_summary(self.traj_aixixiaixaxiaixia, minus)
+                != {"in" : [1, 4, 5], "out" : [6, 2, 3]})
 
diff --git a/openpathsampling/tests/test_visit_all_states.py b/openpathsampling/tests/test_visit_all_states.py
index 678d35e2c..6adcd09dc 100644
--- a/openpathsampling/tests/test_visit_all_states.py
+++ b/openpathsampling/tests/test_visit_all_states.py
@@ -56,7 +56,7 @@ def test_extract_info_from_default_report():
 
 
 class TestVisitAllStatesEnsemble(object):
-    def setup(self):
+    def setup_method(self):
         self.cv = paths.FunctionCV("x", lambda x: x.xyz[0][0])
         vol_A = paths.CVDefinedVolume(self.cv, 0.0, 1.0).named("A")
         vol_B = paths.CVDefinedVolume(self.cv, 2.0, 3.0).named("B")
diff --git a/openpathsampling/tests/test_volume.py b/openpathsampling/tests/test_volume.py
index b59a67ebb..1fc687796 100644
--- a/openpathsampling/tests/test_volume.py
+++ b/openpathsampling/tests/test_volume.py
@@ -226,7 +226,7 @@ def test_get_cv_float(self, inp):
 
 
 class TestCVRangeVolumePeriodic(object):
-    def setup(self):
+    def setup_method(self):
         self.pvolA = volume.PeriodicCVDefinedVolume(op_id, -100, 75)
         self.pvolA_ = volume.PeriodicCVDefinedVolume(op_id, 75, -100)
         self.pvolB = volume.PeriodicCVDefinedVolume(op_id, 50, 100)
diff --git a/openpathsampling/tests/test_wham.py b/openpathsampling/tests/test_wham.py
index 611462258..aba2ae602 100644
--- a/openpathsampling/tests/test_wham.py
+++ b/openpathsampling/tests/test_wham.py
@@ -1,10 +1,8 @@
 from __future__ import division
 from __future__ import absolute_import
 from builtins import object
+import pytest
 from past.utils import old_div
-from nose.tools import (assert_equal, assert_not_equal, raises,
-                        assert_almost_equal)
-from nose.plugins.skip import SkipTest
 from .test_helpers import assert_items_almost_equal, assert_items_equal
 
 import pandas as pd
@@ -19,7 +17,7 @@
 
 
 class TestWHAM(object):
-    def setup(self):
+    def setup_method(self):
         self.exact = [1.0, 0.5, 0.25, 0.125, 0.0625, 0.03125, 0.015625]
         self.iface1 = [2.0, 1.0, 0.5, 0.25, 0.125, 0.0625, 0.0]
         self.iface2 = [1.0, 1.0, 1.0, 0.5, 0.25, 0.125, 0.0625]
@@ -114,7 +112,7 @@ def test_weighted_counts_tis(self):
     def test_generate_lnZ(self):
         guess = [1.0, 1.0, 1.0]
         expected_lnZ = np.log([1.0, old_div(1.0,4.0), old_div(7.0,120.0)])
-        # TODO: I'm not sure the last is log(7/120) 
+        # TODO: I'm not sure the last is log(7/120)
         # however, I got the same result out of the old version, too, and
         # this does combine into the correct result in the end (see
         # test_output_histogram)
@@ -161,7 +159,6 @@ def test_wham_bam_histogram(self):
         wham_hist = self.wham.wham_bam_histogram(self.input_df)
         np.testing.assert_allclose(wham_hist.values, self.exact)
 
-    @raises(RuntimeError)
     def test_check_overlaps_no_overlap_with_first(self):
         bad_data = np.array([[1.0, 0.0, 0.0],
                              [0.5, 0.0, 0.0],
@@ -171,9 +168,9 @@ def test_check_overlaps_no_overlap_with_first(self):
         bad_df = pd.DataFrame(data=bad_data,
                               index=self.index[0:5],
                               columns=self.columns)
-        self.wham.check_cleaned_overlaps(bad_df)
+        with pytest.raises(RuntimeError):
+            self.wham.check_cleaned_overlaps(bad_df)
 
-    @raises(RuntimeError)
     def test_check_overlaps_no_overlap_with_final(self):
         bad_data = np.array([[1.0, 0.0, 0.0],
                              [0.5, 0.0, 0.0],
@@ -184,9 +181,9 @@ def test_check_overlaps_no_overlap_with_final(self):
         bad_df = pd.DataFrame(data=bad_data,
                               index=self.index[0:6],
                               columns=self.columns)
-        self.wham.check_cleaned_overlaps(bad_df)
+        with pytest.raises(RuntimeError):
+            self.wham.check_cleaned_overlaps(bad_df)
 
-    @raises(RuntimeError)
     def test_check_overlaps_no_overlap_in_middle(self):
         bad_data = np.array([[1.0, 0.0, 0.0, 0.0],
                              [0.5, 1.0, 0.0, 0.0],
@@ -197,4 +194,5 @@ def test_check_overlaps_no_overlap_in_middle(self):
         bad_df = pd.DataFrame(data=bad_data,
                               index=self.index[0:6],
                               columns=self.columns + ['Interface 4'])
-        self.wham.check_cleaned_overlaps(bad_df)
+        with pytest.raises(RuntimeError):
+            self.wham.check_cleaned_overlaps(bad_df)