qubes-tests.rst 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247
  1. :py:mod:`qubes.tests` -- Writing tests for qubes
  2. ================================================
  3. Writing tests is very important for ensuring quality of code that is delivered.
  4. Given test case may check for variety of conditions, but they generally fall
  5. inside those two categories of conformance tests:
  6. * Unit tests: these test smallest units of code, probably methods of functions,
  7. or even combination of arguments for one specific method.
  8. * Integration tests: these test interworking of units.
  9. We are interested in both categories.
  10. There is also distinguished category of regression tests (both unit- and
  11. integration-level), which are included because they check for specific bugs that
  12. were fixed in the past and should not happen in the future. Those should be
  13. accompanied with reference to closed ticked that describes the bug.
  14. Qubes' tests are written using :py:mod:`unittest` module from Python Standard
  15. Library for both unit test and integration tests.
  16. Test case organisation
  17. ----------------------
  18. Every module (like :py:mod:`qubes.vm.qubesvm`) should have its companion (like
  19. ``qubes.tests.vm.qubesvm``). Packages ``__init__.py`` files should be
  20. accompanied by ``init.py`` inside respective directory under :file:`tests/`.
  21. Inside tests module there should be one :py:class:`qubes.tests.QubesTestCase`
  22. class for each class in main module plus one class for functions and global
  23. variables. :py:class:`qubes.tests.QubesTestCase` classes should be named
  24. ``TC_xx_ClassName``, where ``xx`` is two-digit number. Test functions should be
  25. named ``test_xxx_test_name``, where ``xxx`` is three-digit number. You may
  26. introduce some structure of your choice in this number.
  27. Integration tests for Qubes core features are stored in :file:`tests/integ/`
  28. directory. Additional tests may be loaded from other packages (see extra test
  29. loader below). Those tests are run only on real Qubes system and are not suitable
  30. for running in VM or in Travis. Test classes of this category inherit from
  31. :py:class:`qubes.tests.SystemTestCase`.
  32. Writing tests
  33. -------------
  34. First of all, testing is art, not science. Testing is not panaceum and won't
  35. solve all of your problems. Rules given in this guide and elsewhere should be
  36. followed, but shouldn't be worshipped.
  37. Test can be divided into three phases. The first part is setup phase. In this
  38. part you should arrange for a test condition to occur. You intentionally put
  39. system under test in some specific state. Phase two is executing test condition
  40. -- for example you check some variable for equality or expect that some
  41. exception is raised. Phase three is responsible for returning a verdict. This is
  42. largely done by the framework.
  43. When writing test, you should think about order of execution. This is the reason
  44. of numbers in names of the classes and test methods. Tests should be written
  45. bottom-to-top, that is, test setups that are ran later may depend on features
  46. that are tested after but not the other way around. This is important, because
  47. when encountering failure we expect the reason happen *before*, and not after
  48. failure occured. Therefore, when encountering multiple errors, we may instantly
  49. focus on fixing the first one and not wondering if any later problems may be
  50. relevant or not. Some people also like to enable
  51. :py:attr:`unittest.TestResult.failfast` feature, which stops on the first failed
  52. test -- with wrong order this messes up their workflow.
  53. Test should fail for one reason only and test one specific issue. This does not
  54. mean that you can use one ``.assert*`` method per ``test_`` function: for
  55. example when testing one regular expression you are welcome to test many valid
  56. and/or invalid inputs, especcialy when test setup is complicated. However, if
  57. you encounter problems during setup phase, you should *skip* the test, and not
  58. fail it. This also aids interpretation of results.
  59. You may, when it makes sense, manipulate private members of classes under tests.
  60. This violates one of the founding principles of object-oriented programming, but
  61. may be required to write tests in correct order if your class provides public
  62. methods with circular dependencies. For example containers may check if added
  63. item is already in container, but you can't test ``__contains__`` method without
  64. something already inside. Don't forget to test the other method later.
  65. Special Qubes-specific considerations
  66. -------------------------------------
  67. Events
  68. ^^^^^^
  69. :py:class:`qubes.tests.QubesTestCase` provides convenient methods for checking
  70. if event fired or not: :py:meth:`qubes.tests.QubesTestCase.assertEventFired` and
  71. :py:meth:`qubes.tests.QubesTestCase.assertEventNotFired`. These require that
  72. emitter is subclass of :py:class:`qubes.tests.TestEmitter`. You may instantiate
  73. it directly::
  74. import qubes.tests
  75. class TC_10_SomeClass(qubes.tests.QubesTestCase):
  76. def test_000_event(self):
  77. emitter = qubes.tests.TestEmitter()
  78. emitter.fire_event('did-fire')
  79. self.assertEventFired(emitter, 'did-fire')
  80. If you need to snoop specific class (which already is a child of
  81. :py:class:`qubes.events.Emitter`, possibly indirect), you can define derivative
  82. class which uses :py:class:`qubes.tests.TestEmitter` as mix-in::
  83. import qubes
  84. import qubes.tests
  85. class TestHolder(qubes.tests.TestEmitter, qubes.PropertyHolder):
  86. pass
  87. class TC_20_PropertyHolder(qubes.tests.QubesTestCase):
  88. def test_000_event(self):
  89. emitter = TestHolder()
  90. self.assertEventNotFired(emitter, 'did-not-fire')
  91. Dom0
  92. ^^^^
  93. Qubes is a complex piece of software and depends on number other complex pieces,
  94. notably VM hypervisor or some other isolation provider. Not everything may be
  95. testable under all conditions. Some tests (mainly unit tests) are expected to
  96. run during compilation, but many tests (probably all of the integration tests
  97. and more) can run only inside already deployed Qubes installation. There is
  98. special decorator, :py:func:`qubes.tests.skipUnlessDom0` which causes test (or
  99. even entire class) to be skipped outside dom0. Use it freely::
  100. import qubes.tests
  101. class TC_30_SomeClass(qubes.tests.QubesTestCase):
  102. @qubes.tests.skipUnlessDom0
  103. def test_000_inside_dom0(self):
  104. # this is skipped outside dom0
  105. pass
  106. @qubes.tests.skipUnlessDom0
  107. class TC_31_SomeOtherClass(qubes.tests.QubesTestCase):
  108. # all tests in this class are skipped
  109. pass
  110. VM tests
  111. ^^^^^^^^
  112. Some integration tests verifies not only dom0 part of the system, but also VM
  113. part. In those cases, it makes sense to iterate them for different templates.
  114. Additionally, list of the templates can be dynamic (different templates
  115. installed, only some considered for testing etc).
  116. This can be achieved by creating a mixin class with the actual tests (a class
  117. inheriting just from :py:class:`object`, instead of
  118. :py:class:`qubes.tests.SystemTestCase` or :py:class:`unittest.TestCase`) and
  119. then create actual test classes dynamically using
  120. :py:func:`qubes.tests.create_testcases_for_templates`.
  121. Test classes created this way will have :py:attr:`template` set to the template
  122. name under test and also this template will be set as the default template
  123. during the test execution.
  124. The function takes a test class name prefix (template name will be appended to
  125. it after '_' separator), a classes to inherit from (in most cases the just
  126. created mixin and :py:class:`qubes.tests.SystemTestCase`) and a current module
  127. object (use `sys.modules[__name__]`). The function will return created test
  128. classes but also add them to the appropriate module (pointed by the *module*
  129. parameter). This should be done in two cases:
  130. * :py:func:`load_tests` function - when test loader request list of tests
  131. * on module import time, using a wrapper
  132. :py:func:`qubes.tests.maybe_create_testcases_on_import` (will call the
  133. function only if explicit list of templates is given, to avoid loading
  134. :file:`qubes.xml` when just importing the module)
  135. An example boilerplate looks like this::
  136. def create_testcases_for_templates():
  137. return qubes.tests.create_testcases_for_templates('TC_00_AppVM',
  138. TC_00_AppVMMixin, qubes.tests.SystemTestCase,
  139. module=sys.modules[__name__])
  140. def load_tests(loader, tests, pattern):
  141. tests.addTests(loader.loadTestsFromNames(
  142. create_testcases_for_templates()))
  143. return tests
  144. qubes.tests.maybe_create_testcases_on_import(create_testcases_for_templates)
  145. This will by default create tests for all the templates installed in the system.
  146. Additionally, it is possible to control this process using environment
  147. variables:
  148. * `QUBES_TEST_TEMPLATES` - space separated list of templates to test
  149. * `QUBES_TEST_LOAD_ALL` - create tests for all the templates (by inspecting
  150. the :file:`qubes.xml` file), even at module import time
  151. This is dynamic test creation is intentionally made compatible with Nose2 test
  152. runner and its load_tests protocol implementation.
  153. Extra tests
  154. ^^^^^^^^^^^
  155. Most tests live in this package, but it is also possible to store tests in other
  156. packages while still using infrastructure provided here and include them in the
  157. common test run. Loading extra tests is implemented in
  158. :py:mod:`qubes.tests.extra`. To write test to be loaded this way, you need to
  159. create test class(es) as usual. You can also use helper class
  160. :py:class:`qubes.tests.extra.ExtraTestCase` (instead of
  161. :py:class:`qubes.tests.SystemTestCase`) which provide few convenient functions
  162. and hide usage of asyncio for simple cases (like `vm.start()`, `vm.run()`).
  163. The next step is to register the test class(es). You need to do this by defining
  164. entry point for your package. There are two groups:
  165. * `qubes.tests.extra` - for general tests (called once)
  166. * `qubes.tests.extra.for_template` - for per-VM tests (called for each template
  167. under test)
  168. As a name in the group, choose something unique, preferably package name. An
  169. object reference should point at the function that returns a list of test
  170. classes.
  171. Example :file:`setup.py`::
  172. from setuptools import setup
  173. setup(
  174. name='splitgpg',
  175. version='1.0',
  176. packages=['splitgpg'],
  177. entry_points={
  178. 'qubes.tests.extra.for_template':
  179. 'splitgpg = splitgpg.tests:list_tests',
  180. }
  181. )
  182. The test loading process can be additionally controlled with environment
  183. variables:
  184. * `QUBES_TEST_EXTRA_INCLUDE` - space separated list of tests to include (named
  185. by a name in an entry point, `splitgpg` in the above example); if defined, only
  186. those extra tests will be loaded
  187. * `QUBES_TEST_EXTRA_EXCLUDE` - space separated list of tests to exclude
  188. Module contents
  189. ---------------
  190. .. automodule:: qubes.tests
  191. :members:
  192. :show-inheritance:
  193. .. vim: ts=3 sw=3 et tw=80