# 单元测试

我们鼓励开发人员在开发的每个模块时编写单元测试,包括添加新功能,修复错误和重构。

或者,也可以直接从 bash 运行完整的单元测试:

  1. Unit tests with Google Test (opens new window) ("GTest") - tests that have minimal, internal-only dependencies
  2. test_[description].cpp 中包括基本 unittest-class<unit_test.h> 以及为新功能编写测试所需的所有文件。
  3. 软件在环(SITL)单元测试。 这些测试需要运行在完整的SITL环境中, 运行起来更慢,更难调试,所以建议尽可能使用GTest代替。

# 编写测试

要查看 px4 shell 中可用测试的完整列表,请执行以下操作:

创建新的单元测试步骤如下:

  1. 单元测试分成三个部分:设置、运行、检查结果。 每个单元测试都应该测试一个特定行为或设置案例,如果测试失败,则很明显你的测试代码有错误。 请尽可能遵循这些标准。
  2. Copy and rename the example unit test AttitudeControlTest (opens new window) to the directory the code to be tested is in.
  3. 将新文件到该目录的CMakeLists.txt文件中。 文件看起来像px4_add_unit_gtest(SRC MyNewUnitTest.cpp LINKLIBS <library_to_be_tested>)
  4. 添加你想要的测试功能。 这包括了添加所需的头文件、新测试(每个测试都应该有单独的名称),并加入相关逻辑,运行测试代码并验证其行为是否符合预期。
  5. 如果需要添加新的依赖库,只要在如上所说的CMakeLists文件中LINKLIBS后面加入库的名字。

可以通用 make tests命令来运行所有测试,然后在 build/px4_sitl_test/unit-MyNewUnit目录中找到二进行制文件。 也可以直接通过调试器中运行。

# 写一个GTest功能测试

当测试或测试的组件依赖参数、uORB 消息、或更高级的GTest功能的时候,应当使用GTest功能测试。 Additionally, functional tests can contain local usage of STL data structures (although be careful of platform differences between e.g. macOS and Linux).

创建一个新的功能测试步骤如下:

  1. 一般来说(与单元测试类似)功能测试应分为三个部分:设置,运行,检查结果。 每个单元测试都应该测试一个特定行为或设置案例,如果测试失败,则很明显你的测试代码有错误。 请尽可能遵循这些标准。
  2. Copy and rename the example functional test ParameterTest (opens new window) to the directory the code to be tested is in.
  3. 将ParameterTest 重命名为更符合你正在测试的代码功能。
  4. 将新文件到该目录的CMakeLists.txt文件中。 文件内容看起来像 px4_add_functional_gtest(SRC MyNewFunctionalTest.cpp LINKLIBS <library_to_be_tested>)
  5. 添加你想要的测试功能。 这包括了,添加特定的头文件、新测试(每个测试都应该使用不同的命名),并设置相关逻辑,运行测试代码并验证是否符合预期。
  6. 如果需要添加新的依赖库,只要在如上所说的CMakeLists文件中LINKLIBS后面加入库的名字。

可以通用make tests命令来运行所有测试,然后在 build/px4_sitl_test/functional-MyNewFunctional目录中找到二进行制文件。 也可以直接通过调试器中运行。 It can be run directly in a debugger, however be careful to only run one test per executable invocation using the --gtest_filter=<regex> (opens new window) arguments, as some parts of the uORB and parameter libraries don't clean themselves up perfectly and may result in undefined behavior if set up multiple times.

# 写一个软件在环(SITL)单元测试

当需要所有的飞行控制组件:驱动、时间或者更多时,应该SITL单元测试。 这些测试运行较慢(每个模块至少1秒+),同时难以测试,所以仅在必要时使用它们。

创建一个新的SITL单元测试步骤如下:

  1. Examine the sample Unittest-class (opens new window).

  2. Create a new .cpp file within tests (opens new window) with name test_[description].cpp.

  3. test_ [description].cpp 中,实现各种测试。

  4. test_ [description].cpp 中实现 run_tests() 方法,其中将运行每个测试[1,2,...]。

  5. [Description]Test 类中,声明公共方法 virtual bool run_tests()

  6. [Description]Test 类中,声明测试相关特征所需的所有私有方法(test1()test2(),...)。

  7. test_ [description].cpp 的底部声明测试。

  8. test_ [description].cpp 中,实现各种测试。

  9. test_ [description].cpp 的底部声明测试。

    ut_declare_test_c(test_[description], [Description]Test)
    

    下面是一个模板:

    #include <unit_test.h>
    #include "[new feature].h"
    ...
    
    #include <unit_test.h>
     #include "[new feature].h"
     ...
    
     class [Description]Test : public UnitTest
     {
     public:
        virtual bool run_tests();
    
     private:
        bool test1();
        bool test2();
        ...
     };
    
     bool [Description]Test::run_tests()
     {
        ut_run_test(test1)
        ut_run_test(test2)
        ...
    
        return (_tests_failed == 0);
     }
    
     bool [Description]Test::test1()
     {
        ut_[name of one of the unit test functions](...
        ut_[name of one of the unit test functions](...
        ...
    
        return true;
     }
    
     bool [Description]Test::test2()
     {
        ut_[name of one of the unit test functions](...
        ut_[name of one of the unit test functions](...
        ...
    
        return true;
     }
     ...
    
     ut_declare_test_c(test_[description], [Description]Test)
    };
    
    bool [Description]Test::run_tests()
    {
        ut_run_test(test1)
        ut_run_test(test2)
        ...
    
        return (_tests_failed == 0);
    }
    
    bool [Description]Test::test1()
    {
        ut_[name of one of the unit test functions](...
        ut_[name of one of the unit test functions](...
        ...
    
        return true;
    }
    
    bool [Description]Test::test2()
    {
        ut_[name of one of the unit test functions](...
        ut_[name of one of the unit test functions](...
        ...
    
        return true;
    }
    ...
    
    ut_declare_test_c(test_[description], [Description]Test)
    

    Note that ut_[name of one of the unit test functions] corresponds to one of the unittest functions defined within unit_test.h (opens new window).

  10. Within tests_main.h (opens new window) define the new test:

    extern int test_[description](int argc, char *argv[]);
    
  11. Within tests_main.c (opens new window) add description name, test function and option:

    ...
    } tests[] = {
        {...
        {"[description]", test_[description], OPTION},
        ...
    }
    

    OPTION can be OPT_NOALLTEST,OPT_NOJIGTEST or 0 and is considered if within px4 shell one of the two commands are called: ```bash pxh> tests all

    pxh> tests all
    

    pxh> tests jig
    

    If a test has option OPT_NOALLTEST, then that test will be excluded when calling tests all. The same is true for OPT_NOJITEST when command test jig is called. 选项“0”表示从不排除测试,这是大多数开发人员想要使用的。 The same is true for OPT_NOJITEST when command test jig is called. Option 0 means that the test is never excluded, which is what most developer want to use.

  12. Add the test test_[description].cpp to the CMakeLists.txt (opens new window).

# 在本地计算机上进行测试

Run the complete list of GTest Unit Tests, GTest Functional Tests and SITL Unit Tests right from bash:

make tests

单独的 GTest 测试二进制文件处于build/px4_sitl_test/ 目录中,可以直接在大多数IDE的调试器中运行。

使用以下命令对ctest名称使用正则表达式对要运行的测试子集进行筛选:

pxh> tests help

例如:

  • make tests TESTFILTER=unit only run GTest unit tests
  • make tests TESTFILTER=sitl only run simulation tests
  • make tests TESTFILTER=Attitude only run the AttitudeControl test