gunittest_testing.rst 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351
  1. Testing GRASS GIS source code and modules
  2. =========================================
  3. Introduction
  4. ------------
  5. For the testing we will be using system based on Python `unittest`_ package.
  6. The system is not finished yet.
  7. For now, the best way is to use just Python unittest package as is.
  8. Additionally, it is possible to use Python `doctest`_ package
  9. which is compatible with unittest at certain level.
  10. Both packages are part of the standard Python distribution.
  11. The content of this document may become part of submitting files and
  12. the documentation of testing framework classes and scripts.
  13. Testing with gunittest package in general
  14. -----------------------------------------
  15. The tests should be in files in a ``testsuite`` directory which is a subdirectory
  16. of the directory with tested files (module, package, library). Each testing file
  17. (test file) can have can have several testing classes (test cases).
  18. All test files names should have pattern ``test*.py``.
  19. GRASS GIS `gunittest` package and testing framework is similar to the standard
  20. Python ``unittest`` package, so the ways to build tests are very similar.
  21. ::
  22. from grass.guinttest import TestCase
  23. class TestPython(TestCase):
  24. def test_counting(self):
  25. """Test that Python can count to two"""
  26. self.assertEqual(1 + 1, 2)
  27. if __name__ == '__main__':
  28. from grass.guinttest import test
  29. test()
  30. Each test file should be able to run by itself and accept certain set of command
  31. line parameters. This is ensured using `gunittest.test()`.
  32. To run (invoke) all tests in the source tree run::
  33. python -m grass.gunittest.main [gisdbase] location test_data_category
  34. All test files in all ``testsuite`` directories will be executed and
  35. a report will be created in a newly created ``testreport`` directory.
  36. Open the file ``testreport/index.html`` to browse though the results.
  37. You need to be in GRASS session to run the tests.
  38. The test_data_category parameter serves to filter tests accoring to data
  39. they can run successfully with. It is ignored for tests which does not have
  40. this specified.
  41. Each running test file gets its own mapset and current working directory
  42. but all run are in one location.
  43. .. warning::
  44. The current location is ignored but you should run not invoke tests
  45. in the location which is precious to you for the case that something fails.
  46. To run individual tests file you should be in GRASS session in GRASS NC sample
  47. location in a mapset of arbitrary name (except for the predefined mapsets).
  48. Your tests can rely on maps which are present in the GRASS NC sample location.
  49. But if you can provide tests which are independent on location it is better.
  50. Read the documentation of Python ``unittest`` package for a list of assert
  51. methods which you can use to test your results. For test of more complex
  52. GRASS-specific results refer to :class:`gunittest.case.TestCase` class
  53. documentation.
  54. Tests of GRASS modules
  55. ----------------------
  56. This is applicable for both GRASS modules written in C or C++ and
  57. GRASS modules written in Python since we are testing the whole module
  58. (which is invoked as a subprocess).
  59. ::
  60. def test_elevation(self):
  61. self.assertModule('r.info', map='elevation', flags='g')
  62. ...
  63. Use method ``assertRasterMinMax()`` to test that a result is within
  64. expected range. This is a very general test which checks the basic
  65. correctness of the result and can be used with different maps
  66. in different locations.
  67. ::
  68. def test_slope_limits(self):
  69. slope = 'limits_slope'
  70. self.assertModule('r.slope.aspect', elevation='elevation',
  71. slope=slope)
  72. self.assertRasterMinMax(map=slope, refmin=0, refmax=90,
  73. msg="Slope in degrees must be between 0 and 90")
  74. .. todo::
  75. Add example of assertions of key-value results.
  76. Especially if a module module has a lot of different parameters allowed
  77. in different combinations, you should test the if the wrong ones are really
  78. disallowed and proper error messages are provided (in addition, you can
  79. test things such as creation and removal of maps in error states).
  80. ::
  81. from grass.gunittest import SimpleModule
  82. class TestRInfoParameterHandling(TestCase):
  83. """Test r.info handling of wrong input of parameters."""
  84. def test_rinfo_wrong_map(self):
  85. """Test input of map which does not exist."""
  86. map_name = 'does_not_exist'
  87. # create a module instance suitable for testing
  88. rinfo = SimpleModule('r.info', map=map_name, flags='g')
  89. # test that module fails (ends with non-zero return code)
  90. self.assertModuleFail(rinfo)
  91. # test that error output is not empty
  92. self.assertTrue(rinfo.outputs.stderr)
  93. # test that the right map is mentioned in the error message
  94. self.assertIn(map_name, stderr)
  95. In some cases it might be advantageous to create a module instance
  96. in `setUp()` method and then modify it in test methods.
  97. .. note:
  98. Test should be (natural) language, i.e. locale, independent
  99. to allow testing the functionality under different locale settings.
  100. So, if you are testing content of messages (which should be usually
  101. translated), use `assertIn()` method (regular expression might be
  102. applicable in some cases but in most cases `in` is exactly the
  103. operation needed).
  104. Tests of C and C++ code
  105. -----------------------
  106. Tests of Python code
  107. --------------------
  108. Use `gunittest` for this purpose in the same way as `unittest`_ would be used.
  109. Testing Python code with doctest
  110. --------------------------------
  111. .. note::
  112. The primary use of ``doctest`` is to ensure that the documentation
  113. for functions and classes is valid. Additionally, it can increase
  114. the number of tests when executed together with other tests.
  115. In Python, the easiest thing to test are functions which performs some
  116. computations or string manipulations, i.e. they have some numbers or strings
  117. on the input and some other numbers or strings on the output.
  118. At the beginning you can use doctest for this purpose. The syntax is as follows::
  119. def sum_list(list_to_sum):
  120. """Here is some documentation in docstring.
  121. And here is the test::
  122. >>> sum_list([2, 5, 3])
  123. 10
  124. """
  125. In case of GRASS modules which are Python scripts, you can add something like
  126. this to your script::
  127. if __name__ == "__main__":
  128. if len(sys.argv) == 2 and sys.argv[1] == '--doctest':
  129. import doctest
  130. doctest.testmod()
  131. else:
  132. main()
  133. No output means that everything was successful. Note that you cannot use all
  134. the ways of running doctest since doctest will fail don the module file due
  135. to the dot or dots in the file name. Moreover, it is sometimes required that
  136. the file is accessible through sys.path which is not true for case of GRASS modules.
  137. However, do not use use doctest for tests of edge cases, for tests which require
  138. generate complex data first, etc. In these cases use `gunittest`.
  139. Data
  140. ----
  141. Most of the tests requires some input data. However, it is good to write
  142. a test in the way that it is independent on the available data.
  143. In case of GRASS, we have we can have tests of functions where
  144. some numbers or strings are input and some numbers or string are output.
  145. These tests does not require any data to be provided since the numbers
  146. can be part of the test. Then we have another category of tests, typically
  147. tests of GRASS modules, which require some maps to be on the input
  148. and thus the output (and test) depends on the specific data.
  149. Again, it it best to have tests which does not require any special data
  150. or generally environment settings (e.g. geographic projection)
  151. but it is much easier to write good tests with a given set of data.
  152. So, an compromises must be made and tests of different types should be written.
  153. In the GRASS testing framework, each test file should be marked according to
  154. category it belongs to. Each category corresponds to GRASS location or locations
  155. where the test file can run successfully.
  156. Universal tests
  157. First category is *universal*. The tests in this category use some some
  158. hard coded constants, generated data, random data, or their own imported
  159. data as in input to function and GRASS modules. All the tests, input data
  160. and reference results should be projection independent. These tests will
  161. runs always regardless of available locations.
  162. Standard names tests
  163. Second category are tests using *standard names*. Tests rely on a
  164. certain set of maps with particular names to be present in the location.
  165. Moreover, the tests can rely also on the (semantic) meaning of the
  166. names, i.e. raster map named elevation will always contain some kind of
  167. digital elevation model of some area, so raster map elevation can be
  168. used to compute aspect. In other words, these tests should be able to
  169. (successfully) run in any location with a maps named in the same way as
  170. in the standard testing location(s).
  171. Standard data tests
  172. Third category of tests rely on *standard data*. These tests expect that the
  173. GRASS location they run in not only contains the maps with particular names
  174. as in the *standard names* but the tests rely also on the data being the
  175. same as in the standard testing location(s). However, the (geographic)
  176. projection or data storage can be different. This is expected to be the
  177. most common case but it is much better if the tests is one of the previous
  178. categories (*universal* or *standard names*). If it is possible the
  179. functions or modules with tests in this category should have also tests
  180. which will fit into one of the previous categories, even though these
  181. additional tests will not be as precise as the other tests.
  182. Location specific tests
  183. Finally, there are tests which requires certain concrete location. There
  184. is (or will be) a set of standard testing locations each will have the same
  185. data (maps) but the projections and data storage types will be different.
  186. The suggested locations are: NC sample location in SPM projection,
  187. NC in SPF, NC in LL, NC in XY, and perhaps NC in UTM, and NC in some
  188. custom projection (in case of strange not-fitting projection, there is
  189. a danger that the results of analyses can differer significantly).
  190. Moreover, the set can be extened by GRASS locations which are using
  191. different storage backends, e.g. PostGIS for vectors and PostgreSQL for
  192. temporal database. Tests can specify one or preferably more of these
  193. standard locations.
  194. Specialized location tests
  195. Additionally, an specialized location with a collection of strange,
  196. incorrect, or generally extreme data will be provided. In theory, more
  197. than one location like this can be created if the data cannot be
  198. together in one location or if the location itself is somehow special,
  199. e.g. because of projection.
  200. Each category, or perhaps each location (will) have a set of external data
  201. available for import or other purposes. The standardization of this data
  202. is in question and thus this may be specific to each location or this
  203. can be a separate resource common to all tests using one of the standardized
  204. locations, or alternatively this data can be associated with the location
  205. with special data.
  206. .. note::
  207. The more general category you choose for your tests the more testing data
  208. can applied to your tests and the more different circumstances can be tried
  209. with your tests.
  210. .. note::
  211. gunittest is under development but, so some things can change, however
  212. this should not stop you from writing tests since the actual differences
  213. in your code will be only subtle.
  214. .. note::
  215. gunittest is not part of GRASS GIS source code yet, it is available
  216. separately. If you don't want to deal with some other code now,
  217. just write tests based only on Python ``unittest``. This will limit
  218. your possibilities of convenient testing but should not stop you
  219. from writing tests, especially if you will write tests of Python functions,
  220. and C functions exposed to Python through ctypes API. (Note that it might
  221. be a good idea to write tests for library function you rely on in your
  222. GRASS module).
  223. Analyzing quality of source code
  224. --------------------------------
  225. Besides testing, you can also use some tools to check the quality of your code
  226. according to various standards and occurrence of certain code patterns.
  227. For C/C++ code use third party solution `Coverity Scan`_ where GRASS GIS
  228. is registered as project number `1038`_. Also you can use `Cppcheck`_
  229. which will show a lot of errors which compilers do not check.
  230. In any case, set your compiler to high error and warning levels,
  231. check them and fix them in your code.
  232. For Python, we recommend pylint and then for style issues pep8 tool
  233. (and perhaps also pep257 tool). However, there is more tools available
  234. you can use them together with the recommend ones.
  235. To provide a way to evaluate the Python source code in the whole GRASS source
  236. tree there is a Python script ``grass_py_static_check.py`` which uses
  237. pylint and pep8 with GRASS-specific settings. Run the tool in GRASS session
  238. in the source code root directory. A HTML report will be created in
  239. ``pylint_report`` directory.
  240. ::
  241. grass_py_static_check.py
  242. .. note::
  243. ``grass_py_static_check.py`` is available in `sandbox`_.
  244. Additionally, if you are invoking your Python code manually using python command,
  245. e.g. when testing, use parameters::
  246. python -Qwarn -tt -3 some_module.py
  247. This will warn you about usage of old division semantics for integers
  248. and about incompatibilities with Python 3 (if you are using Python 2)
  249. which 2to3 tool cannot fix. Finally, it will issue errors if are using tabs
  250. for indentation inconsistently (note that you should not use tabs for
  251. indentation at all).
  252. Further reading
  253. ---------------
  254. .. toctree::
  255. :maxdepth: 2
  256. gunittest
  257. .. _unittest: https://docs.python.org/2/library/unittest.html
  258. .. _doctest: https://docs.python.org/2/library/doctest.html
  259. .. _Coverity Scan: https://scan.coverity.com/
  260. .. _1038: https://scan.coverity.com/projects/1038
  261. .. _Cppcheck: http://cppcheck.sourceforge.net/
  262. .. _sandbox: https://svn.osgeo.org/grass/sandbox/wenzeslaus/grass_py_static_check.py