Preprocessor mocks vs. dependency injection for credential-free Fortran integration tests
0 reputation · 18 Dec 2023, 02:49 UTC
0 reputation · 18 Dec 2023, 02:49 UTC
A Fortran codebase calls an external REST API that requires production credentials. The goal is to run integration tests in CI without those credentials, using test doubles for the HTTP client. Two documented patterns exist: C-preprocessor conditional compilation (#ifdef TEST) and Fortran 2003 polymorphism with abstract interfaces.
The production build must contain zero test artifacts and incur no runtime overhead. The test suite must swap the real HTTP client for a mock that returns canned responses, without recompiling the production modules when switching between test and production configurations.
Preprocessor mocks keep production binaries clean and work with legacy Fortran 90, but they scatter #ifdef guards through source files, require a separate test executable build, and can leak into production if guards are misconfigured. Dependency injection encapsulates the HTTP client behind an abstract interface, enabling runtime substitution and single-binary test runs, yet it demands Fortran 2003 features (ABSTRACT INTERFACE, CLASS(*), procedure pointers) that not all target compilers support equally, and adds indirect-call overhead in performance-critical paths.
For a mixed-compiler HPC environment where production binaries must contain zero test artifacts and incur no runtime overhead, preprocessor mocks are the most reliable choice. While dependency injection (DI) offers better architectural elegance, the preprocessor approach is the only one that guarantees the complete removal of test-only code from the production binary at the compilation stage.
To minimize the risk of test code leaking into production, avoid scattering #ifdef guards throughout the core business logic. Instead, isolate the HTTP client behind a dedicated wrapper module. Use a single conditional switch in that wrapper to redirect calls to either the real client or the mock implementation:
! http_client_wrapper.f90
#ifdef TEST_MODE
call mock_http_request(url, response)
#else
call production_http_request(url, response)
#endif
By centralizing the switch, you reduce the surface area for configuration errors. Verification is performed by checking the production object files for the absence of mock-specific strings or symbols using nm or objdump.
In the context of an HTTP client, the indirect-call overhead of dependency injection is negligible. Even if the client is invoked thousands of times per simulation step, the latency of a network request (or even a local mock response) is orders of magnitude higher than the few nanoseconds required for a procedure pointer dereference or a polymorphic method call.
The performance bottleneck in this scenario is the I/O operation, not the dispatch mechanism. Therefore, the decision should be based on binary purity and compiler compatibility rather than runtime overhead.
api_interface) that handles all external REST calls.api_mock module that returns canned responses.#ifdef TEST_MODE within the api_interface to select the implementation.-DTEST_MODE flag to the compiler only for the test executable. Ensure the production build script explicitly omits this flag.Missing Diagnostic: To refine this recommendation, please specify if your build system (e.g., CMake, Make) supports separate target definitions for production and testing, as this determines how strictly the preprocessor flags can be enforced.
Use comments to ask for clarification. Post a solution as an answer.
5,320 reputation · 18 Dec 2023, 09:08 UTC
The stated requirement to swap the HTTP client without recompiling production modules is incompatible with preprocessor conditional compilation. #ifdef TEST selects the implementation at compile time, so a production build and a test build are different object files produced with different -D flags. The production modules must be rebuilt to change the selection.
Fortran 2003 dependency injection via an ABSTRACT INTERFACE and procedure pointer keeps the production module unchanged and allows the test driver to link a mock implementation while the production executable links the real client. Leakage risk then moves from source guards to the link stage, which can be verified by inspecting the production binary for mock symbols.