Documentation: kunit: provide guidance for testing many inputs

usage.rst goes into a detailed section about faking out classes, but
currently lacks wording about how one might idiomatically test a range
of inputs.

Add a new chapter for "Common Patterns" and group "Isolating behvaior"
and this new section under there.

Give an example of how one might test a hash function via macros/helper
funcs and a table-driven test and very briefly discuss pros and cons.

Also highlight the KUNIT_EXPECT_*_MSG() variants (that aren't mentioned
elsewhere [1]) which are particularly useful in these situations.

It is also criminally underused at the moment, only appearing in 2
tests (both written by people involved in KUnit).

[1] not even on
https://www.kernel.org/doc/html/latest/dev-tools/kunit/api/test.html

Signed-off-by: Daniel Latypov <dlatypov@google.com>
Reviewed-by: Brendan Higgins <brendanhiggins@google.com>
Signed-off-by: Shuah Khan <skhan@linuxfoundation.org>
This commit is contained in:
Daniel Latypov 2020-11-23 14:57:59 -08:00 committed by Shuah Khan
parent f3ed003e64
commit 1f0e943df6
1 changed files with 77 additions and 6 deletions

View File

@ -15,10 +15,10 @@ project, see :doc:`start`.
Organization of this document Organization of this document
============================= =============================
This document is organized into two main sections: Testing and Isolating This document is organized into two main sections: Testing and Common Patterns.
Behavior. The first covers what unit tests are and how to use KUnit to write The first covers what unit tests are and how to use KUnit to write them. The
them. The second covers how to use KUnit to isolate code and make it possible second covers common testing patterns, e.g. how to isolate code and make it
to unit test code that was otherwise un-unit-testable. possible to unit test code that was otherwise un-unit-testable.
Testing Testing
======= =======
@ -218,8 +218,11 @@ test was built in or not).
For more information on these types of things see the :doc:`api/test`. For more information on these types of things see the :doc:`api/test`.
Common Patterns
===============
Isolating Behavior Isolating Behavior
================== ------------------
The most important aspect of unit testing that other forms of testing do not The most important aspect of unit testing that other forms of testing do not
provide is the ability to limit the amount of code under test to a single unit. provide is the ability to limit the amount of code under test to a single unit.
@ -233,7 +236,7 @@ implementer, and architecture-specific functions which have definitions selected
at compile time. at compile time.
Classes Classes
------- ~~~~~~~
Classes are not a construct that is built into the C programming language; Classes are not a construct that is built into the C programming language;
however, it is an easily derived concept. Accordingly, pretty much every project however, it is an easily derived concept. Accordingly, pretty much every project
@ -451,6 +454,74 @@ We can now use it to test ``struct eeprom_buffer``:
destroy_eeprom_buffer(ctx->eeprom_buffer); destroy_eeprom_buffer(ctx->eeprom_buffer);
} }
Testing against multiple inputs
-------------------------------
Testing just a few inputs might not be enough to have confidence that the code
works correctly, e.g. for a hash function.
In such cases, it can be helpful to have a helper macro or function, e.g. this
fictitious example for ``sha1sum(1)``
.. code-block:: c
/* Note: the cast is to satisfy overly strict type-checking. */
#define TEST_SHA1(in, want) \
sha1sum(in, out); \
KUNIT_EXPECT_STREQ_MSG(test, (char *)out, want, "sha1sum(%s)", in);
char out[40];
TEST_SHA1("hello world", "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed");
TEST_SHA1("hello world!", "430ce34d020724ed75a196dfc2ad67c77772d169");
Note the use of ``KUNIT_EXPECT_STREQ_MSG`` to give more context when it fails
and make it easier to track down. (Yes, in this example, ``want`` is likely
going to be unique enough on its own).
The ``_MSG`` variants are even more useful when the same expectation is called
multiple times (in a loop or helper function) and thus the line number isn't
enough to identify what failed, like below.
In some cases, it can be helpful to write a *table-driven test* instead, e.g.
.. code-block:: c
int i;
char out[40];
struct sha1_test_case {
const char *str;
const char *sha1;
};
struct sha1_test_case cases[] = {
{
.str = "hello world",
.sha1 = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed",
},
{
.str = "hello world!",
.sha1 = "430ce34d020724ed75a196dfc2ad67c77772d169",
},
};
for (i = 0; i < ARRAY_SIZE(cases); ++i) {
sha1sum(cases[i].str, out);
KUNIT_EXPECT_STREQ_MSG(test, (char *)out, cases[i].sha1,
"sha1sum(%s)", cases[i].str);
}
There's more boilerplate involved, but it can:
* be more readable when there are multiple inputs/outputs thanks to field names,
* E.g. see ``fs/ext4/inode-test.c`` for an example of both.
* reduce duplication if test cases can be shared across multiple tests.
* E.g. if we wanted to also test ``sha256sum``, we could add a ``sha256``
field and reuse ``cases``.
.. _kunit-on-non-uml: .. _kunit-on-non-uml:
KUnit on non-UML architectures KUnit on non-UML architectures