| .. _tempest_test_writing: |
| |
| Tempest Test Writing Guide |
| ########################## |
| |
| This guide serves as a starting point for developers working on writing new |
| Tempest tests. At a high level, tests in Tempest are just tests that conform to |
| the standard python `unit test`_ framework. But there are several aspects of |
| that are unique to Tempest and its role as an integration test suite running |
| against a real cloud. |
| |
| .. _unit test: https://docs.python.org/3.6/library/unittest.html |
| |
| .. note:: This guide is for writing tests in the Tempest repository. While many |
| parts of this guide are also applicable to Tempest plugins, not all |
| the APIs mentioned are considered stable or recommended for use in |
| plugins. Please refer to :ref:`tempest_plugin` for details about |
| writing plugins |
| |
| |
| Adding a New TestCase |
| ===================== |
| |
| The base unit of testing in Tempest is the `TestCase`_ (also called the test |
| class). Each TestCase contains test methods which are the individual tests that |
| will be executed by the test runner. But, the TestCase is the smallest self |
| contained unit for tests from the Tempest perspective. It's also the level at |
| which Tempest is parallel safe. In other words, multiple TestCases can be |
| executed in parallel, but individual test methods in the same TestCase can not. |
| Also, all test methods within a TestCase are assumed to be executed serially. As |
| such you can use the test case to store variables that are shared between |
| methods. |
| |
| .. _TestCase: https://docs.python.org/3.6/library/unittest.html#unittest.TestCase |
| |
| In standard unittest the lifecycle of a TestCase can be described in the |
| following phases: |
| |
| #. setUpClass |
| #. setUp |
| #. Test Execution |
| #. tearDown |
| #. doCleanups |
| #. tearDownClass |
| |
| setUpClass |
| ---------- |
| |
| The setUpClass phase is the first phase executed by the test runner and is used |
| to perform any setup required for all the test methods to be executed. In |
| Tempest this is a very important step and will automatically do the necessary |
| setup for interacting with the configured cloud. |
| |
| To accomplish this you do **not** define a setUpClass function, instead there |
| are a number of predefined phases to setUpClass that are used. The phases are: |
| |
| * skip_checks |
| * setup_credentials |
| * setup_clients |
| * resource_setup |
| |
| which is executed in that order. Cleanup of resources provisioned during |
| the resource_setup must be scheduled right after provisioning using |
| the addClassResourceCleanup helper. The resource cleanups stacked this way |
| are executed in reverse order during tearDownClass, before the cleanup of |
| test credentials takes place. An example of a TestCase which defines all |
| of these would be:: |
| |
| from tempest.common import waiters |
| from tempest import config |
| from tempest.lib.common.utils import test_utils |
| from tempest import test |
| |
| CONF = config.CONF |
| |
| |
| class TestExampleCase(test.BaseTestCase): |
| |
| @classmethod |
| def skip_checks(cls): |
| """This section is used to evaluate config early and skip all test |
| methods based on these checks |
| """ |
| super(TestExampleCase, cls).skip_checks() |
| if not CONF.section.foo |
| cls.skip('A helpful message') |
| |
| @classmethod |
| def setup_credentials(cls): |
| """This section is used to do any manual credential allocation and also |
| in the case of dynamic credentials to override the default network |
| resource creation/auto allocation |
| """ |
| # This call is used to tell the credential allocator to not create any |
| # network resources for this test case. It also enables selective |
| # creation of other neutron resources. NOTE: it must go before the |
| # super call |
| cls.set_network_resources() |
| super(TestExampleCase, cls).setup_credentials() |
| |
| @classmethod |
| def setup_clients(cls): |
| """This section is used to setup client aliases from the manager object |
| or to initialize any additional clients. Except in a few very |
| specific situations you should not need to use this. |
| """ |
| super(TestExampleCase, cls).setup_clients() |
| cls.servers_client = cls.os_primary.servers_client |
| |
| @classmethod |
| def resource_setup(cls): |
| """This section is used to create any resources or objects which are |
| going to be used and shared by **all** test methods in the |
| TestCase. Note then anything created in this section must also be |
| destroyed in the corresponding resource_cleanup() method (which will |
| be run during tearDownClass()) |
| """ |
| super(TestExampleCase, cls).resource_setup() |
| cls.shared_server = cls.servers_client.create_server(...) |
| cls.addClassResourceCleanup(waiters.wait_for_server_termination, |
| cls.servers_client, |
| cls.shared_server['id']) |
| cls.addClassResourceCleanup( |
| test_utils.call_and_ignore_notfound_exc( |
| cls.servers_client.delete_server, |
| cls.shared_server['id'])) |
| |
| .. _credentials: |
| |
| Allocating Credentials |
| '''''''''''''''''''''' |
| |
| Since Tempest tests are all about testing a running cloud, every test will need |
| credentials to be able to make API requests against the cloud. Since this is |
| critical to operation and, when running in parallel, easy to make a mistake, |
| the base TestCase class will automatically allocate a regular user for each |
| TestCase during the setup_credentials() phase. During this process it will also |
| initialize a client manager object using those credentials, which will be your |
| entry point into interacting with the cloud. For more details on how credentials |
| are allocated the :ref:`tempest_cred_provider_conf` section of the Tempest |
| Configuration Guide provides more details on the operation of this. |
| |
| There are some cases when you need more than a single set of credentials, or |
| credentials with a more specialized set of roles. To accomplish this you have |
| to set a class variable ``credentials`` on the TestCase directly. For example:: |
| |
| from tempest import test |
| |
| class TestExampleAdmin(test.BaseTestCase): |
| |
| credentials = ['primary', 'admin'] |
| |
| @classmethod |
| def skip_checks(cls): |
| ... |
| |
| In this example the ``TestExampleAdmin`` TestCase will allocate 2 sets of |
| credentials, one regular user and one admin user. The corresponding manager |
| objects will be set as class variables ``cls.os_primary`` and ``cls.os_admin`` |
| respectively. You can also allocate a second user by putting **'alt'** in the |
| list too. A set of alt credentials are the same as primary but can be used |
| for tests cases that need a second user/project. |
| |
| You can also specify credentials with specific roles assigned. This is useful |
| for cases where there are specific RBAC requirements hard coded into an API. |
| The canonical example of this are swift tests which often want to test swift's |
| concepts of operator and reseller_admin. An actual example from Tempest on how |
| to do this is:: |
| |
| class PublicObjectTest(base.BaseObjectTest): |
| |
| credentials = [['operator', CONF.object_storage.operator_role], |
| ['operator_alt', CONF.object_storage.operator_role]] |
| |
| @classmethod |
| def setup_credentials(cls): |
| super(PublicObjectTest, cls).setup_credentials() |
| ... |
| |
| In this case the manager objects will be set to ``cls.os_roles_operator`` and |
| ``cls.os_roles_operator_alt`` respectively. |
| |
| |
| There is no limit to how many credentials you can allocate in this manner, |
| however in almost every case you should **not** need more than 3 sets of |
| credentials per test case. |
| |
| To figure out the mapping of manager objects set on the TestCase and the |
| requested credentials you can reference: |
| |
| +-------------------+---------------------+ |
| | Credentials Entry | Manager Variable | |
| +===================+=====================+ |
| | primary | cls.os_primary | |
| +-------------------+---------------------+ |
| | admin | cls.os_admin | |
| +-------------------+---------------------+ |
| | alt | cls.os_alt | |
| +-------------------+---------------------+ |
| | [$label, $role] | cls.os_roles_$label | |
| +-------------------+---------------------+ |
| |
| By default cls.os_primary is available since it is allocated in the base Tempest test |
| class (located in tempest/test.py). If your TestCase inherits from a different |
| direct parent class (it'll still inherit from the BaseTestCase, just not |
| directly) be sure to check if that class overrides allocated credentials. |
| |
| Dealing with Network Allocation |
| ''''''''''''''''''''''''''''''' |
| |
| When Neutron is enabled and a testing requires networking this isn't normally |
| automatically setup when a tenant is created. Since Tempest needs isolated |
| tenants to function properly it also needs to handle network allocation. By |
| default the base test class will allocate a network, subnet, and router |
| automatically (this depends on the configured credential provider, for more |
| details see: :ref:`tempest_conf_network_allocation`). However, there are |
| situations where you do no need all of these resources allocated (or your |
| TestCase inherits from a class that overrides the default in tempest/test.py). |
| There is a class level mechanism to override this allocation and specify which |
| resources you need. To do this you need to call `cls.set_network_resources()` |
| in the `setup_credentials()` method before the `super()`. For example:: |
| |
| from tempest import test |
| |
| |
| class TestExampleCase(test.BaseTestCase): |
| |
| @classmethod |
| def setup_credentials(cls): |
| cls.set_network_resources(network=True, subnet=True, router=False) |
| super(TestExampleCase, cls).setup_credentials() |
| |
| There are 2 quirks with the usage here. First for the set_network_resources |
| function to work properly it **must be called before super()**. This is so |
| that children classes' settings are always used instead of a parent classes'. |
| The other quirk here is that if you do not want to allocate any network |
| resources for your test class simply call `set_network_resources()` without |
| any arguments. For example:: |
| |
| from tempest import test |
| |
| |
| class TestExampleCase(test.BaseTestCase): |
| |
| @classmethod |
| def setup_credentials(cls): |
| cls.set_network_resources() |
| super(TestExampleCase, cls).setup_credentials() |
| |
| This will not allocate any networking resources. This is because by default all |
| the arguments default to False. |
| |
| It's also worth pointing out that it is common for base test classes for |
| different services (and scenario tests) to override this setting. When |
| inheriting from classes other than the base TestCase in tempest/test.py it is |
| worth checking the immediate parent for what is set to determine if your |
| class needs to override that setting. |
| |
| Running some tests in serial |
| ---------------------------- |
| Tempest potentially runs test cases in parallel, depending on the configuration. |
| However, sometimes you need to make sure that tests are not interfering with |
| each other via OpenStack resources. Tempest creates separate projects for each |
| test class to separate project based resources between test cases. |
| |
| If your tests use resources outside of projects, e.g. host aggregates then |
| you might need to explicitly separate interfering test cases. If you only need |
| to separate a small set of testcases from each other then you can use the |
| ``LockFixture``. |
| |
| However, in some cases a small set of tests needs to be run independently from |
| the rest of the test cases. For example, some of the host aggregate and |
| availability zone testing needs compute nodes without any running nova server |
| to be able to move compute hosts between availability zones. But many tempest |
| tests start one or more nova servers. In this scenario you can mark the small |
| set of tests that needs to be independent from the rest with the ``@serial`` |
| class decorator. This will make sure that even if tempest is configured to run |
| the tests in parallel the tests in the marked test class will always be executed |
| separately from the rest of the test cases. |
| |
| Please note that due to test ordering optimization reasons test cases marked |
| for ``@serial`` execution need to be put under ``tempest/serial_tests`` |
| directory. This will ensure that the serial tests will block the parallel tests |
| in the least amount of time. |
| |
| Interacting with Credentials and Clients |
| ======================================== |
| |
| Once you have your basic TestCase setup you'll want to start writing tests. To |
| do that you need to interact with an OpenStack deployment. This section will |
| cover how credentials and clients are used inside of Tempest tests. |
| |
| |
| Manager Objects |
| --------------- |
| |
| The primary interface with which you interact with both credentials and |
| API clients is the client manager object. These objects are created |
| automatically by the base test class as part of credential setup (for more |
| details see the previous :ref:`credentials` section). Each manager object is |
| initialized with a set of credentials and has each client object already setup |
| to use that set of credentials for making all the API requests. Each client is |
| accessible as a top level attribute on the manager object. So to start making |
| API requests you just access the client's method for making that call and the |
| credentials are already setup for you. For example if you wanted to make an API |
| call to create a server in Nova:: |
| |
| from tempest import test |
| |
| |
| class TestExampleCase(test.BaseTestCase): |
| def test_example_create_server(self): |
| self.os_primary.servers_client.create_server(...) |
| |
| is all you need to do. As described previously, in the above example the |
| ``self.os_primary`` is created automatically because the base test class sets the |
| ``credentials`` attribute to allocate a primary credential set and initializes |
| the client manager as ``self.os_primary``. This same access pattern can be used |
| for all of the clients in Tempest. |
| |
| Credentials Objects |
| ------------------- |
| |
| In certain cases you need direct access to the credentials (the most common |
| use case would be an API request that takes a user or project id in the request |
| body). If you're in a situation where you need to access this you'll need to |
| access the ``credentials`` object which is allocated from the configured |
| credential provider in the base test class. This is accessible from the manager |
| object via the manager's ``credentials`` attribute. For example:: |
| |
| from tempest import test |
| |
| |
| class TestExampleCase(test.BaseTestCase): |
| def test_example_create_server(self): |
| credentials = self.os_primary.credentials |
| |
| The credentials object provides access to all of the credential information you |
| would need to make API requests. For example, building off the previous |
| example:: |
| |
| from tempest import test |
| |
| |
| class TestExampleCase(test.BaseTestCase): |
| def test_example_create_server(self): |
| credentials = self.os_primary.credentials |
| username = credentials.username |
| user_id = credentials.user_id |
| password = credentials.password |
| tenant_id = credentials.tenant_id |