TDD(測試驅(qū)動開發(fā))的意義在哪?
在之前的文章中,我們討論了如何使用面向?qū)ο蠛驮O(shè)計模式來設(shè)計算法。然而在實(shí)際的算法開發(fā)中,算法經(jīng)常需要迭代和重構(gòu)。那如何能保證算法的代碼質(zhì)量呢?
為了方便理解,我們以3D投影算法為例。該算法將物體在世界坐標(biāo)系下的坐標(biāo)轉(zhuǎn)換為在相機(jī)圖像中的像素坐標(biāo),涉及三部分:
① 世界坐標(biāo)系轉(zhuǎn)換至相機(jī)坐標(biāo)系
② 相機(jī)坐標(biāo)系轉(zhuǎn)換至圖像坐標(biāo)系
③ 圖像坐標(biāo)系轉(zhuǎn)換到像素坐標(biāo)系

代碼如下(省略了算法細(xì)節(jié)):
void WorldToCamera(const WorldCoordinates& world_coord, ? ? ? ? ? ? ? ? ? CameraCoordinates& camera_coord)
{ ?
// ... ?
// assign value to camera_coord
}
void CameraToImage(const CameraCoordinates& camera_coord,
? ? ? ? ? ? ? ? ImageCoordinates& image_coord)
{
// ... ?
// assign value to image_coord
}
void ImageToPixel(const ImageCoordinates& image_coord,
? ? ? ? ? ? ? ?PixelCoordinates& pixel_coord)
{
// ... ?
// assign value to pixel_coord
}
void WorldToPixel(const WorldCoordinates& world_coord,
? ? ? ? ? ? ? ?PixelCoordinates& pixel_coord)
{
CameraCoordinates camera_coord; ?
ImageCoordinates image_coord; ?
WorldToCamera(world_coord, camera_coord); ?
CameraToImage(camera_coord, image_coord); ?
ImageToPixel(image_coord, pixel_coord);
}
在原有基礎(chǔ)上,我們想優(yōu)化代碼。當(dāng)修改完代碼后發(fā)現(xiàn),最終坐標(biāo)轉(zhuǎn)換結(jié)果錯誤。但是我們無法確定具體是算法中的哪一步驟出了問題。所以我們就開始了漫長的調(diào)試階段。但是,這個過程是很浪費(fèi)時間的。因?yàn)槟悴恢绬栴}出在哪里,所以你就好像是一只“無頭蒼蠅”一樣在代碼中到處調(diào)試,添加斷點(diǎn)、大量的注釋以及打印信息,甚至因著不夠小心,誤刪了或是修改了重要的源代碼而沒有察覺。導(dǎo)致整個調(diào)試過程困難且低效,甚至你的“調(diào)試”會增加更多的bug。那么,如何來解決這個問題呢?
▌ TDD
TDD (Test Driven Development),測試驅(qū)動開發(fā)是一種以測試為優(yōu)先的設(shè)計方法論。通過編寫單元測試用例,保證復(fù)雜系統(tǒng)/算法的開發(fā)和重構(gòu)質(zhì)量。

簡單來說, TDD就是:紅,綠,重構(gòu)。
①首先,非常重要的一點(diǎn)就是“測試先行”。在編寫程序之先,首先寫好測試程序,包括確定測試用例。
②然后才是編寫代碼。當(dāng)代碼初步完成時,運(yùn)行測試程序來驗(yàn)證算法。此時你會發(fā)現(xiàn), 代碼不能通過所有的測試。通過分析這些用例無法通過的原因,修改代碼直至通過全部測試。
③而后便是不斷地重構(gòu)代碼。在此過程中,每次修改代碼都需要通過測試程序的驗(yàn)證。
▌ Gtest
ROS內(nèi)置了gtest模塊,我們可用gtest來實(shí)現(xiàn)TDD。gtest又稱GoogleTest, 是由Google發(fā)布的C++單元測試框架。現(xiàn)在我們?yōu)樯鲜鏊惴ň帉懸粋€測試程序。
// Bring in my package's API, which is what I'm testing
#include <tdd/coordinate.h>
// Bring in gtest
#include <ros/ros.h>
#include <gtest/gtest.h>
TEST(TestSuite, testWorldToCamera)
{ ?
WorldCoordinates world_coord = {200, 300, -20}; ?
CameraCoordinates camera_coord; ?
WorldToCamera(world_coord, camera_coord); ?
EXPECT_EQ(100, camera_coord.x);
EXPECT_EQ(-80, camera_coord.y); ?
EXPECT_EQ(-20, camera_coord.z);
}
TEST(TestSuite, testCameraToImage)
{ ?
CameraCoordinates camera_coord = {100, -80, -20}; ?
ImageCoordinates image_coord; ?
CameraToImage(camera_coord, image_coord); ?
EXPECT_EQ(100, image_coord.x); ?
EXPECT_EQ(-80, image_coord.y);
}
TEST(TestSuite, testImageToPixel)
{ ?
ImageCoordinates image_coord = {100, -80}; ?
PixelCoordinates pixel_coord;
ImageToPixel(image_coord, pixel_coord); ?
EXPECT_EQ(400, pixel_coord.x); ?
EXPECT_EQ(300, pixel_coord.y);
}
TEST(TestSuite, testWorldToPixel)
{ ?
WorldCoordinates world_coord = {200, 300, -20}; ?
PixelCoordinates pixel_coord; ?
WorldToPixel(world_coord, pixel_coord); ?
EXPECT_EQ(400, pixel_coord.x); ?
EXPECT_EQ(300, pixel_coord.y);
}
// Run all the tests that were declared with TEST()
int main(int argc, char **argv){ ?
testing::InitGoogleTest(&argc, argv); ?
ros::init(argc, argv, "tester"); ?
ros::NodeHandle nh; ?return RUN_ALL_TESTS();
}
運(yùn)行測試程序后,若是代碼能通過所有測試用例,將輸出以下結(jié)果:

若是算法中某一部分實(shí)現(xiàn)有錯誤,比如說ImageToPixel坐標(biāo)轉(zhuǎn)換錯誤,將輸出以下結(jié)果:

該輸出結(jié)果告訴我們,WorldToCamera, CameraToImage坐標(biāo)轉(zhuǎn)換正確,而ImageToPixle坐標(biāo)轉(zhuǎn)換錯誤。錯誤值出現(xiàn)在坐標(biāo)的y值上,期望理論值為300,而實(shí)際計算值為200;錯誤的代碼在源文件http://test_coordinate.cc中的41行。
gtest的斷言
斷言在對一個類或函數(shù)進(jìn)行測試時經(jīng)常會使用到。當(dāng)一個斷言,如上述的

失敗時,屏幕上會輸出該代碼所在的源文件及其所在的位置行號,以及錯誤信息。常見的斷言還有,布爾值檢查,數(shù)值型數(shù)據(jù)檢查,字符串比較,異常檢查,浮點(diǎn)型檢查,類型檢查等。
快速反饋
通過運(yùn)行測試程序輸出的信息,我們能快速得到反饋,從而得知算法中的哪一步驟出現(xiàn)問題。表面上看,編寫測試程序增加了代碼量;實(shí)際上在后續(xù)的算法開發(fā)和重構(gòu)階段會為我們節(jié)省大量的時間。如上述所示,我們快速定位到ImageToPixle出現(xiàn)問題,只需要關(guān)注該部分代碼而無需再去檢查其他部分的代碼。并且,每一次測試我們都不需要去做額外的工作,如準(zhǔn)備測試數(shù)據(jù),啟動節(jié)點(diǎn)等,這些都包含在測試程序里。
確保代碼質(zhì)量
通過合適且足夠的測試用例來覆蓋算法的各步驟所涉及的各種情況,為算法提供全方位的質(zhì)量保證。
▌ 總結(jié)
在這篇文章中,我們介紹如何使用gtest在ROS中實(shí)現(xiàn)了TDD,并將其運(yùn)用在算法開發(fā)和重構(gòu)中,提高了開發(fā)效率并保證了代碼質(zhì)量。關(guān)于更多gtest的使用,可查閱參考資料。
參考資料
1.?https://google.github.io/googletest/
2.?http://wiki.ros.org/gtest

本文共1455字
由西湖大學(xué)智能無人系統(tǒng)實(shí)驗(yàn)室工程師陳華奔原創(chuàng)
申請文章授權(quán)請聯(lián)系后臺相關(guān)運(yùn)營人員
▌微信公眾號:空中機(jī)器人前沿
▌知乎:空中機(jī)器人前沿(本文鏈接:https://www.zhihu.com/question/329784671/answer/2483642856)
▌Youtube:Aerial robotics @ Westlake University
▌實(shí)驗(yàn)室網(wǎng)站:https://shiyuzhao.westlake.edu.cn/
