9. ARMI Testing Tools

ARMI has many useful tools to streamline tests in the plugins. Included here are some popular ones. If you are trying to write a new unit test, chances are something like it has been done before and you do not need to design it from scratch. Look around ARMI and other plugins for examples of tests. The armi.testing module is always a good place to start.

9.1. Testing with runLog

Use Case: Test code that prints to stdout

While there are some other mocking examples in ARMI, none are as heavily used as mockRunLogs. mockRunLogs.BufferLog() is used to capture the runLog output instead of printing it.

In test_comparedb3.py, there is a (simplified here) use case. A portion of the test for _diffSpecialData wants to confirm the below printout has happened, so it uses the getStdout() method to check that the expected printout exists.

Example of mockRunLogs:

from armi.tests import mockRunLogs

class TestCompareDB3(unittest.TestCase):
    # ...

    def test_diffSpecialData(self):
        dr = DiffResults(0.01)
        fileName = "test.txt"
        with OutputWriter(fileName) as out:
            with mockRunLogs.BufferLog() as mock:
                #... skip for clarity: create refData & srcData
                _diffSpecialData(refData, srcData, out, dr)
                self.assertEqual(dr.nDiffs(), 0)
                self.assertIn("Special formatting parameters for", mock.getStdout())

There are examples of this throughout ARMI. Search for BufferLog or getStdout in the code to find examples.

9.2. Self-Cleaning Directories

Use Case: Automatically cleans up tests that create files:

from armi.utils.directoryChangers import TemporaryDirectoryChanger

Two main uses of this class in testing:

  1. Standalone test that calls code that creates something (test_operators.py):

def test_snapshotRequest(self, fakeDirList, fakeCopy):
    fakeDirList.return_value = ["mccAA.inp"]
    with TemporaryDirectoryChanger():
        with mockRunLogs.BufferLog() as mock:
            self.o.snapshotRequest(0, 1)
            self.assertIn("ISOTXS-c0", mock.getStdout())
  1. Setup and teardown of a testing class, where all/most of the tests create something (test_comparedb3.py):

class TestCompareDB3(unittest.TestCase):
    """Tests for the compareDB3 module."""

    def setUp(self):
        self.td = TemporaryDirectoryChanger()
        self.td.__enter__()

    def tearDown(self):
        self.td.__exit__(None, None, None)

    def test_outputWriter(self):
        fileName = "test_outputWriter.txt"
        with OutputWriter(fileName) as out:
            out.writeln("Rubber Baby Buggy Bumpers")

        txt = open(fileName, "r").read()
        self.assertIn("Rubber", txt)

Note that sometimes it is necessary to give the temporary directory change object a non-default root path:

Include root argument
THIS_DIR = os.path.dirname(__file__)
# ...

def test_something():
    with TemporaryDirectoryChanger(root=THIS_DIR):
        # test something

9.3. Load a Test Reactor

Use Case: You need a full reactor for a unit test

Warning

This is computationally expensive, and historically over-used for unit tests. Consider whether mocking or BYO components (below) can be used instead.

To get the standard ARMI test reactor, import this:

from armi.reactor.tests.test_reactors import loadTestReactor

This function will return a reactor object. And it takes various input arguments to allow you to customize that reactor:

def loadTestReactor(
    inputFilePath=TEST_ROOT,
    customSettings=None,
    inputFileName="armiRun.yaml",
):

So many interfaces and methods require an operator or a reactor, and loadTestReactor returns both. From there you can use the whole reactor or just grab a single ARMI object, like a fuel block:

 _o, r = loadTestReactor(
    os.path.join(TEST_ROOT, "smallestTestReactor"),
    inputFileName="armiRunSmallest.yaml",
)

# grab a pinned fuel block
b = r.core.getFirstBlock(Flags.FUEL)

If you need a full reactor for a unit test, always try to start with the smallestTestReactor.yaml shown above first. Your tests will run faster if you pick the smallest possible reactor that meets your needs. Less is more.

9.4. Test Blocks and Assemblies

Use Case: Your unit test needs some ARMI objects, but not a full test reactor.

ARMI provides several helpful tools for generating simple blocks and assemblies for unit tests:

  • from armi.reactor.tests.test_assemblies import buildTestAssemblies - Two hex blocks.

  • from armi.reactor.tests.test_blocks import buildSimpleFuelBlock - A simple hex block containing fuel, clad, duct, and coolant.

  • from armi.reactor.tests.test_blocks import loadTestBlock - An annular test block.