Category: Courses

  • Unit Testing with ROS – ROS Debugging Tools

    Unit Testing with ROS: A 16-Week Self-Study Course

    Course Description:

    This comprehensive 4-month (16-week) self-study course is designed to equip motivated beginners and intermediate learners with the essential knowledge and practical skills to implement robust unit testing strategies within the Robot Operating System (ROS) environment. Through engaging lessons, clear explanations, and hands-on examples, you will learn to write effective unit tests for ROS nodes, services, topics, and actions, ensuring the reliability and maintainability of your robotics software. This course emphasizes practical application, guiding you from fundamental testing concepts to advanced testing techniques specific to ROS development, culminating in a final project that integrates all learned skills.

    Primary Learning Objectives:

    Upon successful completion of this course, you will be able to:

    • Understand the importance and benefits of unit testing in robotics software development.
    • Distinguish between different types of testing and their relevance to ROS applications.
    • Set up and configure a ROS workspace for unit testing.
    • Write effective unit tests for ROS nodes, publishers, subscribers, services, and actions using rostest and gtest.
    • Implement mocking and stubbing techniques for isolating ROS components during testing.
    • Utilize advanced testing tools and methodologies for complex ROS systems.
    • Debug and analyze unit test failures efficiently.
    • Integrate unit testing into a continuous integration (CI) workflow for ROS projects.

    Necessary Materials:

    • A computer running Ubuntu 20.04 LTS (Focal Fossa) or later.
    • ROS Noetic or ROS2 Foxy/Galactic installed and configured.
    • A C++ compiler (g++).
    • CMake.
    • Text editor or IDE (e.g., VS Code, Atom, CLion).
    • Internet connection for downloading packages and resources.
    • Basic familiarity with C++ and/or Python programming.
    • Basic understanding of ROS concepts (nodes, topics, messages).

    Course Content:

    Month 1: Foundations of Unit Testing & ROS Testing Basics

    Week 1: Introduction to Software Testing and Unit Testing Principles

    • Title: The Why and How of Unit Testing
    • Learning Objectives:

      Explain the importance of software testing in general and unit testing specifically.

      Differentiate between unit testing, integration testing, and system testing.

      Identify the key characteristics of a good unit test.

    • Key Vocabulary:
      • Unit Testing: A software testing method by which individual units or components of a software are tested.
      • Test-Driven Development (TDD): A software development process relying on the repetition of a very short development cycle: requirements are turned into specific test cases, then the software is improved to pass the new tests, only then is the new code released.
      • Test Case: A set of conditions under which a tester will determine whether an application, software system or one of its features is working as it was originally established for it to do.
      • Assertion: A predicate (a true-false statement) placed in a program to indicate that the developer thinks that the predicate is always true at that place.
    • Content:

      Unit testing is the cornerstone of robust and reliable software development, especially in complex systems like robotics. Imagine building a robot; if a single motor controller or sensor fails, the entire robot might behave unpredictably or even catastrophically. Unit testing helps us identify and fix these individual component failures early in the development cycle, long before they can cause larger issues.

      At its core, unit testing involves testing the smallest testable parts of an application, called “units,” in isolation from the rest of the code. A unit can be a function, a method, a class, or a module. The goal is to verify that each unit performs as expected, given a specific set of inputs.

      Consider the analogy of building a house. Before assembling the entire structure, you would want to ensure that each brick is solid, each wooden beam is sturdy, and each plumbing pipe is leak-free. Unit testing is like testing these individual building blocks before they are integrated into the larger system.

      This contrasts with integration testing, where multiple units are combined and tested as a group, and system testing, where the entire, fully integrated system is tested to verify it meets the specified requirements. While all forms of testing are crucial, unit testing provides the fastest feedback loop and helps in pinpointing bugs precisely to their source.

      The benefits of unit testing are numerous:

      1. Early Bug Detection: Bugs are cheaper and easier to fix when found early.
      2. Improved Code Quality: Writing tests often leads to better-designed, more modular, and loosely coupled code.
      3. Facilitates Refactoring: With a comprehensive test suite, you can confidently refactor your code, knowing that if you break something, your tests will alert you.
      4. Documentation: Tests serve as executable documentation, illustrating how the code is supposed to be used and what its expected behavior is.
      5. Faster Development Cycles: Although it seems counterintuitive initially, unit testing ultimately speeds up development by reducing time spent on debugging and manual testing.

      A good unit test should possess several characteristics:

      • Automated: It should run without manual intervention.
      • Independent: Each test should run independently of others.
      • Repeatable: Running the same test multiple times should yield the same result.
      • Fast: Tests should run quickly to encourage frequent execution.
      • Isolated: Tests should verify a single unit of code in isolation.

      In the context of robotics, unit testing is particularly vital due to the safety-critical nature of many applications. A bug in a navigation algorithm or a motor controller can have severe consequences. By thoroughly unit testing individual components, we build a strong foundation for the entire robotic system.

    • Practical Hands-on Example:
      • Task: Set up a basic C++ or Python project (not ROS-specific yet) and write your first unit test.
      • Instructions (C++ using Google Test):
        1. Create a directory for your project: mkdir my_first_tests && cd my_first_tests
        2. Create a src directory and a file calculator.h:
          // calculator.h\n#ifndef CALCULATOR_H\n#define CALCULATOR_H\n\nclass Calculator {\npublic:\n    int add(int a, int b) {\n        return a + b;\n    }\n    int subtract(int a, int b) {\n        return a - b;\n    }\n};\n\n#endif // CALCULATOR_H\n
        3. Create a tests directory and a file calculator_test.cpp:
          // calculator_test.cpp\n#include "gtest/gtest.h"\n#include "../src/calculator.h"\n\nTEST(CalculatorTest, Add) {\n    Calculator calc;\n    EXPECT_EQ(calc.add(2, 3), 5);\n    EXPECT_EQ(calc.add(-1, 1), 0);\n    EXPECT_EQ(calc.add(0, 0), 0);\n}\n\nTEST(CalculatorTest, Subtract) {\n    Calculator calc;\n    EXPECT_EQ(calc.subtract(5, 2), 3);\n    EXPECT_EQ(calc.subtract(10, 10), 0);\n    EXPECT_EQ(calc.subtract(0, 5), -5);\n}\n\nint main(int argc, char **argv) {\n    ::testing::InitGoogleTest(&argc, argv);\n    return RUN_ALL_TESTS();\n}\n
        4. Create a CMakeLists.txt in the root directory:
          cmake_minimum_required(VERSION 3.0.0)\nproject(my_first_tests)\n\n# Find Google Test\nfind_package(GTest CONFIG REQUIRED)\n\nadd_library(calculator src/calculator.h)\nadd_executable(calculator_test tests/calculator_test.cpp)\ntarget_link_libraries(calculator_test GTest::gtest_main)\n
        5. Build and run the tests:
          mkdir build && cd build\ncmake ..\nmake\n./calculator_test\n
      • Instructions (Python using unittest):
        1. Create a directory for your project: mkdir my_first_tests_py && cd my_first_tests_py
        2. Create a file calculator.py:
          # calculator.py\nclass Calculator:\n    def add(self, a, b):\n        return a + b\n\n    def subtract(self, a, b):\n        return