A good set of tests is a critical requirement for modern software development, both to ensure the quality of the product and to help developers to quickly modify code without introducing bugs or causing regressions. The literature on software testing is vast; excellent tutorials and references are available both online and in print, see Literature Survey.
All developers are encouraged to review tests and ensure that any work is covered by both acceptance and unit tests.
- 1 Current Test Architecture
- 2 Policy
- 3 Writing Tests
- 4 Test Coverage Status
- 5 Known Test Needs
- 6 Literature Survey
Current Test Architecture
Acceptance tests in Gnucash are based on a home-grown (or anonymously sourced) set of macros and functions which can be found in src/test-core and src/engine/test-core. The quality, scope, and coverage of these tests varies greatly; some parts of GnuCash are tested lightly or not at all while others are tested fairly extensively. Although I've labelled them "acceptance tests", in many cases they're written more as unit tests whose scope is a single function rather than a whole module.
Unit testing, invented by Kent Beck in the early 1990s, seeks to test the public interface of classes as thoroughly as possible: All member functions should be tested with as much variation of their parameters as possible, with an emphasis on corner cases. Tests should avoid dependence on the implementation to avoid brittleness.
GnuCash has adopted the GLib testing framework to facilitate unit testing of GObject-based classes and GLib-dependent code. Muslim Choclov wrote unit tests for the most important modules in LibQOF as a GSoC2011 project. Work continues to get all of LibQOF and the engine fully tested to facilitate major architectural changes needed to make GnuCash a proper database application.
For C++ code GnuCash has adopted the Google Test Framework.
See also https://developer.gnome.org/glib/stable/gtester.html for the actual command line tool to run tests.
The collection of all implemented tests in gnucash is run by running
This can be run in any subdirectory to limit the number of tests. If a directory has both acceptance and unit tests, one can further limit the testing to the unit tests by running
which you'd run also if you're working on unit tests and need to recompile. Note that make test doesn't always work outside of the test directory itself. You can also run the unit tests in a directory with the test program, and restrict the tests run with the appropriate suite-name or even test case:
test-engine -p /engine/Account
will run only the Account tests. The test program is really an alias for gtester; read the man page for more details and options, e.g. the --verbose argument.
- All new non-GUI code should include thorough unit tests. Automated testing of the GUI tends to be brittle, so GUI modifications should be hand tested in as many OS environments as possible before being committed.
- make check run from the top build directory should pass before commits are pushed.
Unit Test Policies
- Getter/Setter functions which only set or retrieve an instance member variable do not need to be tested.
- Convenience functions which only wrap another function to change the function's name or to provide a default argument do not require testing.
- Composed functions, or functions which simply string together a series of calls to other functions, need not be tested if the called functions are all tested, have no side effects, and where the composed function has only one flow of control.
- There is some disagreement among testing gurus about whether a function's parameter variations should be exercised in a single test function or separately in a test function per function call. Use your judgement here. Remember that the dictum of Agile Development is to write a little bit at a time and to refactor as often as you need to. That applies as much to test code as it does to production code. It's OK to change your mind!
- Similarly there is tension among the gurus about how much to make a test program dependent upon, and how much to use mock objects to replace actual dependency code. Keeping in mind the goal of a short code-compile-test cycle, use your judgement. That said, at present much of Gnucash is rather interdependent and doesn't virtualize functions -- a requirement for applying mocks. If you're writing new modules, do use modern OO techniques to minimize interdependence, and where it's necessary make sure to use virtual functions so that linking the rest of Gnucash isn't needed to test your work.
GLib-test based tests
Read the GLib test documentation. To set up unit testing in a directory:
- Create a "test" directory if there isn't one already
- Create a Makefile.am in that test directory, again if there isn't one already.
- Add the line
in the Makefile.am and in every parent directory's Makefile.am that doesn't already have that line. If a parent directory has more than one subdirectory and not all of them have unit tests, create or add to the variable TEST_SUBDIRS.
- You will be adding one or more test programs. These are declared by adding their names to the TEST_PROGS variable, then defining the usual automake variables for each. For example:
TEST_PROGS += test-foo test-bar noinst_PROGRAMS = $(TEST_PROGS) test_foo_SOURCES = bar.c baz.c test-foo.c utest-baz.c test_foo_HEADERS = foo.h test_foo_LDADD = ../libgncmod-pepper.la (And so on, also for test_bar)
There is a unit test support module with some useful functions for controlling logging and signals in src/test-core. If you use it, add
to your test program's LDADD variable or add
to the SOURCES variable and
in your test-suite file. You have two options for the actual test programs. You can write a bunch of separate programs with a few tests each or you can group several files containing tests into a single program.
Many Little Programs
- Make a copy of test-templates/testmain.c for each program, renaming it appriopriately, in your test directory.
- Create fixtures and test functions and register the test functions in main(); there are comments in the file to guide you.
- Set up a target in Makefile.am as described above for each program.
A test suite is a collection of test functions registered in a test-suite function; main() runs the test-suite functions. This makes it easier to group tests into separate files with a master test program file to contain main(). We'll call the master test program source file the module file; it's conventionally named after the directory it's testing, e.g. test-engine.c. Normally you'll have a test-suite for each source file in the directory named utest-filename.c, e.g. utest-Split.c.
Note that as the C++ conversion progresses it may be necessary to compile some of these files as C++ in which case the extension will be .cpp instead of .c.
- Copy test-templates/test-module.c to your test directory, rename it, and create a target for it in Makefile.am.
test-templates/make-testfile "Your Name <email@example.com>" path/to/source
passing the path to the first source file you want to write tests for. This will create a template test file for you with all of the necessary functions prototyped and commented out and a populated test suite function.
- Add the file just created to the SOURCES of your test target in Makefile.am.
Google Test based tests
Test Coverage Status
Known Test Needs
- GtkAction callbacks referenced directly in GtkBuilder UI files need at a minimum "presence testing" so that make check will fail if the callbacks don't compile for some reason.
- Test Patterns: Refactoring Test Code The skeleton of this excellent manual for writing and improving unit tests is online; there is a pointer there for purchasing the book as well. While the book focuses on xUnit-style test frameworks (meaning jUnit and its many derivatives) most of the principles and patterns are applicable to any unit test code.