|
| 1 | +# DR003 Test Granularity and Stubbing Challenges {#dr_003} |
| 2 | + |
| 3 | +## Context |
| 4 | + |
| 5 | +Our software development project encounters challenges in designing unit tests due to issues with test granularity and stubbing dependencies. |
| 6 | + |
| 7 | +Note that we structure our software into packages (see |
| 8 | +[Implementing a Plug-in Architecture](<\ref plugin_architecture> "Implementing a Plug-in Architecture")) |
| 9 | +which are technically implemented as [(private) libraries][3] (see |
| 10 | +[Root Directory for the Package's source files](<\ref dr_002> "Root Directory for the Package's source files") |
| 11 | +). |
| 12 | + |
| 13 | +[3]: https://docs.platformio.org/en/latest/projectconf/sections/platformio/options/directory/lib_dir.html#lib-dir "documentation of `lib_dir`" |
| 14 | + |
| 15 | +## Challenges with Stubbing |
| 16 | + |
| 17 | +Our initial assumption was to write unit tests for individual units. |
| 18 | +However, challenges arose, particularly regarding the granularity of "unit" tests envisioned. |
| 19 | + |
| 20 | +### Option 0: Testing individual units {#dr_003_o0} |
| 21 | + |
| 22 | +A unit in this context is, for example, a class or a translation unit. |
| 23 | +A test would be compiled along with the unit under test (UUT), linked together, and executed. |
| 24 | +The build configuration should be defined within the [`platformio.ini` configuration file][1]. |
| 25 | + |
| 26 | +A limitation of PlatformIO [has been experienced][7] specifically with test builds, where not only the UUT but the whole library in which it resides is included in the build (see [issue #4849][2]). |
| 27 | +This prevents stubbing dependencies of the UUT to other translation units within the same library, making it impossible to test individual units isolated from others this way. |
| 28 | + |
| 29 | +[1]: https://docs.platformio.org/en/latest/projectconf/#platformio-ini-project-configuration-file |
| 30 | +[2]: https://github.com/platformio/platformio-core/issues/4849 "Library Dependency Finder (LDF) adds too many files when testing (pio test)" |
| 31 | +[7]: https://community.platformio.org/t/partial-compilation-of-private-libraries-components-while-testing-for-different-environments/37079 "Partial compilation of private libraries/components (while testing) for different environments" |
| 32 | + |
| 33 | +### Option 1: Filter source files {#dr_003_o1} |
| 34 | + |
| 35 | +To overcome the limitations from |
| 36 | +[option 0](<\ref dr_003_o0> "option 0"), |
| 37 | +one can use a [`library.json`][JSON] manifest file for each library in combination with the [`extraScript` option][4] to filter files. |
| 38 | +This way, individual files could be filtered for specific build targets, although this approach is expected to be effortful, as filters have to be implemented for each individual unit. |
| 39 | + |
| 40 | +[JSON]: https://docs.platformio.org/en/latest/manifests/library-json/index.html "PlatformIO documentation of manifest file" |
| 41 | +[4]: https://docs.platformio.org/en/latest/manifests/library-json/fields/build/extrascript.html "documentation of `extraScript`" |
| 42 | + |
| 43 | +### Option 2: Split units into separate libraries {#dr_003_o2} |
| 44 | + |
| 45 | +Another way to circumvent the limitations from |
| 46 | +[option 0](<\ref dr_003_o0> "option 0") |
| 47 | +would be to separate each unit into an individual library. |
| 48 | +This way, only the UUT would be added to the test script for the test build. |
| 49 | +However, a major disadvantage would be that the planned structuring of the software packages into directories would be lost, making the analysis of the software structure more difficult. |
| 50 | + |
| 51 | +### Option 3: Test integrated units {#dr_003_o3} |
| 52 | + |
| 53 | +Test features using units and their dependencies to a certain degree: |
| 54 | + |
| 55 | +Either include (using [`lib_deps`][5] in [the project configuration][1]): |
| 56 | + |
| 57 | +- **Option 3a):** all dependencies compatible with the test environment |
| 58 | +- **Option 3b):** all dependencies compatible with the test environment, but no |
| 59 | + [3rd party library adapters](<\ref third_party_adapters> "3rd party library adapters") |
| 60 | +- **Option 3c):** only those dependencies which inevitably come as part of the library in which the UUT resides |
| 61 | + |
| 62 | +Omitted dependencies need to be substituted with stubs. |
| 63 | + |
| 64 | +[5]: https://docs.platformio.org/en/latest/projectconf/sections/env/options/library/lib_deps.html "documentation of `lib_deps`" |
| 65 | + |
| 66 | +## Comparison of Options {#dr_003_c1} |
| 67 | + |
| 68 | +- **Option 0:** Testing individual units without additional build configuration files |
| 69 | + - Pros: |
| 70 | + - would enable keeping the test design as originally envisioned |
| 71 | + - errors in one unit would not affect other units |
| 72 | + - Cons: |
| 73 | + - currently **impossible** to implement |
| 74 | +- **Option 1:** Testing individual units with filters defined in additional files |
| 75 | + - Pros: same as Option 0 |
| 76 | + - Cons: |
| 77 | + - implementation is **effortful and more complex** |
| 78 | +- **Option 2:** Testing individual units by separating them into individual libraries |
| 79 | + - Pros: same as Option 0 |
| 80 | + - Cons: |
| 81 | + - the structuring of the software into packages could not be represented by directories anymore |
| 82 | +- **Option 3:** Testing integrated units |
| 83 | + - Pros: |
| 84 | + - fewer individual tests necessary |
| 85 | + - less prone to changes (of details irrelevant for the test scope) |
| 86 | + - fits better with the **Common Closure Principle**: |
| 87 | + Tests are defined outside of the libraries but in a [dedicated test directory][6]. |
| 88 | + This is required by the build management tool currently used (PlatformIO). |
| 89 | + Interfaces of some units may change for other reasons than changes to the application requirements. |
| 90 | + For example when interfaces within a layer or to lower level layers change due to refactoring. |
| 91 | + These changes would require to adjust the tests. |
| 92 | + This means that the change in one layer would lead to a change in the "global" test directory. |
| 93 | + This effectively violates Common Closure Principle. |
| 94 | + - Cons: |
| 95 | + - to cover the same amount of control paths within the software with the integrated tests as with testing individual units, integrated test may require more complex or additional test cases. |
| 96 | + |
| 97 | +[6]: https://docs.platformio.org/en/stable/projectconf/sections/platformio/options/directory/test_dir.html "documentation of `test_dir`" |
| 98 | + |
| 99 | +### Comparing Options 3a)-3c) {#dr_003_c2} |
| 100 | + |
| 101 | +- **Option 3a):** Testing integrated units including all compatible dependencies |
| 102 | + - Pros: |
| 103 | + - highest test coverage of options 3a)-3c) |
| 104 | + - Cons: |
| 105 | + - requires stubs of the 3rd party dependencies incompatible with the test environment |
| 106 | +- **Option 3b):** Testing integrated units including all compatible dependencies, excluding 3rd party adapters |
| 107 | + - Pros: |
| 108 | + - Does not depend on the interface of the 3rd party dependencies incompatible with the test environment. |
| 109 | + - Cons: |
| 110 | + - requires stubs of all 3rd party adapters |
| 111 | +- **Option 3c):** Testing integrated units of the libraries each |
| 112 | + - Pros: |
| 113 | + - Does not depend on the interface of the 3rd party dependencies incompatible with the test environment. |
| 114 | + - Cons: |
| 115 | + - requires the highest amount of stubs from the options 3a)-3c) |
| 116 | + |
| 117 | +## Decision & Rationale {#dr_003_decision} |
| 118 | + |
| 119 | +What do we want to achieve with the tests? |
| 120 | +To check if the observable behavior of the overall software is as expected. |
| 121 | + |
| 122 | +As long as libraries are not delivered separately, it is not necessary to test them individually. |
| 123 | + |
| 124 | +After careful consideration, we've decided on: |
| 125 | + |
| 126 | +> **Option 3b):** Testing integrated units including all compatible dependencies, excluding 3rd party adapters |
| 127 | +
|
| 128 | +This decision aligns with the test objective, aiming to achieve good software quality while avoiding excessive effort. |
| 129 | + |
| 130 | +This design decision shall be regarded as a minimum requirement. |
| 131 | +Additional tests or stubbing may be defined at one's liberty. |
| 132 | + |
| 133 | +## Next Steps {#dr_003_next} |
| 134 | + |
| 135 | +To implement this decision: |
| 136 | + |
| 137 | +- Future tests are only required for the application layer. |
| 138 | + Units from lower layers will be tested indirectly. |
| 139 | +- Future tests may test several units at once. |
| 140 | +- Legacy tests that require modification must be checked if they satisfy the new test design and modified or removed accordingly. |
| 141 | + |
0 commit comments