Okay, here is the abstract and summary for the Doctest presentation transcript.
Abstract:
Victor Kirov presents Doctest, a C++ testing framework heavily inspired by Catch2 and the testing styles of languages like Rust and Nim. The framework's core philosophy is that tests serve as documentation and should reside close to the production code they verify. Doctest differentiates itself by being extremely lightweight, focusing on minimal compile-time impact, offering the ability to completely remove test code from release binaries, and ensuring zero compiler warnings even on aggressive settings. The presentation details key features such as automatic test registration, expression-decomposing assertions, subcases for shared setup/teardown, templated test cases, exception handling utilities, and a rich command-line interface. Kirov delves into implementation techniques, including the use of __COUNTER__, workarounds for static initialization order, preprocessor tricks (_Pragma) for warning suppression, and template metaprogramming for assertion decomposition and exception translation. Extensive compile-time and runtime benchmarks are presented, comparing Doctest's various assertion types against Catch2 and highlighting significant performance advantages achieved through careful optimization. The talk concludes with usage patterns for integrating tests into production code (for applications and libraries), a discussion of development challenges, and a roadmap for future features like reporters, multi-process execution, and thread-safe assertions.
Doctest: A Lightweight C++ Testing Framework
00:00:08 Introduction: Victor Kirov introduces Doctest, a C++ testing framework.
00:00:44 Inspiration & Philosophy: Inspired by Rust/Nim's ability to write tests in production code and heavily influenced by the Catch2 framework. Its mantra is that tests should be a form of documentation located near the code they test.
00:01:01 Comparison with Catch2: Doctest is similar to Catch2 (single header) but currently lacks features like reporters and matchers, while offering test suites and templated test cases.
00:02:12 Single Header Usage: Explains the common single-header library pattern: include the header everywhere for the interface, but define an identifier (e.g., DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN) in one source file to compile the implementation.
00:03:06 Basic Example: Demonstrates a simple test case with an assertion, showing how failed assertions report the decomposed expression values.
00:03:45 Key Differentiators: Doctest prioritizes being lightweight, non-intrusive, having the smallest possible compile-time footprint, allowing complete removal of test code from binaries, avoiding namespace pollution, not including standard headers in its interface part, and generating zero warnings.
00:05:10 Benefits of Tests in Production Code: Lowers the barrier for writing tests, acts as up-to-date documentation, simplifies testing internal APIs, and aids test-driven development.
00:05:57 Core Features: Includes expression-decomposing assertion macros (e.g., CHECK) and automatic registration of test cases using the TEST_CASE macro.
00:06:24 Subcases: Provides SUBCASE for structuring tests and sharing setup/teardown code, similar to Catch2's SECTION. Test cases are re-entered for each subcase path.
00:06:51 Logging Macros: Offers macros like INFO, WARN, etc., for adding context to assertions, featuring lazy stringification (only evaluated on failure).
00:07:25 Customization: Users can teach Doctest how to translate custom exceptions and stringify custom types for better failure messages.
00:07:44 Templated Test Cases:TEST_CASE_TEMPLATE allows instantiating a test case over a list of types.
00:08:06 Exception Assertions: Provides CHECK_THROWS, CHECK_NOTHROW, etc.
00:08:12 Floating Point Comparison: Includes a utility class for approximate floating-point comparisons.
00:08:16 Decorators: Test cases can be decorated (e.g., skip, timeout).
00:08:41 Other Features: Crash handling (signals, SEH), debugger breaking on failure, rich command-line options for filtering, ordering, and listing tests. Supports parallel execution by running ranges of tests in separate processes.
00:09:33 Implementation: Unique Variables: Uses the non-standard but widely supported __COUNTER__ preprocessor macro to generate unique variable names internally.
00:10:09 Implementation: Auto Registration:TEST_CASE macro expands to create a function for the test body and a global dummy variable whose initialization calls a registration function (regTest) before main runs. static is used to prevent linker errors across files.
00:11:07 Implementation: Static Init Order Fix: Uses the "construct on first use" idiom (static variable inside a getter function) to ensure the test registry is initialized before any test case tries to register itself.
00:12:20 Implementation: Test Suites:TEST_SUITE macro uses anonymous namespaces to redefine a ts::get() function, allowing nested test cases to retrieve the current suite name during registration.
00:13:16 Implementation: Warning Suppression: Employs compiler-specific diagnostic push/pop pragmas around the header code and uses the _Pragma operator within macros to suppress warnings generated by the macro expansions themselves (like unused variables or global constructors).
00:17:22 Implementation: Subcases:SUBCASE macro expands into an if statement where a temporary object manages entry/exit logic based on the test runner's global state, controlling the re-entry behavior.
00:19:42 Integration with Production Code: Explains how to integrate Doctest when you have your own main function: create a Context, parse command-line arguments, optionally override options, call run(), and use shouldExit() to decide whether to proceed with the main application logic. Supports running tests only, app only, or both.
00:21:01 Removing Tests from Binary: Defining DOCTEST_CONFIG_DISABLE turns test cases into uninstantiated templates and assertion macros into no-ops (void(0)), effectively removing them from the compiled binary.
00:22:07 Implementation: Expression Decomposition: Uses template magic and operator overloading. An ExpressionDecomposer object captures the left operand via operator<<, returns a LeftOperand object which then captures the right operand via overloaded comparison operators (e.g., operator==), performs the comparison, and stringifies the operands upon failure.
00:25:08 Implementation: Exception Translation: Uses an ITranslator interface and a Translator<T> template class. Users register translators via REC_TRANSLATOR. When an exception occurs in an assert, the framework iterates registered translators, calling a virtual translate method which attempts to rethrow_exception and catch it as the specific type T.
00:28:40 Compile-Time Benchmarks (Header Inclusion): Doctest header inclusion is significantly faster than Catch2 (~20ms vs ~500ms) because Doctest's interface part includes almost no other headers, even forward-declaring std::ostream (though potentially non-standard).
00:31:52 Compile-Time Benchmarks (Assertions): Presents benchmarks for compiling many assertions. Doctest offers faster assertion macros (binary, fast binary, super fast) that trade features (like decomposition or try-catch blocks) for compile speed, significantly outperforming standard decomposing asserts.
00:36:04 Runtime Benchmarks: Shows Doctest assertion execution is very fast, often comparable to or faster than Catch2, especially after recent optimizations.
00:37:27 Runtime Optimizations: Key gains came from lazy stringification (avoiding string work if asserts pass) and implementing a custom string class with Small String Optimization (SSO). Marking functions noinline helped compile times without major runtime cost.
00:38:35 Usage Scenarios: Discusses how to ship products with tests (either disabled via DOCTEST_CONFIG_DISABLE or included but inactive by default) and how to manage tests in libraries (using ifdefs, prefixing/suiting test names, handling shared/static library linking issues).
00:41:44 Author's Usage: Shows a personal setup using a proxy header to configure Doctest (e.g., force fast asserts, undefine short macro names).
00:42:14 Development Challenges: Significant effort went into CI setup (many build configurations), achieving zero warnings, fixing obscure compiler issues (like strict-overflow warnings), and toolchain problems.
00:43:42 Future Roadmap: Plans include a reporter system (XML output), multi-process test execution, death tests (like Google Test), data generators, matchers, IDE integration adapters, and thread-safe assertions.
00:46:16 Q&A: Discusses potential collaboration with Catch team, roadmap prioritization based on user feedback, future mocking framework integration, dynamic test registration possibilities, and usage in dynamically loaded libraries.