|
Packit |
1470ea |
|
|
Packit |
1470ea |
<page xmlns="http://projectmallard.org/1.0/" xmlns:its="http://www.w3.org/2005/11/its" type="topic" id="unit-testing" xml:lang="de">
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<info>
|
|
Packit |
1470ea |
<link type="guide" xref="index#general-guidelines"/>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<credit type="author copyright">
|
|
Packit |
1470ea |
<name>Philip Withnall</name>
|
|
Packit |
1470ea |
<email its:translate="no">philip.withnall@collabora.co.uk</email>
|
|
Packit |
1470ea |
<years>2015</years>
|
|
Packit |
1470ea |
</credit>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<include xmlns="http://www.w3.org/2001/XInclude" href="cc-by-sa-3-0.xml"/>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<desc>Designing software to be tested and writing unit tests for it</desc>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright">
|
|
Packit |
1470ea |
<mal:name>Mario Blättermann</mal:name>
|
|
Packit |
1470ea |
<mal:email>mario.blaettermann@gmail.com</mal:email>
|
|
Packit |
1470ea |
<mal:years>2016</mal:years>
|
|
Packit |
1470ea |
</mal:credit>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<mal:credit xmlns:mal="http://projectmallard.org/1.0/" type="translator copyright">
|
|
Packit |
1470ea |
<mal:name>Christian Kirbach</mal:name>
|
|
Packit |
1470ea |
<mal:email>christian.kirbach@gmail.com</mal:email>
|
|
Packit |
1470ea |
<mal:years>2016</mal:years>
|
|
Packit |
1470ea |
</mal:credit>
|
|
Packit |
1470ea |
</info>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<title>Unit Testing</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<synopsis>
|
|
Packit |
1470ea |
<title>Zusammenfassung</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Unit testing should be the primary method of testing the bulk of code
|
|
Packit |
1470ea |
written, because a unit test can be written once and run many times —
|
|
Packit |
1470ea |
manual tests have to be planned once and then manually run each time.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Development of unit tests starts with the architecture and API design of
|
|
Packit |
1470ea |
the code to be tested: code should be designed to be easily testable, or
|
|
Packit |
1470ea |
will potentially be very difficult to test.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<list>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
Write unit tests to be as small as possible, but no smaller.
|
|
Packit |
1470ea |
(<link xref="#writing-unit-tests"/>)
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
Use code coverage tools to write tests to get high code coverage.
|
|
Packit |
1470ea |
(<link xref="#writing-unit-tests"/>)
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
Run all unit tests under Valgrind to check for leaks and other problems.
|
|
Packit |
1470ea |
(<link xref="#leak-checking"/>)
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
Use appropriate tools to automatically generate unit tests where
|
|
Packit |
1470ea |
possible. (<link xref="#test-generation"/>)
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
Design code to be testable from the beginning.
|
|
Packit |
1470ea |
(<link xref="#writing-testable-code"/>)
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
</list>
|
|
Packit |
1470ea |
</synopsis>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="writing-unit-tests">
|
|
Packit |
1470ea |
<title>Writing Unit Tests</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Unit tests should be written in conjunction with looking at
|
|
Packit |
1470ea |
<link xref="tooling#gcov-and-lcov">code coverage information gained from
|
|
Packit |
1470ea |
running the tests</link>. This typically means writing an initial set of
|
|
Packit |
1470ea |
unit tests, running them to get coverage data, then reworking and
|
|
Packit |
1470ea |
expanding them to increase the code coverage levels. Coverage should be
|
|
Packit |
1470ea |
increased first by ensuring all functions are covered (at least in part),
|
|
Packit |
1470ea |
and then by ensuring all lines of code are covered. By covering functions
|
|
Packit |
1470ea |
first, API problems which will prevent effective testing can be found
|
|
Packit |
1470ea |
quickly. These typically manifest as internal functions which cannot
|
|
Packit |
1470ea |
easily be called from unit tests. Overall, coverage levels of over 90%
|
|
Packit |
1470ea |
should be aimed for; don’t just test cases covered by project
|
|
Packit |
1470ea |
requirements, test everything.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Like <link xref="version-control">git commits</link>, each unit test
|
|
Packit |
1470ea |
should be ‘as small as possible, but no smaller’, testing a single
|
|
Packit |
1470ea |
specific API or behavior. Each test case must be able to be run
|
|
Packit |
1470ea |
individually, without depending on state from other test cases. This is
|
|
Packit |
1470ea |
important to allow debugging of a single failing test, without having to
|
|
Packit |
1470ea |
step through all the other test code as well. It also means that a single
|
|
Packit |
1470ea |
test failure can easily be traced back to a specific API, rather than a
|
|
Packit |
1470ea |
generic ‘unit tests failed somewhere’ message.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
GLib has support for unit testing with its
|
|
Packit |
1470ea |
<link href="https://developer.gnome.org/glib/stable/glib-Testing.html">GTest
|
|
Packit |
1470ea |
framework</link>, allowing tests to be arranged in groups and hierarchies.
|
|
Packit |
1470ea |
This means that groups of related tests can be run together for enhanced
|
|
Packit |
1470ea |
debugging too, by running the test binary with the <cmd>-p</cmd> argument:
|
|
Packit |
1470ea |
<cmd>./test-suite-name -p /path/to/test/group</cmd>.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="installed-tests">
|
|
Packit |
1470ea |
<title>Installierte Tests</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
All unit tests should be installed system-wide, following the
|
|
Packit |
1470ea |
<link href="https://wiki.gnome.org/Initiatives/GnomeGoals/InstalledTests">installed-tests
|
|
Packit |
1470ea |
standard</link>.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
By installing the unit tests, continuous integration (CI) is made easier,
|
|
Packit |
1470ea |
since tests for one project can be re-run after changes to other projects
|
|
Packit |
1470ea |
in the CI environment, thus testing the interfaces between modules. That
|
|
Packit |
1470ea |
is useful for a highly-coupled set of projects like GNOME.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
To add support for installed-tests, add the following to
|
|
Packit |
1470ea |
<file>configure.ac</file>:
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
# Installed tests
|
|
Packit |
1470ea |
AC_ARG_ENABLE([modular_tests],
|
|
Packit |
1470ea |
AS_HELP_STRING([--disable-modular-tests],
|
|
Packit |
1470ea |
[Disable build of test programs (default: no)]),,
|
|
Packit |
1470ea |
[enable_modular_tests=yes])
|
|
Packit |
1470ea |
AC_ARG_ENABLE([installed_tests],
|
|
Packit |
1470ea |
AS_HELP_STRING([--enable-installed-tests],
|
|
Packit |
1470ea |
[Install test programs (default: no)]),,
|
|
Packit |
1470ea |
[enable_installed_tests=no])
|
|
Packit |
1470ea |
AM_CONDITIONAL([BUILD_MODULAR_TESTS],
|
|
Packit |
1470ea |
[test "$enable_modular_tests" = "yes" ||
|
|
Packit |
1470ea |
test "$enable_installed_tests" = "yes"])
|
|
Packit |
1470ea |
AM_CONDITIONAL([BUILDOPT_INSTALL_TESTS],[test "$enable_installed_tests" = "yes"])
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Then in <file>tests/Makefile.am</file>:
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
insttestdir = $(libexecdir)/installed-tests/[project]
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
all_test_programs = \
|
|
Packit |
1470ea |
test-program1 \
|
|
Packit |
1470ea |
test-program2 \
|
|
Packit |
1470ea |
test-program3 \
|
|
Packit |
1470ea |
$(NULL)
|
|
Packit |
1470ea |
if BUILD_MODULAR_TESTS
|
|
Packit |
1470ea |
TESTS = $(all_test_programs)
|
|
Packit |
1470ea |
noinst_PROGRAMS = $(TESTS)
|
|
Packit |
1470ea |
endif
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
if BUILDOPT_INSTALL_TESTS
|
|
Packit |
1470ea |
insttest_PROGRAMS = $(all_test_programs)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
testmetadir = $(datadir)/installed-tests/[project]
|
|
Packit |
1470ea |
testmeta_DATA = $(all_test_programs:=.test)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
testdatadir = $(insttestdir)
|
|
Packit |
1470ea |
testdata_DATA = $(test_files)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
testdata_SCRIPTS = $(test_script_files)
|
|
Packit |
1470ea |
endif
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
EXTRA_DIST = $(test_files)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
%.test: % Makefile
|
|
Packit |
1470ea |
$(AM_V_GEN) (echo '[Test]' > $@.tmp; \
|
|
Packit |
1470ea |
echo 'Type=session' >> $@.tmp; \
|
|
Packit |
1470ea |
echo 'Exec=$(insttestdir)/$<' >> $@.tmp; \
|
|
Packit |
1470ea |
mv $@.tmp $@)
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="leak-checking">
|
|
Packit |
1470ea |
<title>Leak Checking</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Once unit tests with high code coverage have been written, they can be run
|
|
Packit |
1470ea |
under various dynamic analysis tools, such as
|
|
Packit |
1470ea |
<link xref="tooling#valgrind">Valgrind</link> to check for leaks,
|
|
Packit |
1470ea |
threading errors, allocation problems, etc. across the entire code base.
|
|
Packit |
1470ea |
The higher the code coverage of the unit tests, the more confidence the
|
|
Packit |
1470ea |
Valgrind results can be treated with. See <link xref="tooling"/> for more
|
|
Packit |
1470ea |
information, including build system integration instructions.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Critically, this means that unit tests should not leak memory or other
|
|
Packit |
1470ea |
resources themselves, and similarly should not have any threading
|
|
Packit |
1470ea |
problems. Any such problems would effectively be false positives in the
|
|
Packit |
1470ea |
analysis of the actual project code. (False positives which need to be
|
|
Packit |
1470ea |
fixed by fixing the unit tests.)
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="test-generation">
|
|
Packit |
1470ea |
<title>Test Generation</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Certain types of code are quite repetitive, and require a lot of unit
|
|
Packit |
1470ea |
tests to gain good coverage; but are appropriate for
|
|
Packit |
1470ea |
<link href="http://en.wikipedia.org/wiki/Test_data_generation">test data
|
|
Packit |
1470ea |
generation</link>, where a tool is used to automatically generate test
|
|
Packit |
1470ea |
vectors for the code. This can drastically reduce the time needed for
|
|
Packit |
1470ea |
writing unit tests, for code in these specific domains.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="json">
|
|
Packit |
1470ea |
<title>JSON</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
One example of a domain amenable to test data generation is parsing,
|
|
Packit |
1470ea |
where the data to be parsed is required to follow a strict schema — this
|
|
Packit |
1470ea |
is the case for XML and JSON documents. For JSON, a tool such as
|
|
Packit |
1470ea |
<link href="http://people.collabora.com/~pwith/walbottle/">Walbottle</link>
|
|
Packit |
1470ea |
can be used to generate test vectors for all types of valid and invalid
|
|
Packit |
1470ea |
input according to the schema.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Every type of JSON document should have a
|
|
Packit |
1470ea |
<link href="http://json-schema.org/">JSON Schema</link> defined for it,
|
|
Packit |
1470ea |
which can then be passed to Walbottle to generate test vectors:
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
json-schema-generate --valid-only schema.json
|
|
Packit |
1470ea |
json-schema-generate --invalid-only schema.json
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
These test vectors can then be passed to the code under test in its unit
|
|
Packit |
1470ea |
tests. The JSON instances generated by <cmd>--valid-only</cmd> should be
|
|
Packit |
1470ea |
accepted; those from <cmd>--invalid-only</cmd> should be rejected.
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="writing-testable-code">
|
|
Packit |
1470ea |
<title>Writing Testable Code</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
Code should be written with testability in mind from the design stage, as
|
|
Packit |
1470ea |
it affects API design and architecture in fundamental ways. A few key
|
|
Packit |
1470ea |
principles:
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<list>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
Do not use global state. Singleton objects are usually a bad idea as
|
|
Packit |
1470ea |
they can’t be instantiated separately or controlled in the unit tests.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
Separate out use of external state, such as databases, networking, or
|
|
Packit |
1470ea |
the file system. The unit tests can then replace the accesses to
|
|
Packit |
1470ea |
external state with mocked objects. A common approach to this is to use
|
|
Packit |
1470ea |
dependency injection to pass a file system wrapper object to the code
|
|
Packit |
1470ea |
under test. For example, a class should not load a global database (from
|
|
Packit |
1470ea |
a fixed location in the file system) because the unit tests would then
|
|
Packit |
1470ea |
potentially overwrite the running system’s copy of the database, and
|
|
Packit |
1470ea |
could never be executed in parallel. They should be passed an object
|
|
Packit |
1470ea |
which provides an interface to the database: in a production system,
|
|
Packit |
1470ea |
this would be a thin wrapper around the database API; for testing, it
|
|
Packit |
1470ea |
would be a mock object which checks the requests given to it and returns
|
|
Packit |
1470ea |
hard-coded responses for various tests.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
Expose utility functions where they might be generally useful.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
<item>
|
|
Packit |
1470ea |
Split projects up into collections of small, private libraries which are
|
|
Packit |
1470ea |
then linked together with a minimal amount of glue code into the overall
|
|
Packit |
1470ea |
executable. Each can be tested separately.
|
|
Packit |
1470ea |
</item>
|
|
Packit |
1470ea |
</list>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<section id="external-links">
|
|
Packit |
1470ea |
<title>Externe Links</title>
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
The topic of software testability is covered in the following articles:
|
|
Packit |
1470ea |
|
|
Packit |
1470ea |
<list>
|
|
Packit |
1470ea |
<item><link href="http://msdn.microsoft.com/en-us/magazine/dd263069.aspx">Design for testability</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://en.wikipedia.org/wiki/Software_testability">Software testability</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://en.wikipedia.org/wiki/Dependency_injection">Dependency injection</link> </item>
|
|
Packit |
1470ea |
<item><link href="http://c2.com/cgi/wiki?SoftwareDesignForTesting">Software design for testing</link> </item>
|
|
Packit |
1470ea |
</list>
|
|
Packit |
1470ea |
</section>
|
|
Packit |
1470ea |
</page>
|