| 62 | |
| 63 | == Design of testing API == |
| 64 | |
| 65 | {{{ |
| 66 | #!python |
| 67 | import unittest |
| 68 | import grass.pygrass.modules as gmodules |
| 69 | |
| 70 | # alternatively, these can be private to module with setter and getter |
| 71 | # or it can be in a class |
| 72 | USE_VALGRIND = False |
| 73 | |
| 74 | |
| 75 | class GrassTestCase(unittest.TestCase): |
| 76 | """Base class for GRASS test cases.""" |
| 77 | |
| 78 | def run_module(self, module): |
| 79 | """Method to run the module. It will probably use some class or instance variables""" |
| 80 | # get command from pygrass module |
| 81 | command = module.make_cmd() |
| 82 | # run command using valgrind if desired and module is not python script |
| 83 | if is_not_python_script(command[0]) and USE_VALGRIND: |
| 84 | command = ['valgrind', '--tool=...', '--xml=...', '--xml-file=...'] + command |
| 85 | # run command |
| 86 | # store valgrind output (memcheck has XML output to a file) |
| 87 | # store module return code, stdout and stderr, how to distinguish from valgrind? |
| 88 | # return code, stdout and stderr could be returned in tuple |
| 89 | |
| 90 | def assertRasterMap(self, actual, reference, msg=None): |
| 91 | # e.g. g.compare.md5 from addons |
| 92 | # uses msg if provided, generates its own if not, |
| 93 | # or both if self.longMessage is True (unittest.TestCase.longMessage) |
| 94 | # precision should be considered too (for FCELL and DCELL but perhaps also for CELL) |
| 95 | if check sums not equal: |
| 96 | self.fail(...) # unittest.TestCase.fail |
| 97 | }}} |
| 98 | |
| 99 | {{{ |
| 100 | #!python |
| 101 | class SomeModuleTestCase(GrassTestCase): |
| 102 | """Example of test case for a module.""" |
| 103 | |
| 104 | def test_flag_g(self): |
| 105 | """Test to validate the output of r.info using flag "g" |
| 106 | """ |
| 107 | # Configure a r.info test |
| 108 | module = gmodules.Module("r.info", map="test", flags="g", run_=False) |
| 109 | |
| 110 | self.run_module(module=module) |
| 111 | # it is not clear where to store stdout and stderr |
| 112 | self.assertStdout(actual=module.stdout, reference="r_info_g.ref") |
| 113 | |
| 114 | def test_something_complicated(self): |
| 115 | """Test something which has several outputs |
| 116 | """ |
| 117 | # Configure a r.info test |
| 118 | module = gmodules.Module("r.complex", rast="test", vect="test", flags="p", run_=False) |
| 119 | |
| 120 | (ret, stdout, stderr) = self.run_module(module=module) |
| 121 | self.assertEqual(ret, 0, "Module should have suceed but return code is not 0") |
| 122 | self.assertStdout(actual=stdout, reference="r_complex_stdout.ref") |
| 123 | self.assertRasterMap(actual=module.rast, reference="r_complex_rast.ref") |
| 124 | self.assertVectorMap(actual=module.vect, reference="r_complex_vect.ref") |
| 125 | }}} |
| 126 | |
| 127 | Compared to suggestion in ticket:2105#comment:4 it does not solve everything in `test_module` (`run_module`) function but it uses `self.assert*` similarly to `unittest.TestCase` which (syntactically) allows to check more then one thing. |
| 128 | |
| 129 | == Data types to be checked == |
| 130 | |
| 131 | We must deal especially with GRASS specific files such as raster maps. We consider that comparison of simple things such as strings and individual numbers is already implemented by [https://docs.python.org/2/library/unittest.html#test-cases unittest]. |
| 132 | |
| 133 | * raster map |
| 134 | * composite? reclassified map? |
| 135 | * color table included |
| 136 | * vector map |
| 137 | * 3D raster map |
| 138 | * color table |
| 139 | * SQL table |
| 140 | * file |
| 141 | |
| 142 | Most of the outputs can be checked with different numerical precision. |
| 143 | |
| 144 | Resources: |
| 145 | * http://grasswiki.osgeo.org/wiki/Test_Suite#Modules |