单元测试成神之路

您所在的位置:网站首页 单元测试谁负责 单元测试成神之路

单元测试成神之路

2023-03-30 01:27| 来源: 网络整理| 查看: 265

单元测试成神之路——C++篇 28 Sep 2020

Reading time ~8 minutes

前言 一、常用C++单测框架 二. Google Test 1. 基本概念 2. 断言 2.1. 基本断言 2.2. 普通比较型断言 2.3. C字符串比较型断言 2.4. 浮点数比较型断言 2.5. 明确的成功和失败 2.6. 异常断言 2.7. 使用已有布尔函数 3. 测试 4. 测试夹具:多个测试有共有的数据配置 三、Catch 2 1. REQUIRE 2. SECTIONS 3. 标签 4. 特点总结 四、CppUTest 1. main和test 2. 断言 3. 特点 五、Google Mock 1. Fake、Mock、Stub 2. 简单例子:Mock Turtle 3. 在测试中使用Mock 4. 指定期望 4.1. 基本语法 4.2. 参数匹配:哪次调用 4.3. 基数:被调用几次 4.4. 行为:该做什么 4.5. 使用多个预期 4.6. Sequence 5. 更多 六、情景示例 1. 项目结构 2. 普通测试 3. 简单 Mock 4. Web请求 5. 数据库访问 七、FAQ 1. 单元测试源文件应该放在项目的什么位置? 2. Google Mock只能Mock虚函数,如果我想Mock非虚函数怎么办? 3. Google Test官方文档中说测测试套件名称、测试夹具名称、测试名称中不应该出现下划线_。为什么? 4. 测试输出里有很多Uninteresting mock function call警告怎么办? 八、结语 1. 实践小结 2. 特别鸣谢 3. 相关阅读 前言

  在上一篇单元测试成神之路————GoLang篇 中, 首先介绍了单元测试的意义和编写单元测试的一般方法。作为同系列出品,本篇文章则主要介绍如何在C++中写单元测试。

  本文直接从常用的C++单元测试框架出发,分别对几种框架进行了简单的介绍和小结,然后介绍了Mock的框架, 并以具体代码示例进行说明,最后列举了一些常见问题。

一、常用C++单测框架

  常用的C++单测对比如下:

Google Test Catch 2 CppUTest   特点 成熟、兼容性好简洁、有效率常用、学习资源多 框架只有一个catch.hpp、集成轻松有Given-When-Then分区,适合BDD行为驱动开发无自带Mock框架 可以检测内存泄露输出更简洁适合在嵌入式系统项目中使用 Mock框架 Google Mock 无自带Mock框架 CppUMock 推荐指数 ★★★★★ ★★★☆☆ ★★☆☆☆

  一般情况下,我们推荐使用Google Test搭配Google Mock。如果项目有特殊需求或更适合其他框架,也可以考虑。

  根据实际使用频率,在以下部分,Google Test和Google Mock的介绍更为详细;对于其他框架,这里介绍它们的主要特点, 具体使用方法,可以查阅各自文档。

二. Google Test

  Google Test是目前比较成熟而且最常用的C++单元测试框架之一。

1. 基本概念

  断言(Assertions) 是检查条件是否为真的语句。断言的结果可能是成功或者失败, 而失败又分为非致命失败或致命失败。如果发生致命失败,测试进程将中止当前运行,否则它将继续运行。

  测试(Test) 使用断言来验证被测试代码的行为。如果测试崩溃或断言失败,则测试失败;否则测试成功。

  测试套件(Test Suite) 包含一个或多个测试(Test)。当测试套件中的多个测试需要共享通用对象和子例程时, 可以将它们放入测试夹具(Test Fixture)。

  测试程序(Test Program) 可以包含多个测试套件。

2. 断言

  Google Test中,断言(Assertions) 是类似函数调用的宏。断言失败时,googletest会输出断言的源文件和 行号位置以及失败消息;我们还可以提供自定义失败消息,该消息将附加到googletest消息中。

  断言成对出现(ASSERT_*和EXPECT_*),它们测试的对象相同,但对当前运行有不同的影响。ASSERT_*版本失败时 会产生致命故障,并中止当前函数(不一定是整个TEST)运行。EXPECT_*版本会产生非致命故障,不会停止当前函数运行。 通常EXPECT_*是首选,因为可以在测试中报告多个故障。但是如果在断言失败时继续执行没有意义,则应使用ASSERT_*。

  要提供自定义失败消息,只需使用= 5 ); } } 3. 标签

  Catch提供标签特性。

TEST_CASE( "A", "[widget]" ) { /* ... */ } TEST_CASE( "B", "[widget]" ) { /* ... */ } TEST_CASE( "C", "[gadget]" ) { /* ... */ } TEST_CASE( "D", "[widget][gadget]" ) { /* ... */ } "[widget]" 选取 A、B、D. "[gadget]" 选取 C、D. "[widget][gadget]" 只选取 D "[widget],[gadget]" 所有A、B、C、D. 还有一些特殊标签指定特殊行为 4. 特点总结 框架只有一个catch.hpp、集成轻松 有Given-When-Then分区,适合BDD行为驱动开发 无自带Mock框架 四、CppUTest 1. main和test

main.cpp:

#include "CppUTest/CommandLineTestRunner.h" int main(int ac, char** av){ return CommandLineTestRunner::RunAllTests(ac, av); }

test.cpp:

#include "CppUTest/TestHarness.h" TEST_GROUP(FirstTestGroup){ void setup(){ // Init stuff } void teardown(){ // Uninit stuff } }; TEST(FirstTestGroup, FirstTest){ FAIL("Fail me!"); } TEST(FirstTestGroup, SecondTest){ STRCMP_EQUAL("hello", "world"); } 2. 断言 CHECK(boolean condition)检查任何布尔结果。 CHECK_TEXT(boolean condition, text)检查任何布尔结果,并在失败时输出文本。 CHECK_FALSE(condition)检查任何布尔结果 CHECK_EQUAL(expected, actual)使用==检查实体之间的相等性。因此,如果有一个支持operator==()的类,则可以使用此宏比较两个实例。 CHECK_COMPARE(first, relop, second)检查在两个实体之间是否存在关系运算符。失败时,打印两个操作数求和的结果。 CHECK_THROWS(expected_exception, expression)检查表达式是否抛出expected_exception(例如std::exception)。CHECK_THROWS仅在使用标准C ++库(默认)构建CppUTest时可用。 STRCMP_EQUAL(expected, actual)使用strcmp()检查const char *字符串是否相等。 STRNCMP_EQUAL(expected, actual, length)使用strncmp()检查const char *字符串是否相等。 STRCMP_NOCASE_EQUAL(expected, actual)不考虑大小写,检查const char *字符串是否相等。 3. 特点 可以检测内存泄露 输出更简洁 使用在嵌入式系统项目中使用 五、Google Mock

  Google Mock一般来说和Google Test搭配使用,但Google Test也可以和其他Mock框架一起使用。 本部分是Google Mock基础常用的用法,如需要特殊用法,请查阅Google Mock官方文档。

1. Fake、Mock、Stub Fake对象有具体的实现,但采取一些捷径,比如用内存替代真实的数据库读取。 Stub对象没有具体的实现,只是返回提前准备好的数据。 Mock对象和Stub类似,只是在测试中需要调用时,针对某种输入指定期望的行为。Mock和Stub的区别是, Mock除了返回数据还可以指定期望以验证行为。 2. 简单例子:Mock Turtle

Turtle类:

class Turtle { ... virtual ~Turtle() {}; virtual void PenUp() = 0; virtual void PenDown() = 0; virtual void Forward(int distance) = 0; virtual void Turn(int degrees) = 0; virtual void GoTo(int x, int y) = 0; virtual int GetX() const = 0; virtual int GetY() const = 0; };

MockTurtle类:

#include "gmock/gmock.h" class MockTurtle : public Turtle { public: ... MOCK_METHOD(void, PenUp, (), (override)); MOCK_METHOD(void, PenDown, (), (override)); MOCK_METHOD(void, Forward, (int distance), (override)); MOCK_METHOD(void, Turn, (int degrees), (override)); MOCK_METHOD(void, GoTo, (int x, int y), (override)); MOCK_METHOD(int, GetX, (), (const, override)); MOCK_METHOD(int, GetY, (), (const, override)); };

  创建Mock类的步骤:

MockTurtle继承Turtle 找到Turtle的一个虚函数 在public:的部分中,写一个MOCK_METHOD();

将虚函数函数签名复制进MOCK_METHOD();中,加两个逗号:一个在返回类型和函数名之间另一个在函数名和参数列表之间

例如:void PenDown()有三部分:void、PenDown和(),这三部分就是MOCK_METHOD的前三个参数

如果要模拟const方法,添加一个包含(const) 的第4个参数(必须带括号)。 建议添加override关键字。所以对于const方法,第四个参数变为(const, override),对于非const方法,第四个参数变为(override)。这不是强制性的。 重复步骤直至完成要模拟的所有虚拟函数。 3. 在测试中使用Mock

  在测试中使用Mock的步骤:

从testing名称空间导入gmock.h的函数名(每个文件只需要执行一次)。 创建一些Mock对象。 指定对它们的期望(方法将被调用多少次?带有什么参数?每次应该做什么(对参数做什么、返回什么值)?等等)。 使用Mock对象;可以使用googletest断言检查结果。如果mock函数的调用超出预期或参数错误,将会立即收到错误消息。 当Mock对象被销毁时,gMock自动检查对模拟的所有期望是否得到满足。 #include "path/to/mock-turtle.h" #include "gmock/gmock.h" #include "gtest/gtest.h" using ::testing::AtLeast; // #1 TEST(PainterTest, CanDrawSomething) { MockTurtle turtle; // #2 EXPECT_CALL(turtle, PenDown()) // #3 .Times(AtLeast(1)); Painter painter(&turtle); // #4 EXPECT_TRUE(painter.DrawCircle(0, 0, 10)); // #5 }

  在这个例子中,我们期望turtle的PenDown()至少被调用一次。如果在turtle对象被销毁时,PenDown()还没有被调用或者调用两次或以上,测试会失败。

4. 指定期望

EXPECT_CALL(指定期望)是使用Google Mock的核心。EXPECT_CALL的作用是两方面的:

告诉这个Mock(假)方法如何模仿原始方法:

我们在EXPECT_CALL中告诉Google Mock,某个对象的某个方法被第一次调用时,会修改某个参数,会返回某个值;第二次调用时,会修改某个参数,会返回某个值…….

验证被调用的情况

我们在EXPECT_CALL中告诉Google Mock,某个对象的某个方法总共会被调用N次(或大于N次、小于N次)。如果最终次数不符合预期,会导致测试失败。

4.1. 基本语法 EXPECT_CALL(mock_object, method(matchers)) .Times(cardinality) .WillOnce(action) .WillRepeatedly(action); mock_object 是对象 method(matchers) 用于匹配相应的函数调用 cardinality 指定基数(被调用次数情况) action 指定被调用时的行为

例子:

using ::testing::Return; ... EXPECT_CALL(turtle, GetX()) .Times(5) .WillOnce(Return(100)) .WillOnce(Return(150)) .WillRepeatedly(Return(200));

  这个EXPECT_CALL()指定的期望是:在turtle这个Mock对象销毁之前,turtle的getX()函数会被调用五次。第一次返回100,第二次返回150,第三次及以后都返回200。指定期望后,5次对getX的调用会有这些行为。但如果最终调用次数不为5次,则测试失败。

4.2. 参数匹配:哪次调用 using ::testing::_; using ::testing::Ge; // 只与Forward(100)匹配 EXPECT_CALL(turtle, Forward(100)); // 与GoTo(x,y)匹配, 只要x>=50 EXPECT_CALL(turtle, GoTo(Ge(50), _)); _相当于“任何”。 100相当于Eq(100)。 Ge(50)指参数大于或等于50。 如果不关心参数,只写函数名就可以。比如EXPECT_CALL(turtle, GoTo);。 4.3. 基数:被调用几次

  用Times(m),Times(AtLeast(n))等来指定期待的调用次数。

  Times可以被省略。比如整个EXPECT_CALL只有一个WillOnce(action)相当于也说明了调用次数只能为1。

4.4. 行为:该做什么

  常用模式:如果需要指定前几次调用的特殊情况,并且之后的调用情况相同。使用一系列WillOnce()之后有WillRepeatedly()

  除了用来指定调用返回值的Return(),Google Mock中常用行为中还有:SetArgPointee(value), SetArgPointee将第N个指针参数(从0开始)指向的变量赋值为value。

  比如void getObject(Object* response){...}的EXPECT_CALL:

Object* a = new Object; EXPECT_CALL(object, request) .WillOnce(SetArgPointee(*a));

  就修改了传入的指针response,使其指向了一个我们新创建的对象。

  如果有多个行为,应该使用DoAll(a1, a2, ..., an)。DoAll执行所有n个action并返回an的结果。

4.5. 使用多个预期

例子:

using ::testing::_; ... EXPECT_CALL(turtle, Forward(_)) // #1 .Times(3); EXPECT_CALL(turtle, Forward(10)) // #2 .Times(2); ...mock对象函数被调用... //Forward(10); // 与#2匹配 //Forward(20); // 与#1匹配

  正常情况下,Google Mock以倒序搜索预期:如果和多个EXPECT_CALL都可以匹配,只有之前的, 距离调用最近的一个EXPECT_CALL()会被匹配。例如:

连续三次调用Forward(10)会生错误因为它和#2匹配。 连续三次调用Forward(20)不会有错误因为它和#1匹配。

  一旦匹配,该预期会被一直绑定,即使执行次数达到上限之后,还是是生效的,这就是为什么三次调用 Forward(10)超过了2号EXPECT_CALL的上限时,不会去试图绑定1号EXPECT_CALL而是报错的原因。

  为了明确地让某一个EXPECT_CALL“退休”,可以加上RetiresOnSaturation(),例子:

using ::testing::Return; EXPECT_CALL(turtle, GetX()) // #1 .WillOnce(Return(10)) .RetiresOnSaturation(); EXPECT_CALL(turtle, GetX()) // #2 .WillOnce(Return(20)) .RetiresOnSaturation(); turtle.GetX() // 与#2匹配,返回20,然后#2“退休” turtle.GetX() // 与#1匹配,返回10

  在这个例子中,第一次GetX()调用和#2匹配,返回20,然后这个EXPECT_CALL就“退休”了; 第二次GetX()调用和#1匹配,返回10

4.6. Sequence

  可以用sequence来指定期望匹配的顺序。

using ::testing::Return; using ::testing::Sequence; Sequence s1, s2; ... EXPECT_CALL(foo, Reset()) .InSequence(s1, s2) .WillOnce(Return(true)); EXPECT_CALL(foo, GetSize()) .InSequence(s1) .WillOnce(Return(1)); EXPECT_CALL(foo, Describe(A())) .InSequence(s2) .WillOnce(Return("dummy"));

  在上面的例子中,创建了两个Sequence s1和s2,属于s1的有Reset()和GetSize(), 所以Reset()必须在GetSize()之前执行。属于s2的有Reset()和Describe(A()), 所以Reset()必须在Describe(A())之前执行。所以,Reset()必须在GetSize() 和Describe()之前执行。而GetSize()和Describe()这两者之间没有顺序约束。

  如果需要指定很多期望的顺序,有另一种用法:

using ::testing::InSequence; { InSequence seq; EXPECT_CALL(...)...; EXPECT_CALL(...)...; ... EXPECT_CALL(...)...; }

  在这种用法中,scope中(大括号中)的期望必须遵守严格的顺序。

5. 更多 Google Mock Cheat Sheet Google Mock Cook Book 六、情景示例

  在这部分,我们用一个示例项目来演示,如何在不同情景中使用 Google Test和Google Mock写单元测试用例。

1. 项目结构

  示例项目是一个C++命令行聊天室软件,包含服务器和客户端。

. ├── CMakeLists.txt ├── README.md ├── client_main.cpp ├── server_main.cpp ├── include │ ├── chat_client.hpp │ ├── chat_message.hpp │ ├── chat_participant.hpp │ ├── chat_room.hpp │ ├── chat_server.hpp │ ├── chat_session.hpp │ ├── http_request.hpp │ ├── http_request_impl.hpp │ ├── message_dao.hpp │ └── message_dao_impl.hpp ├── src │ ├── chat_client.cpp │ ├── chat_message.cpp │ ├── chat_room.cpp │ ├── chat_server.cpp │ ├── chat_session.cpp │ ├── http_request_impl.cpp │ └── message_dao_impl.cpp └── tests ├── chat_message_unittest.cpp └── chat_room_unittest.cpp 2. 普通测试

  如果被测试的函数不包含外部依赖,用Google Test基础的用法就可以完成用例编写。

原函数:

void chat_message::body_length(std::size_t new_length) { body_length_ = new_length; if (body_length_ > 512) body_length_ = 512; }

  这个函数很简单。就是给body_length_赋值但是有最大值限制。测试用例可以这样写:

TEST(ChatMessageTest, BodyLengthNegative) { chat_message c; c.body_length(-50); EXPECT_EQ(512, c.body_length()); } TEST(ChatMessageTest, BodyLength0) { chat_message c; c.body_length(0); EXPECT_EQ(0, c.body_length()); } TEST(ChatMessageTest, BodyLength100) { chat_message c; c.body_length(100); EXPECT_EQ(100, c.body_length()); } TEST(ChatMessageTest, BodyLength512) { chat_message c; c.body_length(512); EXPECT_EQ(512, c.body_length()); } TEST(ChatMessageTest, BodyLength513) { chat_message c; c.body_length(513); EXPECT_EQ(512, c.body_length()); }

  我们可以看到,对于这类函数,用例编写很直接简单,步骤都是构造变量,再用合适的Google Test的 宏来验证变量值或者函数调用返回值。

3. 简单 Mock

原函数

void chat_room::leave(chat_participant_ptr participant) { participants_.erase(participant); }

  participants_ 的类型是 std::set。这个函数的目的很明显,将一个participant从set中移除。

  真实地创建一个聊天参与者participant对象可以条件比较苛刻或者成本比较高。为了有效率地验证这个函数,我们可以新建一些Mock的chat_participant_ptr而不用严格地去创建真实的participant对象。

chat_participant对象:

class chat_participant { public: virtual ~chat_participant() {} virtual void deliver(const chat_message &msg) = 0; };

Mock对象:

class mock_chat_participant : public chat_participant { public: MOCK_METHOD(void, deliver, (const chat_message &msg), (override)); };

测试用例:

TEST(ChatRoomTest, leave) { auto p1 = std::make_shared(); //新建第一个Mock指针 auto p2 = std::make_shared(); //新建第二个Mock指针 auto p3 = std::make_shared(); //新建第三个Mock指针 auto p4 = std::make_shared(); //新建第四个Mock指针 chat_room cr; //新建待测试对象chat_room cr.join(p1); cr.join(p2); cr.join(p3); cr.join(p4); EXPECT_EQ(cr.participants().size(), 4); cr.leave(p4); EXPECT_EQ(cr.participants().size(), 3); cr.leave(p4); EXPECT_EQ(cr.participants().size(), 3); cr.leave(p2); EXPECT_EQ(cr.participants().size(), 2); } 4. Web请求

  chat_room中有一个log(),依赖网络请求。原函数:

std::string chat_room::log() { std::string* response; this->requester->execute("request",response); // web访问,结果存在response指针中 return *response; }

  在单元测试中,我们只关心被测试部分的逻辑。为了测试这个函数,我们不应该创建真实的requester,应该使用mock。

http_request对象:

class http_request { public: virtual ~http_request(){} virtual bool execute(std::string request, std::string* response)=0; };

Mock对象:

class mock_http_request : public http_request { public: MOCK_METHOD(bool, execute, (std::string request, std::string * response), (override)); };

测试用例:

TEST(ChatRoomTest, log) { testing::NiceMock mock_dao; //在下一部分会提到mock_message_dao mock_http_request mock_requester; //Mock对象 std::string response = "response"; //期待调用函数的第二个参数将指向这个string对象 EXPECT_CALL(mock_requester, execute) .WillRepeatedly( //每次调用都会(WillRepeatedly)执行 testing::DoAll( //每次执行包含多个行为 testing::SetArgPointee(response),//将传入参数指针变量response指向response testing::Return(true))); //返回值为true chat_room cr = chat_room(&mock_dao, &mock_requester); //将mock对象通过chat_room的constructor注入 EXPECT_EQ(cr.log(),"response"); //调用和Google Test断言 } 5. 数据库访问

  chat_room对象会将聊天者发送的消息存储在redis数据库中。当新用户加入时,chat_room对象从数据库 获取所有历史消息发送给该新用户。

join()函数:

void chat_room::join(chat_participant_ptr participant) { participants_.insert(participant); std::vector recent_msg_strs = this->dao->get_messages(); //从数据库中获取历史消息 for (std::string recent_msg_str: recent_msg_strs) { //将每一个消息发送给该聊天参与者 auto msg = chat_message(); msg.set_body_string(recent_msg_str); participant->deliver(msg); } }

message_dao对象:

class message_dao { public: virtual ~message_dao(){} virtual bool add_message(std::string m)=0; virtual std::vector get_messages()=0; };

Mock对象:

class mock_message_dao : public message_dao { public: MOCK_METHOD(bool, add_message, (std::string m), (override)); MOCK_METHOD(std::vector, get_messages, (), (override)); };

测试用例:

TEST(ChatRoomTest, join) { mock_message_dao mock_dao; //创建mock对象(需要注入chat_room) http_request_impl requester; //创建web访问对象(也需要注入chat_room) auto mock_p1 = std::make_shared(); //创建participant的mock指针 EXPECT_CALL(mock_dao, get_messages) .WillOnce(testing::Return(std::vector{"test_msg_body_1", "test_msg_body_2", "test_msg_body_3"})); //指定get_messages调用的返回值 EXPECT_CALL(*mock_p1, deliver).Times(3); //指定deliver调用的次数 chat_room cr = chat_room(&mock_dao, &requester); //创建chat_room对象,注入dao和requester cr.join(mock_p1); //调用 }

  先创建mock对象,再指定函数调用的预期,最后指向被测试函数。我们可以看到,mock_dao指定了get_messages的 返回值时一个长度为3的vector,所以有3条消息会被deliver。

七、FAQ 1. 单元测试源文件应该放在项目的什么位置?

  一般来说,我们会在根目录创建一个tests文件夹,里面放单元测试部分的源代码,从而不会和被测试代码混在一起。

  如果需要和其他测试(如接口测试、压力测试)等区分开来,可以

把tests改成unittests、utests等,或者 在tests创建不同子文件夹存放不同类型的测试代码。 2. Google Mock只能Mock虚函数,如果我想Mock非虚函数怎么办?

  由于Google Mock(及其他大部分Mock框架)通过继承来动态重载机制的限制,一般来说Google Mock只能Mock虚函数。如果要mock非虚函数,官方文档提供这几种思路:

Mock类和原类没有继承关系,在测试对象使用函数模板。在测试中,测试对象接受Mock类。 创建一个接口(抽象类),原类继承自这个接口(抽象类)。在测试中Mock这个接口(抽象类)。

  这两种方法,都需要对代码进行一定的修改或重构。如果不想修改被测试代码。可以考虑使用hook技术替换被mock的部分从而mock一般函数。

使用TMock对非虚函数mock的例子:

mock函数

# include "tmock.h" class MockClass { public: //注册mock类 TMOCK_CLASS(MockClass); //声明mock类函数,TMOCK_METHOD{n}第一个参数与attach_func_lib第一个参数相同,其余参考与MOCK_METHOD{n}一致。 TMOCK_METHOD1("original", original, uint32_t(const char * str_file_md5) ) };

  单测中应用tmock的方法和Google Mock基本一致。但在结束的时候需要使用TMOCK_CLEAR清除exception, detach hook的函数,防止干扰其他单元测试。

3. Google Test官方文档中说测测试套件名称、测试夹具名称、测试名称中不应该出现下划线_。为什么?

  TEST(TestSuiteName, TestName)生成名为TestSuiteName_TestName_Test的类。

  下划线_是特殊的,因为C ++保留以下内容供编译器和标准库使用。所以开头和结尾有下划线很容易让生成的类的标识符不合法。

  另一方面,下划线可能让不同测试生成相同的类。比如TEST(Time,Flies_Like_An_Arrow){...} 和TEST(Time_Flies,Like_An_Arrow){...} 都生成名为Time_Flies_Like_An_Arrow_Test的类。

4. 测试输出里有很多Uninteresting mock function call警告怎么办?

  创建的Mock的对象的某些调用如果没有相应匹配的EXPECT_CALL,Google Mock会生成这个警告。

  为了去除这个警告,可以使用NiceMock。比如如果原本使用MockFoo nice_foo;新建mock对象的话,可以改成NiceMock nice_foo;。NiceMock是MockFoo的子类。

八、结语 1. 实践小结

  和GoLang单元测试框架有些区别的是,GoLang自生就提供了自带的测试框架,也有第三方框架进行选择。 而C/C++/php等语言的单元测试框架则需要第三方提供和安装。

  框架的使用,无非是一些语法糖的差异和使用的难易程度。不管使用什么语言,什么框架,最关键的是利用单元测试的思路, 写出解耦的、可测试的、易于维护的代码,保证代码的质量。

  单元测试是一种手段,能够一定程度的改善生产力。凡事有度过犹不及,如果一味的盲目的追求测试覆盖率, 忽视了测试代码本身的质量,那么各种无效的单元测试反而带来了沉重的维护负担。因此单测的代码,本身也是代码, 也是和项目本身的代码一样,需要重构、维护的(好好写代码)。

2. 特别鸣谢

  感谢实习生钟梓轩,在暑假实习期间,主导整理了C++单测的代码示例和部分文章内容。

3. 相关阅读 单元测试成神之路——GoLang篇 测试 Share Tweet +1


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3