| 1 | Testing GRASS GIS source code and modules
|
|---|
| 2 | =========================================
|
|---|
| 3 |
|
|---|
| 4 | If you are already familiar with the basic concepts
|
|---|
| 5 | of GRASS testing framework, you might want to skip to one of:
|
|---|
| 6 |
|
|---|
| 7 | * :ref:`test-module` section
|
|---|
| 8 | * :ref:`test-c` section
|
|---|
| 9 | * :ref:`test-python` section
|
|---|
| 10 | * :ref:`test-doctest` section
|
|---|
| 11 | * :class:`~gunittest.case.TestCase` class
|
|---|
| 12 | * :ref:`running-tests-report` section
|
|---|
| 13 |
|
|---|
| 14 |
|
|---|
| 15 | Introduction
|
|---|
| 16 | ------------
|
|---|
| 17 |
|
|---|
| 18 | For the testing in GRASS GIS, we are using a `gunittest` package and
|
|---|
| 19 | we usually refer to the system of writing and running tests
|
|---|
| 20 | as to a *GRASS testing framework*.
|
|---|
| 21 |
|
|---|
| 22 | The framework is based on Python `unittest`_ package with a large number
|
|---|
| 23 | of GRASS-specific improvements, extensions and changes. These include
|
|---|
| 24 | things such as creation of GRASS-aware HTML test reports,
|
|---|
| 25 | or running of test in the way that process terminations potentially
|
|---|
| 26 | caused by C library functions does not influence the main testing process.
|
|---|
| 27 |
|
|---|
| 28 | Some tests will run without any data but many tests require
|
|---|
| 29 | the small (basic) version of GRASS GIS sample Location for North Carolina
|
|---|
| 30 | (see `GRASS GIS sample data`).
|
|---|
| 31 |
|
|---|
| 32 | Basic example
|
|---|
| 33 | -------------
|
|---|
| 34 |
|
|---|
| 35 | If you are writing a test of a GRASS module,
|
|---|
| 36 | create a Python script with the content derived from the example below.
|
|---|
| 37 | When using existing existing maps, suppose you are in North Carolina SPM
|
|---|
| 38 | GRASS sample location.
|
|---|
| 39 |
|
|---|
| 40 | The file can contain one or more test case classes. Each class
|
|---|
| 41 | can contain one or more test methods (functions).
|
|---|
| 42 | Here we create one test case class with one test method.
|
|---|
| 43 | The other two methods are class methods ensuring the right environment
|
|---|
| 44 | for all test methods inside a test case class.
|
|---|
| 45 | When a test file becomes part of source code (which is the usual case)
|
|---|
| 46 | it must be placed into a directory named ``testsuite``.
|
|---|
| 47 |
|
|---|
| 48 | .. code-block:: python
|
|---|
| 49 |
|
|---|
| 50 | from grass.gunittest.case import TestCase
|
|---|
| 51 | from grass.gunittest.main import test
|
|---|
| 52 |
|
|---|
| 53 |
|
|---|
| 54 | # test case class must be derived from grass.gunittest.TestCase
|
|---|
| 55 | class TestSlopeAspect(TestCase):
|
|---|
| 56 |
|
|---|
| 57 | @classmethod
|
|---|
| 58 | def setUpClass(cls):
|
|---|
| 59 | """Ensures expected computational region"""
|
|---|
| 60 | # to not override mapset's region (which might be used by other tests)
|
|---|
| 61 | cls.use_temp_region()
|
|---|
| 62 | # cls.runModule or self.runModule is used for general module calls
|
|---|
| 63 | cls.runModule('g.region', raster='elevation')
|
|---|
| 64 | # note that the region set by default for NC location is the same as
|
|---|
| 65 | # the elevation raster map, this is an example shows what to do
|
|---|
| 66 | # in the general case
|
|---|
| 67 |
|
|---|
| 68 | @classmethod
|
|---|
| 69 | def tearDownClass(cls):
|
|---|
| 70 | cls.del_temp_region()
|
|---|
| 71 |
|
|---|
| 72 | # test method must start with test_
|
|---|
| 73 | def test_limits(self):
|
|---|
| 74 | """Test that slope and aspect are in expected limits"""
|
|---|
| 75 | # we don't have to delete (g.remove) the maps
|
|---|
| 76 | # but we need to use unique names within one test file
|
|---|
| 77 | slope = 'limits_slope'
|
|---|
| 78 | aspect = 'limits_aspect'
|
|---|
| 79 | # self.assertModule is used to call module which we test
|
|---|
| 80 | # we expect module to finish successfully
|
|---|
| 81 | self.assertModule('r.slope.aspect', elevation='elevation',
|
|---|
| 82 | slope=slope, aspect=aspect)
|
|---|
| 83 | # function tests if map's min and max are within expected interval
|
|---|
| 84 | self.assertRasterMinMax(map=slope, refmin=0, refmax=90,
|
|---|
| 85 | msg="Slope in degrees must be between 0 and 90")
|
|---|
| 86 | self.assertRasterMinMax(map=aspect, refmin=0, refmax=360,
|
|---|
| 87 | msg="Aspect in degrees must be between 0 and 360")
|
|---|
| 88 |
|
|---|
| 89 |
|
|---|
| 90 | if __name__ == '__main__':
|
|---|
| 91 | test()
|
|---|
| 92 |
|
|---|
| 93 | In the example we have used only two assert methods, one to check that
|
|---|
| 94 | module runs and end successfully and the other to test that map values are
|
|---|
| 95 | within an expect interval. There is a much larger selection of assert methods
|
|---|
| 96 | in :class:`~gunittest.case.TestCase` class documentation
|
|---|
| 97 | and also in Python `unittest`_ package documentation.
|
|---|
| 98 |
|
|---|
| 99 | To run the test, run GRASS GIS, use NC SPM sample location and create
|
|---|
| 100 | a separate mapset (name it ``test`` for example). Then go to the directory
|
|---|
| 101 | with the test file and run it:
|
|---|
| 102 |
|
|---|
| 103 | .. code-block:: sh
|
|---|
| 104 |
|
|---|
| 105 | python some_test_file.py
|
|---|
| 106 |
|
|---|
| 107 | The output goes to the terminal in this case. Read further to see
|
|---|
| 108 | also more advanced ways of invoking the tests.
|
|---|
| 109 |
|
|---|
| 110 | We have shown a test of a GRASS module using NC sample location.
|
|---|
| 111 | However, tests can be written also for C and Python library and also
|
|---|
| 112 | for internal functions in modules. See the rests of this document
|
|---|
| 113 | for a complete guide.
|
|---|
| 114 |
|
|---|
| 115 |
|
|---|
| 116 | Building blocks and terminology
|
|---|
| 117 | -------------------------------
|
|---|
| 118 |
|
|---|
| 119 | .. note::
|
|---|
| 120 | Some parts of the terminology should be revised to ensure understanding and
|
|---|
| 121 | acceptance.
|
|---|
| 122 |
|
|---|
| 123 | test function and test method
|
|---|
| 124 | A *test function* is a test of one particular feature or a test of
|
|---|
| 125 | one particular result.
|
|---|
| 126 | A *test function* is referred to as *test method*, *individual test*
|
|---|
| 127 | or just *test*.
|
|---|
| 128 |
|
|---|
| 129 | assert function and assert method
|
|---|
| 130 | An *assert function* (or *assert method*) refers to a function
|
|---|
| 131 | which checks that some predicate is fulfilled. For example,
|
|---|
| 132 | predicate can be that two raster maps does not differ from each
|
|---|
| 133 | other or that module run ends with successfully.
|
|---|
| 134 |
|
|---|
| 135 | test case
|
|---|
| 136 | The test methods testing one particular topic or feature are in one
|
|---|
| 137 | test case class.
|
|---|
| 138 |
|
|---|
| 139 | From another point of view, one test case class contains all tests
|
|---|
| 140 | which require the same preparation and cleanup steps.
|
|---|
| 141 | In other words, a *test case* class contains all tests which are
|
|---|
| 142 | using the same *test fixture*.
|
|---|
| 143 |
|
|---|
| 144 | There is also a general :class:`~gunittest.case.TestCase` class which
|
|---|
| 145 | all concrete test case classes should inherit from to get all
|
|---|
| 146 | GRASS-specific testing functionality and also to be found
|
|---|
| 147 | by the testing framework.
|
|---|
| 148 |
|
|---|
| 149 | test suite
|
|---|
| 150 | A *test suite*, or also *testsuite*, is a set of tests focused on one
|
|---|
| 151 | topic, functionality or unit (similarly to test case).
|
|---|
| 152 | In GRASS GIS, it is a set of files in one ``testsuite`` directory.
|
|---|
| 153 | The test files in one ``testsuite``
|
|---|
| 154 | directory are expected to test what is in the parent directory
|
|---|
| 155 | of a given ``testsuite`` directory. This is used to organize
|
|---|
| 156 | tests in the source code and also to generate test reports.
|
|---|
| 157 |
|
|---|
| 158 | The term *test suite* may also refer to ``TestSuite`` class
|
|---|
| 159 | which is part of Python `unittest`_ test invocation mechanism
|
|---|
| 160 | used by `gunittest` internally.
|
|---|
| 161 |
|
|---|
| 162 | More generally, a *test suite* is a group of test cases or any tests
|
|---|
| 163 | (test methods, test cases and other test suites) in one or more files.
|
|---|
| 164 |
|
|---|
| 165 | test file
|
|---|
| 166 | A *test file* is a Python script executable as a standalone process.
|
|---|
| 167 | It does not set up any special environment and runs where it was invoked.
|
|---|
| 168 | The testing framework does not rely on the file to end in a standard
|
|---|
| 169 | way which means that if one file ends with segmentation fault
|
|---|
| 170 | the testing framework can continue in testing of other test files.
|
|---|
| 171 | Test files are central part `gunittest` system and are also the biggest
|
|---|
| 172 | difference from Python `unittest`_. Test file name should be unique
|
|---|
| 173 | but does not have to contain all parent directory names, for example
|
|---|
| 174 | it can consist from a simplified name of a module plus a word or two
|
|---|
| 175 | describing which functionality is tested. The name should not contain
|
|---|
| 176 | dots (except for the ``.py`` suffix).
|
|---|
| 177 |
|
|---|
| 178 | Alternatively, a test file could be called *test script* or
|
|---|
| 179 | *test module* (both in Python and GRASS sense) but note that
|
|---|
| 180 | none of these is used.
|
|---|
| 181 |
|
|---|
| 182 | test runner and test invoker
|
|---|
| 183 | Both *test runner* and *test invoker* refer to classes, functions or
|
|---|
| 184 | scripts used to run (invoke) tests or test files. One of the terms may
|
|---|
| 185 | fade of in the future (probably *invoke* because it is not used by
|
|---|
| 186 | Python `unittest`_).
|
|---|
| 187 |
|
|---|
| 188 | test fixture (test set up and tear down)
|
|---|
| 189 | The preparation of the test is called *setup* or *set up* and the cleaning
|
|---|
| 190 | after the test is called *teardown* or *tear down*. A *test fixture* refers
|
|---|
| 191 | to these two steps and also to the environment where the test or tests
|
|---|
| 192 | are executed.
|
|---|
| 193 |
|
|---|
| 194 | Each test case class can define ``setUp``, ``setUpClass``, ``tearDown``
|
|---|
| 195 | and ``tearDownClass`` methods to implement preparation and cleanup
|
|---|
| 196 | steps for tests it contains. The methods ending with ``Class`` are
|
|---|
| 197 | class methods (in Python terminology) and should be defined using
|
|---|
| 198 | ``@classmethod`` decorator and with ``cls`` as first argument. These
|
|---|
| 199 | methods are executed once for the whole class while the methods
|
|---|
| 200 | without ``Class`` are executed for each test method.
|
|---|
| 201 |
|
|---|
| 202 | In GRASS GIS, the preparation may, but does not have to, contain imports
|
|---|
| 203 | of maps, using temporary region, setting computational region,
|
|---|
| 204 | or generating random maps. The cleanup step should remove temporary
|
|---|
| 205 | region as well as remove all created maps and files.
|
|---|
| 206 |
|
|---|
| 207 | test report
|
|---|
| 208 | A *test report* is a document or set of documents with results of
|
|---|
| 209 | all executed tests together with additional information such as output
|
|---|
| 210 | of test.
|
|---|
| 211 |
|
|---|
| 212 | Note that also *test result* is used also used in similar context
|
|---|
| 213 | because the class responsible for representing or creating the report
|
|---|
| 214 | in Python `unittest`_ package is called ``TestResult``.
|
|---|
| 215 |
|
|---|
| 216 | test failure and test error
|
|---|
| 217 | A *test failure* occurs when a assert fails, e.g. value of
|
|---|
| 218 | a parameter given to ``assertTrue()`` function is ``False``.
|
|---|
| 219 | A *test error* occurs when something what is not tested fails,
|
|---|
| 220 | i.e. when exception is risen for example preparation code or
|
|---|
| 221 | a test method itself.
|
|---|
| 222 |
|
|---|
| 223 | .. _test-general:
|
|---|
| 224 |
|
|---|
| 225 | Testing with gunittest package in general
|
|---|
| 226 | -----------------------------------------
|
|---|
| 227 |
|
|---|
| 228 | The tests should be in files in a ``testsuite`` directory which is a subdirectory
|
|---|
| 229 | of the directory with tested files (module, package, library). Each test file
|
|---|
| 230 | (testing file) can have can have several test cases (testing classes).
|
|---|
| 231 | All test file names should have pattern ``test*.py`` or ``*.py``
|
|---|
| 232 | if another naming convention seems more appropriate.
|
|---|
| 233 |
|
|---|
| 234 | GRASS GIS `gunittest` package and testing framework is similar to the standard
|
|---|
| 235 | Python ``unittest`` package, so the ways to build tests are very similar.
|
|---|
| 236 | Test methods are in a test test case class and each test method tests one
|
|---|
| 237 | think using one or more assert methods.
|
|---|
| 238 |
|
|---|
| 239 | ::
|
|---|
| 240 |
|
|---|
| 241 | from grass.gunittest.case import TestCase
|
|---|
| 242 | from grass.gunittest.main import test
|
|---|
| 243 |
|
|---|
| 244 |
|
|---|
| 245 | class TestPython(TestCase):
|
|---|
| 246 |
|
|---|
| 247 | def test_counting(self):
|
|---|
| 248 | """Test that Python can count to two"""
|
|---|
| 249 | self.assertEqual(1 + 1, 2)
|
|---|
| 250 |
|
|---|
| 251 |
|
|---|
| 252 | if __name__ == '__main__':
|
|---|
| 253 | test()
|
|---|
| 254 |
|
|---|
| 255 | Each test file should be able to run by itself accept certain set of command
|
|---|
| 256 | line parameters (currently none). This is done using
|
|---|
| 257 | ``if __name__ == '__main__'`` and ``gunittest.test()`` function.
|
|---|
| 258 |
|
|---|
| 259 | To run a test file, start GRASS session in the location and mapset suitable for
|
|---|
| 260 | testing (typically, NC sample location) and go to the test file's directory
|
|---|
| 261 | (it will be usually some ``testsuite`` directory in the source code)
|
|---|
| 262 | and run it as a Python script::
|
|---|
| 263 |
|
|---|
| 264 | python test_something.py
|
|---|
| 265 |
|
|---|
| 266 | When running individual test files, it is advisable to be in a separate
|
|---|
| 267 | mapset, so for example when using NC sample location, you should use
|
|---|
| 268 | a new mapset of arbitrary name but not one of the predefined mapsets).
|
|---|
| 269 |
|
|---|
| 270 | To run all tests in the source tree, you have to be in the source code
|
|---|
| 271 | directory where you want to find tests, also you need to be inside
|
|---|
| 272 | a GRASS session and use command similar to this one::
|
|---|
| 273 |
|
|---|
| 274 | python -m grass.gunittest.main --location nc_spm_grass7 --location-type nc
|
|---|
| 275 |
|
|---|
| 276 | All test files in all ``testsuite`` directories will be executed and
|
|---|
| 277 | a report will be created in a newly created ``testreport`` directory.
|
|---|
| 278 | Open the file ``testreport/index.html`` to browse though the results.
|
|---|
| 279 | Note that again you need to be in GRASS session to run the tests in this way.
|
|---|
| 280 |
|
|---|
| 281 | The ``--location-type`` parameter serves to filter tests according to data
|
|---|
| 282 | they can run successfully with. It is ignored for tests which does not have
|
|---|
| 283 | this specified.
|
|---|
| 284 |
|
|---|
| 285 | In this case each running test file gets its own mapset and
|
|---|
| 286 | current working directory but all run are in one location.
|
|---|
| 287 |
|
|---|
| 288 | .. warning::
|
|---|
| 289 | The current location is ignored but you should not run tests
|
|---|
| 290 | in the location which is precious to you for the case that something fails
|
|---|
| 291 | and current location is used for tests.
|
|---|
| 292 |
|
|---|
| 293 | When your are writing tests you can rely on having maps which are present
|
|---|
| 294 | in the NC sample location, or you can generate random maps. You can also
|
|---|
| 295 | import your data which you store inside ``data`` directory inside the
|
|---|
| 296 | given ``testsuite`` directory (for maps, ASCII formats are usually used).
|
|---|
| 297 | If you can create tests independent on location projection and location data
|
|---|
| 298 | it is much better then relying on given data but it is not at all required
|
|---|
| 299 | and all approaches are encouraged.
|
|---|
| 300 |
|
|---|
| 301 | Whenever possible it is advantageous to use available assert methods.
|
|---|
| 302 | GRASS-specific assert methods are in :class:`gunittest.case.TestCase` class.
|
|---|
| 303 | For general assert methods refer to Python `unittest`_ package documentation.
|
|---|
| 304 | Both are used in the same way; they are methods of a given test case class.
|
|---|
| 305 | In cases (which should be rare) when no assert methods fits the purpose,
|
|---|
| 306 | you can use your own checking finalized with a call of ``assertTrue()``
|
|---|
| 307 | or ``assertFalse()`` method with the ``msg`` parameter parameter set
|
|---|
| 308 | to an informative message.
|
|---|
| 309 |
|
|---|
| 310 | When you are using multiple assert methods in one test method, you must
|
|---|
| 311 | carefully consider what assert methods are testing and in which order
|
|---|
| 312 | you should put them. Consider the following example::
|
|---|
| 313 |
|
|---|
| 314 | # incorrect order
|
|---|
| 315 | def test_map_in_list_wrong(self):
|
|---|
| 316 | maps = get_list_of_maps()
|
|---|
| 317 | self.assertIn('elevation', maps)
|
|---|
| 318 | # there is no point in testing that
|
|---|
| 319 | # if list (or string) was empty or None execution of test ended
|
|---|
| 320 | # at the line with assertIn
|
|---|
| 321 | self.assertTrue(maps)
|
|---|
| 322 |
|
|---|
| 323 | # correct order
|
|---|
| 324 | def test_map_in_list_correct(self):
|
|---|
| 325 | maps = get_list_of_maps()
|
|---|
| 326 | # see if list (or string) is not empty (or None)
|
|---|
| 327 | self.assertTrue(maps)
|
|---|
| 328 | # and then see if the list fulfills more advanced conditions
|
|---|
| 329 | self.assertIn('elevation', maps)
|
|---|
| 330 |
|
|---|
| 331 | If you are not sure when you would use multiple asserts consider the case
|
|---|
| 332 | when using only ``assertIn()`` function::
|
|---|
| 333 |
|
|---|
| 334 | def test_map_in_list_short(self):
|
|---|
| 335 | maps = get_list_of_maps()
|
|---|
| 336 | self.assertIn('elevation', maps)
|
|---|
| 337 |
|
|---|
| 338 | If the list (or string) is empty, the test fails and the message says
|
|---|
| 339 | something about ``elevation''`` not being in the ``maps`` but
|
|---|
| 340 | it might be much more useful if it would tell us that the list ``maps``
|
|---|
| 341 | does not contain any items. In case of ``maps`` being ``None``, the situation
|
|---|
| 342 | is more complicated since we using ``assertIn`` with ``None`` will
|
|---|
| 343 | cause test error (not only failure). We must consider what is
|
|---|
| 344 | expected behavior of ``get_list_of_maps()`` function and what
|
|---|
| 345 | we are actually testing. For example, if we would be testing function
|
|---|
| 346 | interface, we probably should test separately different possibilities
|
|---|
| 347 | using ``assertIsNotNone()`` and then ``assertTrue()`` and then anything else.
|
|---|
| 348 |
|
|---|
| 349 | Another reason for using multiple assert methods is that we may want to
|
|---|
| 350 | test different qualities of a result. Following the previous example,
|
|---|
| 351 | we can test that a list contains some map and does not contain some other.
|
|---|
| 352 | If you are testing a lot of things and they don't have any clear order
|
|---|
| 353 | or dependencies, it might be more advantageous to split
|
|---|
| 354 | testing into several testing methods and do the preparation (creating a list
|
|---|
| 355 | in our example) in ``setUpClass()`` or ``setUp()`` method.
|
|---|
| 356 |
|
|---|
| 357 |
|
|---|
| 358 | .. _test-module:
|
|---|
| 359 |
|
|---|
| 360 | Tests of GRASS modules
|
|---|
| 361 | ----------------------
|
|---|
| 362 |
|
|---|
| 363 | This is applicable for both GRASS modules written in C or C++ and
|
|---|
| 364 | GRASS modules written in Python since we are testing the whole module
|
|---|
| 365 | (which is invoked as a subprocess).
|
|---|
| 366 |
|
|---|
| 367 | ::
|
|---|
| 368 |
|
|---|
| 369 | def test_elevation(self):
|
|---|
| 370 | self.assertModule('r.info', map='elevation', flags='g')
|
|---|
| 371 | ...
|
|---|
| 372 |
|
|---|
| 373 | Use method ``assertRasterMinMax()`` to test that a result is within
|
|---|
| 374 | expected range. This is a very general test which checks the basic
|
|---|
| 375 | correctness of the result and can be used with different maps
|
|---|
| 376 | in different locations.
|
|---|
| 377 |
|
|---|
| 378 | ::
|
|---|
| 379 |
|
|---|
| 380 | def test_slope_limits(self):
|
|---|
| 381 | slope = 'limits_slope'
|
|---|
| 382 | self.assertModule('r.slope.aspect', elevation='elevation',
|
|---|
| 383 | slope=slope)
|
|---|
| 384 | self.assertRasterMinMax(map=slope, refmin=0, refmax=90,
|
|---|
| 385 | msg="Slope in degrees must be between 0 and 90")
|
|---|
| 386 |
|
|---|
| 387 | .. todo::
|
|---|
| 388 | Add example of assertions of key-value results.
|
|---|
| 389 |
|
|---|
| 390 | Especially if a module module has a lot of different parameters allowed
|
|---|
| 391 | in different combinations, you should test the if the wrong ones are really
|
|---|
| 392 | disallowed and proper error messages are provided (in addition, you can
|
|---|
| 393 | test things such as creation and removal of maps in error states).
|
|---|
| 394 |
|
|---|
| 395 | ::
|
|---|
| 396 |
|
|---|
| 397 | from grass.gunittest.gmodules import SimpleModule
|
|---|
| 398 |
|
|---|
| 399 | class TestRInfoParameterHandling(TestCase):
|
|---|
| 400 | """Test r.info handling of wrong input of parameters."""
|
|---|
| 401 |
|
|---|
| 402 | def test_rinfo_wrong_map(self):
|
|---|
| 403 | """Test input of map which does not exist."""
|
|---|
| 404 | map_name = 'does_not_exist'
|
|---|
| 405 | # create a module instance suitable for testing
|
|---|
| 406 | rinfo = SimpleModule('r.info', map=map_name, flags='g')
|
|---|
| 407 | # test that module fails (ends with non-zero return code)
|
|---|
| 408 | self.assertModuleFail(rinfo)
|
|---|
| 409 | # test that error output is not empty
|
|---|
| 410 | self.assertTrue(rinfo.outputs.stderr)
|
|---|
| 411 | # test that the right map is mentioned in the error message
|
|---|
| 412 | self.assertIn(map_name, stderr)
|
|---|
| 413 |
|
|---|
| 414 | In some cases it might be advantageous to create a module instance
|
|---|
| 415 | in `setUp()` method and then modify it in test methods.
|
|---|
| 416 |
|
|---|
| 417 | .. note:
|
|---|
| 418 | Test should be (natural) language, i.e. locale, independent
|
|---|
| 419 | to allow testing the functionality under different locale settings.
|
|---|
| 420 | So, if you are testing content of messages (which should be usually
|
|---|
| 421 | translated), use `assertIn()` method (regular expression might be
|
|---|
| 422 | applicable in some cases but in most cases `in` is exactly the
|
|---|
| 423 | operation needed).
|
|---|
| 424 |
|
|---|
| 425 | .. _test-c:
|
|---|
| 426 |
|
|---|
| 427 | Tests of C and C++ code
|
|---|
| 428 | -----------------------
|
|---|
| 429 |
|
|---|
| 430 | There are basically two possibilities how to test C and C++ code.
|
|---|
| 431 | If you are testing GRASS library code the functions which are part of API
|
|---|
| 432 | these functions are exposed through Python ``ctypes`` and thus can be tested
|
|---|
| 433 | in Python. See section :ref:`test-python` for reference.
|
|---|
| 434 |
|
|---|
| 435 | However, more advantageous and more preferable (although sometimes
|
|---|
| 436 | more complicated) solution is to write a special program, preferably
|
|---|
| 437 | GRASS module (i.e., using ``G_parser``). The dedicated program can
|
|---|
| 438 | provide more direct interface to C and C++ functions used by
|
|---|
| 439 | a GRASS module then the module and can also serve for doing benchmarks
|
|---|
| 440 | which are not part of the testing.
|
|---|
| 441 | This can approach can be applied to both
|
|---|
| 442 |
|
|---|
| 443 | See the example in ``lib/raster3d`` GRASS source code directory
|
|---|
| 444 | to create a proper Makefiles. A ``main()`` function should be written
|
|---|
| 445 | in the same way as for a standard module.
|
|---|
| 446 |
|
|---|
| 447 | Having a GRASS module for the purpose of testing you can write test
|
|---|
| 448 | as if it would be standard GRASS module.
|
|---|
| 449 |
|
|---|
| 450 |
|
|---|
| 451 | .. _test-python:
|
|---|
| 452 |
|
|---|
| 453 | Tests of Python code
|
|---|
| 454 | --------------------
|
|---|
| 455 |
|
|---|
| 456 | For testing of Python code contained in some package, use
|
|---|
| 457 | `gunittest` in the same way as `unittest`_ would be used.
|
|---|
| 458 | This basically means that if you will write tests of Python functions
|
|---|
| 459 | and C functions exposed to Python
|
|---|
| 460 | through ``ctypes`` API, you might want to focus more on `unittest`_
|
|---|
| 461 | documentation since you will perhaps need the more standard
|
|---|
| 462 | assert functions rather then the GRASS-specific ones.
|
|---|
| 463 |
|
|---|
| 464 |
|
|---|
| 465 | .. _test-doctest:
|
|---|
| 466 |
|
|---|
| 467 | Testing Python code with doctest
|
|---|
| 468 | --------------------------------
|
|---|
| 469 |
|
|---|
| 470 | .. note::
|
|---|
| 471 | The primary use of ``doctest`` is to ensure that the documentation
|
|---|
| 472 | for functions and classes is valid. Additionally, it can increase
|
|---|
| 473 | the number of tests when executed together with other tests.
|
|---|
| 474 |
|
|---|
| 475 | In Python, the easiest thing to test are functions which performs some
|
|---|
| 476 | computations or string manipulations, i.e. they have some numbers or strings
|
|---|
| 477 | on the input and some other numbers or strings on the output.
|
|---|
| 478 |
|
|---|
| 479 | At the beginning you can use doctest for this purpose. The syntax is as follows::
|
|---|
| 480 |
|
|---|
| 481 | def sum_list(list_to_sum):
|
|---|
| 482 | """Here is some documentation in docstring.
|
|---|
| 483 |
|
|---|
| 484 | And here is the test::
|
|---|
| 485 |
|
|---|
| 486 | >>> sum_list([2, 5, 3])
|
|---|
| 487 | 10
|
|---|
| 488 | """
|
|---|
| 489 |
|
|---|
| 490 | In case of GRASS modules which are Python scripts, you can add something like
|
|---|
| 491 | this to your script::
|
|---|
| 492 |
|
|---|
| 493 | if __name__ == "__main__":
|
|---|
| 494 | if len(sys.argv) == 2 and sys.argv[1] == '--doctest':
|
|---|
| 495 | import doctest
|
|---|
| 496 | doctest.testmod()
|
|---|
| 497 | else:
|
|---|
| 498 | main()
|
|---|
| 499 |
|
|---|
| 500 | No output means that everything was successful. Note that you cannot use all
|
|---|
| 501 | the ways of running doctest since doctest will fail don the module file due
|
|---|
| 502 | to the dot or dots in the file name. Moreover, it is sometimes required that
|
|---|
| 503 | the file is accessible through sys.path which is not true for case of GRASS modules.
|
|---|
| 504 |
|
|---|
| 505 | However, do not use use doctest for tests of edge cases, for tests which require
|
|---|
| 506 | generate complex data first, etc. In these cases use `gunittest`.
|
|---|
| 507 |
|
|---|
| 508 |
|
|---|
| 509 | .. _test-as-scripts:
|
|---|
| 510 |
|
|---|
| 511 | Tests as general scripts
|
|---|
| 512 | ------------------------
|
|---|
| 513 |
|
|---|
| 514 | GRASS testing framework supports also general Python or Shell scripts
|
|---|
| 515 | to be used as test files. This is strongly discouraged because it
|
|---|
| 516 | is not using standard ``gnunittest`` assert functions which only leads
|
|---|
| 517 | to reimplementing the functionality, relying on a person examining the data,
|
|---|
| 518 | or improper tests such as mere testing
|
|---|
| 519 | if the module executed without an error without looking at the actual results.
|
|---|
| 520 | Moreover, the testing framework does not have a control over what is
|
|---|
| 521 | executed and how which limits potential usage and features of testing
|
|---|
| 522 | framework. Doing this also prevents testing framework from creating a
|
|---|
| 523 | detailed report and thus better understanding of what is tested and what is
|
|---|
| 524 | failing. Shell scripts are also harder to execute on MS Windows where the
|
|---|
| 525 | interpreter might not be available or might not be on path.
|
|---|
| 526 |
|
|---|
| 527 | The testing framework uses Shell interpreter with ``-e`` flag when executing
|
|---|
| 528 | the tests, so the tests does not have to use ``set -e`` and can rely on it being
|
|---|
| 529 | set from outside. The flag ensures that if some command fails, i.e. ends with
|
|---|
| 530 | non-zero return code (exit status), the execution of the script ends too.
|
|---|
| 531 | The testing framework also uses ``-x`` flag to print the executed commands
|
|---|
| 532 | which usually makes examining of the test output easier.
|
|---|
| 533 |
|
|---|
| 534 | If multiple test files are executed using ``grass.gunittest.main`` module,
|
|---|
| 535 | the testing framework creates a temporary Mapset for the general Python and
|
|---|
| 536 | Shell scripts in the same way as it does for ``gunittest``-based test files.
|
|---|
| 537 | When the tests are executed separately, the clean up in current Mapset
|
|---|
| 538 | and current working directory must be ensured by the user or the script itself
|
|---|
| 539 | (which is generally true for all test files).
|
|---|
| 540 |
|
|---|
| 541 | .. warning::
|
|---|
| 542 | This is a bad practice which prevents creation of detailed reports and
|
|---|
| 543 | usage of advanced ``gunittest`` features, so you should avoid it
|
|---|
| 544 | whenever possible.
|
|---|
| 545 |
|
|---|
| 546 |
|
|---|
| 547 | Data
|
|---|
| 548 | ----
|
|---|
| 549 |
|
|---|
| 550 | .. note::
|
|---|
| 551 | Both the section and the practice itself are under development.
|
|---|
| 552 |
|
|---|
| 553 | Most of the tests requires some input data. However, it is good to write
|
|---|
| 554 | a test in the way that it is independent on the available data.
|
|---|
| 555 | In case of GRASS, we have we can have tests of functions where
|
|---|
| 556 | some numbers or strings are input and some numbers or string are output.
|
|---|
| 557 | These tests does not require any data to be provided since the numbers
|
|---|
| 558 | can be part of the test. Then we have another category of tests, typically
|
|---|
| 559 | tests of GRASS modules, which require some maps to be on the input
|
|---|
| 560 | and thus the output (and test) depends on the specific data.
|
|---|
| 561 | Again, it it best to have tests which does not require any special data
|
|---|
| 562 | or generally environment settings (e.g. geographic projection)
|
|---|
| 563 | but it is much easier to write good tests with a given set of data.
|
|---|
| 564 | So, an compromises must be made and tests of different types should be written.
|
|---|
| 565 |
|
|---|
| 566 | In the GRASS testing framework, each test file should be marked according to
|
|---|
| 567 | category it belongs to. Each category corresponds to GRASS location or locations
|
|---|
| 568 | where the test file can run successfully.
|
|---|
| 569 |
|
|---|
| 570 | Universal tests
|
|---|
| 571 | First category is *universal*. The tests in this category use some some
|
|---|
| 572 | hard coded constants, generated data, random data, or their own imported
|
|---|
| 573 | data as in input to function and GRASS modules. All the tests, input data
|
|---|
| 574 | and reference results should be projection independent. These tests will
|
|---|
| 575 | runs always regardless of available locations.
|
|---|
| 576 |
|
|---|
| 577 | Standard names tests
|
|---|
| 578 | Second category are tests using *standard names*. Tests rely on a
|
|---|
| 579 | certain set of maps with particular names to be present in the location.
|
|---|
| 580 | Moreover, the tests can rely also on the (semantic) meaning of the
|
|---|
| 581 | names, i.e. raster map named elevation will always contain some kind of
|
|---|
| 582 | digital elevation model of some area, so raster map elevation can be
|
|---|
| 583 | used to compute aspect. In other words, these tests should be able to
|
|---|
| 584 | (successfully) run in any location with a maps named in the same way as
|
|---|
| 585 | in the standard testing location(s).
|
|---|
| 586 |
|
|---|
| 587 | Standard data tests
|
|---|
| 588 | Third category of tests rely on *standard data*. These tests expect that the
|
|---|
| 589 | GRASS location they run in not only contains the maps with particular names
|
|---|
| 590 | as in the *standard names* but the tests rely also on the data being the
|
|---|
| 591 | same as in the standard testing location(s). However, the (geographic)
|
|---|
| 592 | projection or data storage can be different. This is expected to be the
|
|---|
| 593 | most common case but it is much better if the tests is one of the previous
|
|---|
| 594 | categories (*universal* or *standard names*). If it is possible the
|
|---|
| 595 | functions or modules with tests in this category should have also tests
|
|---|
| 596 | which will fit into one of the previous categories, even though these
|
|---|
| 597 | additional tests will not be as precise as the other tests.
|
|---|
| 598 |
|
|---|
| 599 | Location specific tests
|
|---|
| 600 | Finally, there are tests which requires certain concrete location. There
|
|---|
| 601 | is (or will be) a set of standard testing locations each will have the same
|
|---|
| 602 | data (maps) but the projections and data storage types will be different.
|
|---|
| 603 | The suggested locations are: NC sample location in SPM projection,
|
|---|
| 604 | NC in SPF, NC in LL, NC in XY, and perhaps NC in UTM, and NC in some
|
|---|
| 605 | custom projection (in case of strange not-fitting projection, there is
|
|---|
| 606 | a danger that the results of analyses can differer significantly).
|
|---|
| 607 | Moreover, the set can be extended by GRASS locations which are using
|
|---|
| 608 | different storage backends, e.g. PostGIS for vectors and PostgreSQL for
|
|---|
| 609 | temporal database. Tests can specify one or preferably more of these
|
|---|
| 610 | standard locations.
|
|---|
| 611 |
|
|---|
| 612 | Specialized location tests
|
|---|
| 613 | Additionally, an specialized location with a collection of strange,
|
|---|
| 614 | incorrect, or generally extreme data will be provided. In theory, more
|
|---|
| 615 | than one location like this can be created if the data cannot be
|
|---|
| 616 | together in one location or if the location itself is somehow special,
|
|---|
| 617 | e.g. because of projection.
|
|---|
| 618 |
|
|---|
| 619 | Each category, or perhaps each location (will) have a set of external data
|
|---|
| 620 | available for import or other purposes. The standardization of this data
|
|---|
| 621 | is in question and thus this may be specific to each location or this
|
|---|
| 622 | can be a separate resource common to all tests using one of the standardized
|
|---|
| 623 | locations, or alternatively this data can be associated with the location
|
|---|
| 624 | with special data.
|
|---|
| 625 |
|
|---|
| 626 | .. note::
|
|---|
| 627 | The more general category you choose for your tests the more testing data
|
|---|
| 628 | can applied to your tests and the more different circumstances can be tried
|
|---|
| 629 | with your tests.
|
|---|
| 630 |
|
|---|
| 631 |
|
|---|
| 632 | Data specific to one test
|
|---|
| 633 | -------------------------
|
|---|
| 634 |
|
|---|
| 635 | If the data required by the test are not part of standard location
|
|---|
| 636 | and cannot be part of the test file itself, this data should be stored
|
|---|
| 637 | in files in ``data`` subdirectory of ``testsuite`` directory.
|
|---|
| 638 | The test should access the data using a relative path from its location,
|
|---|
| 639 | i.e. all data will be accessed using ``data/...``. This ``data`` directory
|
|---|
| 640 | might be used directly when running test file directly in the directory
|
|---|
| 641 | in the source code or might be copied to the test current working directory
|
|---|
| 642 | when running tests by the main test invoking tool.
|
|---|
| 643 |
|
|---|
| 644 |
|
|---|
| 645 | Tests creating separate Mapsets, Locations and GRASS Databases
|
|---|
| 646 | --------------------------------------------------------------
|
|---|
| 647 |
|
|---|
| 648 | If test is creating a custom Mapset or Mapsets, it can create them in
|
|---|
| 649 | the current Location or create a custom GRASS Database in the current
|
|---|
| 650 | directory. In any case, test has to take care of cleaning up (deleting)
|
|---|
| 651 | the created directories and it has to use names which will be unique
|
|---|
| 652 | enough (name of the test case class or the file is probably a good choice
|
|---|
| 653 | but completely unique identifier is probably much better).
|
|---|
| 654 |
|
|---|
| 655 | If test needs custom Location or it tests something related to GRASS Database,
|
|---|
| 656 | it must always create a new GRASS Database in the current directory.
|
|---|
| 657 |
|
|---|
| 658 | In any case, the author must try the tests cautiously and several times
|
|---|
| 659 | in the same Location to see if everything works as expected. Testing
|
|---|
| 660 | framework is using Mapsets to separate the tests and the functions
|
|---|
| 661 | does not explicitly check for the case where a test is using different
|
|---|
| 662 | Mapset then the one which has been given to it by the framework.
|
|---|
| 663 |
|
|---|
| 664 |
|
|---|
| 665 | Analyzing quality of source code
|
|---|
| 666 | --------------------------------
|
|---|
| 667 |
|
|---|
| 668 | Besides testing, you can also use some tools to check the quality of your code
|
|---|
| 669 | according to various standards and occurrence of certain code patterns.
|
|---|
| 670 |
|
|---|
| 671 | For C/C++ code use third party solution `Coverity Scan`_ where GRASS GIS
|
|---|
| 672 | is registered as project number `1038`_. Also you can use `Cppcheck`_
|
|---|
| 673 | which will show a lot of errors which compilers do not check.
|
|---|
| 674 | In any case, set your compiler to high error and warning levels,
|
|---|
| 675 | check them and fix them in your code. Furthermore, `Travis-CI`_ is used
|
|---|
| 676 | to check if the source code can still be compiled after submitting changes
|
|---|
| 677 | to the repository.
|
|---|
| 678 |
|
|---|
| 679 | For Python, we recommend pylint and then for style issues pep8 tool
|
|---|
| 680 | (and perhaps also pep257 tool). However, there is more tools available
|
|---|
| 681 | you can use them together with the recommend ones.
|
|---|
| 682 |
|
|---|
| 683 | To provide a way to evaluate the Python source code in the whole GRASS source
|
|---|
| 684 | tree there is a Python script ``grass_py_static_check.py`` which uses
|
|---|
| 685 | pylint and pep8 with GRASS-specific settings. Run the tool in GRASS session
|
|---|
| 686 | in the source code root directory. A HTML report will be created in
|
|---|
| 687 | ``pylint_report`` directory.
|
|---|
| 688 |
|
|---|
| 689 | ::
|
|---|
| 690 |
|
|---|
| 691 | grass_py_static_check.py
|
|---|
| 692 |
|
|---|
| 693 | .. note::
|
|---|
| 694 | ``grass_py_static_check.py`` is available in `sandbox`_.
|
|---|
| 695 |
|
|---|
| 696 | Additionally, if you are invoking your Python code manually using python command,
|
|---|
| 697 | e.g. when testing, use parameters::
|
|---|
| 698 |
|
|---|
| 699 | python -Qwarn -tt -3 some_module.py
|
|---|
| 700 |
|
|---|
| 701 | This will warn you about usage of old division semantics for integers
|
|---|
| 702 | and about incompatibilities with Python 3 (if you are using Python 2)
|
|---|
| 703 | which 2to3 tool cannot fix. Finally, it will issue errors if are using tabs
|
|---|
| 704 | for indentation inconsistently (note that you should not use tabs for
|
|---|
| 705 | indentation at all).
|
|---|
| 706 |
|
|---|
| 707 |
|
|---|
| 708 | Further reading
|
|---|
| 709 | ---------------
|
|---|
| 710 |
|
|---|
| 711 | .. toctree::
|
|---|
| 712 | :maxdepth: 2
|
|---|
| 713 |
|
|---|
| 714 | gunittest
|
|---|
| 715 | gunittest_running_tests
|
|---|
| 716 |
|
|---|
| 717 |
|
|---|
| 718 | .. _unittest: https://docs.python.org/2/library/unittest.html
|
|---|
| 719 | .. _doctest: https://docs.python.org/2/library/doctest.html
|
|---|
| 720 | .. _Coverity Scan: https://scan.coverity.com/
|
|---|
| 721 | .. _Travis-CI: https://travis-ci.org/GRASS-GIS/grass-ci
|
|---|
| 722 | .. _1038: https://scan.coverity.com/projects/1038
|
|---|
| 723 | .. _Cppcheck: http://cppcheck.sourceforge.net/
|
|---|
| 724 | .. _sandbox: https://svn.osgeo.org/grass/sandbox/wenzeslaus/grass_py_static_check.py
|
|---|
| 725 | .. _GRASS GIS sample data: https://grass.osgeo.org/download/sample-data and http://fatra.cnr.ncsu.edu/data/ (nc_spm_full_v2alpha)
|
|---|