You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* Apply feedback from Javi and Mich
* refactor TDD
* clarify and add exemple EXPECT vs ASSERT
* Address comments and try to solve the specific-ci problem
* document pre-commit in modules 1 and 6
* Minor modifications in guide
* Rename Module 1
* Minor change
---------
Co-authored-by: JesusSilvaUtrera <jesus.silva@ekumenlabs.com>
Co-authored-by: Xavier Ruiz <xavier.ruiz@ekumenlabs.com>
Copy file name to clipboardExpand all lines: README.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,16 +51,16 @@ By the end of the workshop, participants will be able to:
51
51
52
52
## 📋 Workshop structure
53
53
54
-
This workshop is organized into six modules that progressively develop the participant’s understanding of **testing in ROS 2**, from code quality fundamentals to complete Continuous Integration pipelines.
54
+
This workshop is organized into six modules that progressively develop the participant’s understanding of **testing in ROS 2**, from static analysis fundamentals to complete Continuous Integration pipelines.
55
55
56
56
Each module combines conceptual material with practical exercises that apply the ideas directly to real ROS 2 code. All exercises are designed to be executed in a consistent environment using the provided Docker setup.
57
57
58
58
> [!IMPORTANT]
59
59
> Before starting, build the Docker environment provided for this workshop. It includes all dependencies and tools required for the exercises. Follow the detailed instructions in the [Docker README](./docker/README.md).
Copy file name to clipboardExpand all lines: modules/module_2/README.md
+20-8Lines changed: 20 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -79,7 +79,19 @@ A few core concepts are especially useful:
79
79
- `EXPECT_*` records a failure but allows the test to continue.
80
80
- `ASSERT_*` aborts the test immediately on failure.
81
81
82
-
Use `EXPECT_*` for checks that can accumulate, and `ASSERT_*` when later steps would be meaningless if the check fails.
82
+
Consider testing that a function returns a `std::vector<int>` with the right length and expected contents:
83
+
84
+
```cpp
85
+
std::vector<int> vec = get_vector();
86
+
// If the length is wrong, further checks (indexing) would be invalid -> abort test
87
+
ASSERT_EQ(3u, vec.size()); // stop the test immediately if size != 3
88
+
// Now it is safe to check contents; these can be EXPECT so we see all mismatches at once
89
+
EXPECT_EQ(10, vec[0]);
90
+
EXPECT_EQ(20, vec[1]);
91
+
EXPECT_EQ(30, vec[2]);
92
+
```
93
+
94
+
Use `ASSERT_*` for preconditions that must hold for remaining assertions to make sense (avoid crashes and meaningless failures). Use `EXPECT_*` for value checks where continuing to run the test to collect multiple failures is useful
83
95
84
96
- **Fixtures**: allows code to be reused across multiple tests. Define a test class deriving from `::testing::Test` and use `TEST_F` instead of `TEST`.
85
97
- **Parameterized tests**: The same test logic can be executed against multiple input values with `TEST_P`. This reduces duplication and is especially helpful when validating algorithms across many corner cases
@@ -142,17 +154,17 @@ ROS 2 wraps GoogleTest/GoogleMock with lightweight CMake helpers so tests build
142
154
143
155
## How to Write Tests
144
156
145
-
Writing good unit tests is as much about structure as it is about logic. Two key concepts guide this process: **Test-Driven Development (TDD)** and the **Arrange-Act-Assert (AAA)** pattern.
146
-
147
-
**Test-Driven Development (TDD)** is an iterative approach where tests are written before the actual code. Each cycle begins by defining a small, failing test that expresses a desired behavior. The minimal code needed to make the test pass is then implemented, followed by a short refactoring step to clean up or generalize the design. This rhythm of red → green → refactor encourages clear requirements, modular code, and continuous verification.
148
-
149
-
The **AAA** pattern provides a simple mental model for structuring each test.
157
+
A good unit test is clear, concise, and focused. The best way to achieve this is by following the Arrange-Act-Assert (AAA) pattern, which provides a simple mental model for structuring each test:
150
158
151
159
- **Arrange**: prepare the environment, inputs, and objects needed for the test.
152
160
- **Act**: execute the function or behavior being tested.
153
161
- **Assert**: verify that the observed result matches the expected outcome.
154
162
155
-
Following this structure makes tests easy to read, maintain, and reason about. Each test should describe one behavior clearly, without hidden dependencies or side effects.
163
+
Following this pattern leads to tests that are consistent, self-explanatory, and easy to debug when they fail.
164
+
165
+
Beyond how tests are written, it’s also important to consider when they are written. This leads to a popular development workflow known as **Test-Driven Development (TDD)**. TDD follows an iterative approach where tests are written before the actual code. Each cycle begins by defining a small, failing test that expresses a desired behavior. The minimal code needed to make the test pass is then implemented, followed by a short refactoring step to clean up or generalize the design. This rhythm of red → green → refactor encourages clear requirements, modular code, and continuous verification.
166
+
167
+
While TDD helps drive better design decisions and encourages modular, testable architectures, the same testing principles can be applied in traditional “test-after” workflows. The key takeaway is that **testability should guide design**, regardless of whether tests come before or after the code.
156
168
157
169
## Exercises
158
170
@@ -187,4 +199,4 @@ The task is complete when tests are run and the output shows **0 errors** and **
187
199
- [Google Test Repo](https://github.com/google/googletestl)
188
200
- [Google Test Macros](https://google.github.io/googletest/reference/testing.html)
189
201
- [Google Test Assertions](https://google.github.io/googletest/reference/assertions.html)
The most common mistake in integration testing is writing a **flaky test**. A flaky test is one that passes sometimes and fails other times, even when no code has changed. This is almost always caused by a race condition.
108
+
109
+
Flaky (Bad) Test Logic:
110
+
111
+
1. Launch nodes.
112
+
2. Immediately publish a message (for example, on `/scan`).
113
+
3. Check for an expected result (for example, a log message).
114
+
115
+
**Why it fails**: The nodes in `generate_test_description` are launched, but they are not guaranteed to be ready or subscribed to their topics by the time the test case runs. The `ReadyToTest()` action only means the launch process is complete. If the test publishes its message before the node is subscribed, the message is dropped, and the test fails.
116
+
117
+
Reliable (Good) Test Logic:
118
+
119
+
1. Launch nodes.
120
+
2. In the test case, create the publisher.
121
+
3. Wait for the system to be ready. A simple, robust way is to wait until the publisher sees that a subscriber is connected.
122
+
4. Once the subscription has been confirmed, then publish the message.
123
+
5. Check for the expected result.
124
+
125
+
This event-driven approach is deterministic and eliminates the race condition.
126
+
104
127
### Alternative: launch_pytest
105
128
106
129
While using `launch_testing` with `unittest` is the classic approach used, support for more modern approaches like using `pytest` is also available. `Pytest` is a powerful and modern third party framework (`unittest` is part of the Python standard library) that has become the most used option for Python testing in the community. It is also gaining popularity within the ROS ecosystem.
@@ -130,7 +153,7 @@ Now, run the tests. This will fail because the test script is incomplete:
130
153
colcon test --packages-up-to module_4 --event-handlers console_direct+
131
154
```
132
155
133
-
The incomplete test script already handles launching the nodes. You need to fill in the logic inside the `unittest.TestCase` to verify its behavior.
156
+
The incomplete test script already handles launching the nodes. Fill in the logic inside the `unittest.TestCase` to verify its behavior.
Copy file name to clipboardExpand all lines: modules/module_5/README.md
+1-5Lines changed: 1 addition & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -33,12 +33,9 @@ Its purpose is to validate the robot's ability to meet a high-level requirement,
33
33
34
34
The importance of E2E testing in robotics includes:
35
35
36
-
<!-- TODO: Review this, and see if it matches more with field debugging -->
37
-
38
36
-**Validating the "Mission"**: It's the only test level that answers the question: "Does the robot actually achieve its goal?"
39
37
-**Testing Against Reality**: By using data recorded from the real world (or a high-fidelity simulator), rosbags provide a "ground truth" scenario. This makes possible to test complex, emergent behaviors and edge cases that are impossible to script in a simple integration test.
40
38
-**Ultimate Regression-Proofing**: An E2E test is the ultimate safety net. If a change in any package (perception, control, navigation) breaks the robot's ability to complete its mission, a good E2E test will catch it.
41
-
-**Debugging Complex Failures**: When a robot fails in the field, a rosbag of that failure is invaluable. It can be replayed in a simulator over and over until the root cause (for example, a race condition, a state machine logic error) is found.
42
39
43
40
## The rosbag Toolset
44
41
@@ -104,7 +101,7 @@ This command acts like a "data simulator", providing a perfectly repeatable stre
104
101
105
102
This is the most common and intuitive form of E2E testing. It involves a human operator launching the system, providing a scenario (usually via a rosbag), and visual or log-based verification of the result.
106
103
107
-
This is perfect for debugging, or for a final "sanity check" before merging a major feature.
104
+
This is perfect for a final "sanity check" before merging a major feature.
108
105
109
106
### Using Pre-recorded Rosbags
110
107
@@ -115,7 +112,6 @@ A typical manual test session looks like this:
115
112
3. Observe and Verify: The engineer watches the output:
116
113
- In `RViz`: "Does the robot's navigation visualization show it reaching the goal?"
117
114
- In the terminal: "Did the mission control node log 'MISSION_COMPLETE'?"
118
-
4. Analyze: If it fails, now it's possible to debug the running nodes, knowing the input data is identical every single time.
119
115
120
116
This workflow is incredibly powerful but has one major drawback: it's not automated. It relies on a human to launch, observe, and judge success.
Copy file name to clipboardExpand all lines: modules/module_6/README.md
+5Lines changed: 5 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,6 +48,11 @@ Integrating CI into the workflow is essential because it:
48
48
49
49
For projects hosted on GitHub, the easiest and most popular way to implement CI is with **GitHub Actions**.
50
50
51
+
> [!TIP]
52
+
> **Use Pre-commit Hooks to Optimize Your Workflow**
53
+
>
54
+
> While **CI is the mandatory quality gate** for merging, it's often faster for developers to catch simple style errors **locally** before they push. Tools like **pre-commit hooks** (as discussed in Module 1) run fast checks like formatting locally, saving the developer time waiting for the CI pipeline to run just to fail on a style inconsistency. They complement the CI by ensuring your commits are clean and focused on functional changes.
55
+
51
56
### Introduction to GitHub Actions
52
57
53
58
GitHub Actions is a CI/CD platform built directly into GitHub. Automation workflows are defined in a **YAML file** in a special directory in the repository: `.github/workflows/`. GitHub automatically detects these files and runs them based on a set of custom-defined rules or triggers, such as when code is pushed, a pull request is opened, or a scheduled job is due.
0 commit comments