Overview
The module pyTooling.Testing offers enhanced classes for writing unit tests with Python’s unittest
framework, which is also what pytest runs.
Testcase
Testcase is the class to derive a testcase from. It is a
unittest.TestCase - everything that class offers is available unchanged - and adds what pyTooling’s test
suites otherwise write themselves.
from pyTooling.Testing import Testcase
class Slots(Testcase):
def test_SlotsAreDerived(self) -> None:
self.assertHasAttr(MyClass, "__slots__")
Deriving from it rather than from unittest.TestCase costs nothing and means a test suite picks up what is
added later without changing every class again.
Assertions
assertHasAttr() and assertNotHasAttr() check
whether an object has an attribute. They were added to unittest.TestCase in Python 3.14, so a test
suite running on 3.11 to 3.13 cannot use them - Testcase provides them there.
On Python 3.14 and newer the class defines nothing of its own, so the standard library’s implementations and messages are used and the two behave identically on every supported interpreter.
Application Testing
A unit test imports the code it tests. An application test starts the installed program the way a user does, so
the chain under test includes what importing cannot reach: the console_scripts entry point, the argument parsing
and the exit code.
ApplicationTestcase derives from Testcase. It
resolves the console script once per test class and offers two ways to start the program:
RunEntrypoint()runs the installed console script - the path a user takes, and therefore the one covering the entry-point wiring.RunModule()runspython -m <module>. When this passes while the entry point fails, the packaging is at fault rather than the code.
Both capture stdout and stderr as text and take a timeout, so a hanging program fails the test instead
of the test suite.
from pyTooling.Testing import ApplicationTestcase
class Commands(ApplicationTestcase):
_consoleScript = "myprogram"
_runnableModule = "myPackage.CLI"
def test_Version(self) -> None:
result = self.RunEntrypoint("--version")
self.assertExitCode(result)
self.assertIn("myprogram", result.stdout)
Both class variables are mandatory: a test class naming neither cannot run anything, so
setUpClass() raises a
TestingException instead of letting every testcase in the class fail with a less obvious
error. A console script that is not installed is reported the same way.
Assertions
assertExitCode() compares the exit code and, when they differ,
reports the command line together with what the program printed. That output is what explains the failure, and it
is gone once the test has finished, so it belongs in the assertion message rather than in the console.
Helpers
stripANSIColorCodes() removes ANSI escape sequences from a text. A program writing to a
terminal colors its output while the same program in a pipe usually does not - comparing the stripped text is more
robust than encoding a rule about when the codes appear.