Welcome to charm-test’s documentation!¶
Overview¶
This package sports a collection of helpers for unit-testing Juju charms.
In particular, it extends systemfixtures by faking out hook tools processes (config-get, juju-log, etc), so authors have a complete suite of fakes for the typical “boundaries” of a Juju charm.
The examples below cover the Juju-related boundaries part, for information about the rest of fake boundaries (file system, processes, network, users, groups, etc) see the systemfixtures reference.
Examples¶
Juju environment variables¶
The CharmTest
base class uses an EnvironmentVariable
fixture to set the environment variables that the charm code runtime
expects (for instance when calling the associated charmhelpers
APIs):
>>> from testtools.matchers import DirExists
>>> from charmtest import CharmTest
>>> from charmhelpers.core import hookenv
>>> def example_charm_logic():
... return {
... "service-name": hookenv.service_name(),
... "local-unit": hookenv.local_unit(),
... "charm-dir": hookenv.charm_dir(),
... }
>>> class ExampleCharmTest(CharmTest):
...
... def test_charm_logic(self):
... result = example_charm_logic()
... self.assertEqual("test", result["service-name"])
... self.assertEqual("test/0", result["local-unit"])
... self.assertThat(result["charm-dir"], DirExists())
>>> ExampleCharmTest(methodName="test_charm_logic").run().wasSuccessful()
True
Juju hook tools¶
The CharmTest
base class uses a FakePopen fixture to redirect calls
to Python’s subprocess.Popen
.
This means that the execution of Juju hook tools will be handled by fake code that modifies fake backend data, which is in turn exposed to test methods.
You can programmatically modify such fake backend data to exercise the charm code under test.
For example, assuming that you’re using using the charmhelpers.core.hookenv
utilities to execute hook tools, you can have tests like:
>>> def example_charm_logic():
... hookenv.log("Hello world!")
... hookenv.open_port(1234)
... return hookenv.config()["foo"]
>>> class ExampleCharmTest(CharmTest):
...
... def test_charm_logic(self):
... self.fakes.juju.config["foo"] = "bar"
... result = example_charm_logic()
... self.assertEqual("INFO: Hello world!", self.fakes.juju.log[0])
... self.assertEqual("bar", result)
... self.assertEqual({1234}, self.fakes.juju.ports["TCP"])
... self.assertEqual("10.1.2.3", hookenv.unit_private_ip())
>>> ExampleCharmTest(methodName="test_charm_logic").run().wasSuccessful()
True
Charm metadata, config and templates¶
The CharmTest
base class will traverse the process’ all ancestors of the
current working directory, until it finds a directory containing a file
named “metadata.yaml”. That directory will be considered the code tree of the
charm under test. Charm metadata, default config values and templates will
be made available to the underlying tests:
>>> import os
>>>
>>> def example_charm_logic():
... return {
... "summary": hookenv.metadata()["summary"],
... "config-foo": hookenv.config()["foo"],
... "has-templates-dir": os.path.exists(os.path.join(hookenv.charm_dir(), "templates")),
... }
>>> class ExampleCharmTest(CharmTest):
...
... def test_charm_logic(self):
... result = example_charm_logic()
... self.assertEqual({
... "summary": "Test charm",
... "config-foo": "abc",
... "has-templates-dir": True},
... result)
>>> ExampleCharmTest(methodName="test_charm_logic").run().wasSuccessful()
True
Processes, network, file system, users, groups, etc.¶
The CharmTest
base class also sets up a number of useful fixtures from
the systemfixtures
package. See the systemfixtures reference for further
documentation:
>>> class ExampleCharmTest(CharmTest):
...
... def test_other_fakes(self):
... self.assertTrue(self.fakes.processes)
... self.assertTrue(self.fakes.fs)
... self.assertTrue(self.fakes.users)
... self.assertTrue(self.fakes.groups)
... self.assertTrue(self.fakes.network)
>>> ExampleCharmTest(methodName="test_other_fakes").run().wasSuccessful()
True