官网:http://wiki.ros.org/gtest 翻译如下:
1. 获取gtest
ros版使用 http://wiki.ros.org/ros_comm
2. Goolge Test
GoolgeTest /gtest,c++语言的单元测试,官方文档 https://github.com/google/googletest,也可以参考http://www.ibm.com/developerworks/aix/library/au-googletestingframework.html
这些网页给了一些细节步骤,写并且调用单元测试,包括在ROS的代码中
3. 代码结构
按照惯例,如果你的包结构不复杂,建一个test的目录,并在test下写单元测试就够了。
4. 写单元测试
一个基础的测试结果如下:
1 // Bring in my package’s API, which is what I’m testing
2 #include “foo/foo.h”
3 // Bring in gtest
4 #include <gtest/gtest.h>
5
6 // Declare a test
7 TEST(TestSuite, testCase1)
8 {
9 <test things here, calling EXPECT_* and/or ASSERT_* macros as needed>
10 }
11
12 // Declare another test
13 TEST(TestSuite, testCase2)
14 {
15 <test things here, calling EXPECT_* and/or ASSERT_* macros as needed>
16 }
17
18 // Run all the tests that were declared with TEST()
19 int main(int argc, char **argv){
20 testing::InitGoogleTest(&argc, argv);
21 ros::init(argc, argv, “tester”);
22 ros::NodeHandle nh; 23 return RUN_ALL_TESTS()
23 return RUN_ALL_TESTS()
24 }
4.1. 测试命名惯例
每一个测试是一个test case,test case被组合到test suites里。需要合理的使用test suites。很多包可以用同一个test suites,但是如果有必要的话你可以使用多个。
test suites命名类似c++的驼峰式的类型定义
test case的命名方法都类似c++驼峰式的函数定义
5. 构建和执行tests
使用CMakeLists.txt构建的:
catkin_add_gtest(utest test/utest.cpp)
6. 测试输出
类似如下:
[==========] Running 3 tests from 1 test case.
[----------] Global test environment set-up.
[----------] 3 tests from MapServer
[ RUN ] MapServer.loadValidPNG
[ OK ] MapServer.loadValidPNG
[ RUN ] MapServer.loadValidBMP
[ OK ] MapServer.loadValidBMP
[ RUN ] MapServer.loadInvalidFile
[ OK ] MapServer.loadInvalidFile
[----------] Global test environment tear-down
[==========] 3 tests from 1 test case ran.
[ PASSED ] 3 tests.
执行完毕在test_results文件夹下输出一个xml,汇总了输出结果
7. 例子
7.1 函数调用
下面的例子是从math_utils包拿出来的。它可以你怎么test各种不同的函数。
EXCEPT_*的宏和ASSERT_*的宏的区别是,后者遇到失败的情况,直接退出程序。
#include "math_utils/MathExpression.h"
#include "math_utils/math_utils.h"
#include <gtest/gtest.h>
#define TEST_EXPRESSION(a) EXPECT_EQ((a), meval::EvaluateMathExpression(#a))
TEST(MathExpressions, operatorRecognition){
EXPECT_TRUE(meval::ContainsOperators("+"));
EXPECT_TRUE(meval::ContainsOperators("-"));
EXPECT_TRUE(meval::ContainsOperators("/"));
EXPECT_TRUE(meval::ContainsOperators("*"));
EXPECT_FALSE(meval::ContainsOperators("1234567890qwertyuiop[]asdfghjkl;'zxcvbnm,._=?8"));
}
TEST(MathExpressions, basicOperations){
EXPECT_EQ(5, meval::EvaluateMathExpression("2+3"));
EXPECT_EQ(5, meval::EvaluateMathExpression("2 + 3"));
EXPECT_EQ(10, meval::EvaluateMathExpression("20/2"));
EXPECT_EQ(-4, meval::EvaluateMathExpression("6 - 10"));
EXPECT_EQ(24, meval::EvaluateMathExpression("6 * 4"));
}
TEST(MathExpressions, complexOperations){
TEST_EXPRESSION(((3 + 4) / 2.0) + 10);
TEST_EXPRESSION(7 * (1 + 2 + 3 - 2 + 3.4) / 12.7);
TEST_EXPRESSION((1 + 2 + 3) - (8.0 / 10));
}
TEST(MathExpressions, UnaryMinus){
TEST_EXPRESSION(-5);
}
TEST(MathExpressions, badInput){
//TODO - figure out what good error behavior is and test for it properly
//EXPECT_EQ(0, meval::EvaluateMathExpression("4.1.3 - 4.1"));
//EXPECT_EQ(0, meval::EvaluateMathExpression("4.1.3"));
}
TEST(MathUtils, basicOperations){
EXPECT_EQ(math_utils::clamp<int>(-10, 10, 20), 10);
EXPECT_EQ(math_utils::clamp<int>(15, 10, 20), 15);
EXPECT_EQ(math_utils::clamp<int>(25, 10, 20), 20);
}
int main(int argc, char **argv){
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
7.2 处理异常
google 不使用异常,所以gtest不处理他们
如果你的testing代码抛异常,需要自己try catch,并且使用ADD_FAILURE或者FAIL等宏,ADD_FAILURE
另外注意,SUCCEED的宏是纯记录性质的,如果你想test中间的时候终止test程序,必须显示的return
7.3 持久化数据,使用test fixtures
通过创建test fixtures,你可以创建一个持久化的数据对象,具体是类继承testing::Test,测试的时候使用TEST_F这个宏定义。
一个最常见的场景是,当你在不同的test case中测试相同的一个函数,一些需要初始化的变量和条件冗长,且需要多次使用。
下面是一个例子:
#ifndef TALKER
#define TALKER
#include <ros/ros.h>
#include "geometry_msgs/Vector3.h"
class Talker{
ros::NodeHandle nh;
ros::Publisher pub;
public:
Talker();
void pubMsg(geometry_msgs::Vector3&& v);
geometry_msgs::Vector3 add(const geometry_msgs::Vector3& a, const geometry_msgs::Vector3& b);
geometry_msgs::Vector3 mult(const geometry_msgs::Vector3& a, const geometry_msgs::Vector3& b);
};
# endif // TALKER_H
如果我们想测试add和mult的时候,使用相同的参数,这时候就可以使用test fixture来初始化和销毁我们的变量
#include <gtest/gtest.h>
#include "talker/talker.h"
// helper function to create a geometry_msg::Vector3
auto createVec = [](double x, double y, double z) {
geometry_msgs::Vector3 v;
v.x = x;
v.y = y;
v.z = z;
return v;
};
class TalkerFixture : public ::testing::Test {
protected:
geometry_msgs::Vector3 a;
geometry_msgs::Vector3 b;
// Setup
TalkerTest() {
a = createVec(1,2,3);
b = createVec(2,2,2);
}
};
TEST_F(TalkerFixture, TestAdder) {
Talker talk;
// a and b are inherited from TalkerTest
geometry_msgs::Vector3 vec = talk.add(a,b);
geometry_msgs::Vector3 ans = createVec(3,4,5);
ASSERT_EQ(ans.x, vec.x) << "Talker add x's failed";
ASSERT_EQ(ans.y, vec.y) << "Talker add y's failed";
ASSERT_EQ(ans.z, vec.z) << "Talker add z's failed";
}
TEST_F(TalkerFixture, TestMult) {
Talker talk;
// a and b are inherited from TalkerTest
geometry_msgs::Vector3 vec = talk.mult(a,b);
geometry_msgs::Vector3 ans = createVec(2,4,6);
ASSERT_EQ(ans.x, vec.x) << "Talker mult x's failed";
ASSERT_EQ(ans.y, vec.y) << "Talker mult y's failed";
ASSERT_EQ(ans.z, vec.z) << "Talker mult z's failed";
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
ros::init(argc, argv, "test_talker");
ros::NodeHandle nh;
return RUN_ALL_TESTS();
}