Skip to content

Commit

Permalink
migrate unittest to pytest (#62)
Browse files Browse the repository at this point in the history
* migrate unittest to pytest

* add custom graphical marker

* fix docstrings

* review actions
  • Loading branch information
bjlittle committed Jun 4, 2020
1 parent b378dba commit 2284ec9
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 185 deletions.
2 changes: 2 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ exclude =
[tool:pytest]
testpaths =
tephi/
markers =
graphical: mark a test as a graphical test
addopts =
-ra
-v
Expand Down
85 changes: 33 additions & 52 deletions tephi/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@
import json
import os
import sys
import unittest

import filelock
import matplotlib
import numpy as np
import pytest
import requests

from tephi import DATA_DIR
Expand Down Expand Up @@ -60,9 +60,9 @@
INET_AVAILABLE = False


skip_inet = unittest.skipIf(
requires_inet = pytest.mark.skipif(
not INET_AVAILABLE,
('Test(s) require an "internet connection", which is not available.'),
reason=('Test requires an "internet connection", which is not available.'),
)


Expand Down Expand Up @@ -99,67 +99,47 @@ def get_result_path(relative_path):
return os.path.abspath(os.path.join(_RESULT_PATH, relative_path))


class TephiTest(unittest.TestCase):
class TephiTest:
"""
A subclass of unittest.TestCase which provides testing functionality
specific to tephi.
Utility class containing common testing framework functionality.
"""

_assertion_counts = collections.defaultdict(int)

def _unique_id(self):
"""
Returns the unique ID for the current assertion.
The ID is composed of two parts: a unique ID for the current test
(which is itself composed of the module, class, and test names), and
a sequential counter (specific to the current test) that is incremented
on each call.
For example, calls from a "test_tx" routine followed by a "test_ty"
routine might result in::
test_plot.TestContourf.test_tx.0
test_plot.TestContourf.test_tx.1
test_plot.TestContourf.test_tx.2
test_plot.TestContourf.test_ty.0
"""
# Obtain a consistent ID for the current test.

# NB. unittest.TestCase.id() returns different values depending on
# whether the test has been run explicitly, or via test discovery.
# For example:
# python tests/test_brand.py
# => '__main__.TestBranding.test_combo'
# python -m unittest discover
# => 'tephi.tests.test_brand.TestBranding.test_combo'
bits = self.id().split(".")[-3:]
if bits[0] == "__main__":
file_name = os.path.basename(sys.modules["__main__"].__file__)
bits[0] = os.path.splitext(file_name)[0]
test_id = ".".join(bits)

# Derive the sequential assertion ID within the test
assertion_id = self._assertion_counts[test_id]
self._assertion_counts[test_id] += 1

return "{}.{}".format(test_id, assertion_id)

def assertArrayEqual(self, a, b):
__tracebackhide__ = True
return np.testing.assert_array_equal(a, b)

def assertArrayAlmostEqual(self, a, b, *args, **kwargs):
__tracebackhide__ = True
return np.testing.assert_array_almost_equal(a, b, *args, **kwargs)


class GraphicsTest(TephiTest):
def tearDown(self):
# If a plotting test bombs out it can leave the current figure in an
# odd state, so we make sure it's been disposed of.
plt.close()

def check_graphic(self):
_assertion_count = collections.defaultdict(int)

def _unique_id(self, nodeid):
"""Create a hashable key to represent the unique test invocation.
Construct the hashable key from the provided nodeid and a sequential
counter specific to the current test, that is incremented on each call.
Parameters
----------
nodeid : str
Unique identifier for the current test. See :func:`nodeid` fixture.
Returns
-------
str
The nodeid with sequential counter.
"""
count = self._assertion_count[nodeid]
self._assertion_count[nodeid] += 1
return f"{nodeid}.{count}"

def check_graphic(self, nodeid):
"""
Check the hash of the current matplotlib figure matches the expected
image hash for the current graphic test.
Expand All @@ -170,11 +150,12 @@ def check_graphic(self):
output directory, and the imagerepo.json file being updated.
"""
__tracebackhide__ = True
import imagehash
from PIL import Image

dev_mode = os.environ.get("TEPHI_TEST_CREATE_MISSING")
unique_id = self._unique_id()
unique_id = self._unique_id(nodeid)
repo_fname = os.path.join(_RESULT_PATH, "imagerepo.json")
repo = {}
if os.path.isfile(repo_fname):
Expand Down
43 changes: 43 additions & 0 deletions tephi/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright Tephi contributors
#
# This file is part of Tephi and is released under the LGPL license.
# See COPYING and COPYING.LESSER in the root of the repository for full
# licensing details.
"""pytest configuration"""

import matplotlib.pyplot as plt
import pytest


@pytest.fixture
def close_plot():
"""This fixture closes the current matplotlib plot associated with the graphical test.
"""
yield
plt.close()


@pytest.fixture
def nodeid(request):
"""This fixture returns the unique test name for the method.
Constructs the nodeid, which is composed of the test module name,
class name, and method name.
Parameters
----------
request : fixture
pytest built-in fixture providing information of the requesting
test function.
Returns
-------
str
The test nodeid consisting of the module, class and test name.
"""
root = request.fspath.basename.split(".")[0]
klass = request.cls.__name__
func = request.node.name
return ".".join([root, klass, func])
15 changes: 5 additions & 10 deletions tephi/tests/test_imagerepo.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import itertools
import json
import os
import unittest
import pytest

import requests

Expand All @@ -27,13 +27,13 @@
)


@tests.skip_inet
class TestImageRepoJSON(tests.TephiTest):
@tests.requires_inet
class TestImageRepoJSON:
def test(self):
response = requests.get(IMAGE_MANIFEST)

emsg = 'Failed to download "image_manifest.txt"'
self.assertEqual(response.status_code, requests.codes.ok, msg=emsg)
assert response.status_code == requests.codes.ok, emsg

image_manifest = response.content.decode("utf-8")
image_manifest = [line.strip() for line in image_manifest.split("\n")]
Expand All @@ -59,9 +59,4 @@ def test(self):
)
emsg = emsg.format(count, IMAGE_MANIFEST)
emsg += "\t".join(uri for uri in missing)
# Always fails when we get here: report the problem.
self.assertEqual(count, 0, msg=emsg)


if __name__ == "__main__":
unittest.main()
pytest.fail(emsg)

0 comments on commit 2284ec9

Please sign in to comment.