在之前一篇C++ Programming with TDD博客中,我带给大家gmock框架的简介(地址戳着里),今天我们继续本系列,带个大家C++中的单元测试框架CppUTest的介绍。
CppUTest 是一个功能全面的测试框架。CppUTest是为了支持在多种操作系统上开发嵌入式软件而特别设计的。CppUTest的宏被设计成不需要了解C++也可以写测试用例。这使得C程序员更容易用这个测试框架。CppUTest只使用C++语言中主要的那部分子集,这种选择很好地适应了那些编译器不能完全支持全部C++语言特性的嵌入式开发。你会看到用Unity和CppUTest写出的单元测试几乎一模一样。你当然可以选择任意一个测试框架来进行你的产品开发。
一、 CppUTest的安装
CppUTest项目地址:http://www.cpputest.org/,目前的最新版本是cpputest-3.5,下载解压,然后切换到源文件目录,运行以下命令安装:
1 cd $CPPUTEST_HOME 2 ./configure 3 make 4 make -f Makefile_CppUTestExt
二、 CppUTest实例
直接贴代码:
1 /* 2 Filename: location.h 3 */ 4 #ifndef Location_h 5 #define Location_h 6 7 #include <limits> 8 #include <cmath> 9 #include <ostream> 10 11 const double Pi{ 4.0 * atan(1.0) }; 12 const double ToRadiansConversionFactor{ Pi / 180 }; 13 const double RadiusOfEarthInMeters{ 6372000 }; 14 const double MetersPerDegreeAtEquator{ 111111 }; 15 16 const double North{ 0 }; 17 const double West{ 90 }; 18 const double South{ 180 }; 19 const double East{ 270 }; 20 const double CloseMeters{ 3 }; 21 22 class Location { 23 public: 24 Location(); 25 Location(double latitude, double longitude); 26 27 inline double toRadians(double degrees) const { 28 return degrees * ToRadiansConversionFactor; 29 } 30 31 inline double toCoordinate(double radians) const { 32 return radians * (180 / Pi); 33 } 34 35 inline double latitudeAsRadians() const { 36 return toRadians(latitude_); 37 } 38 39 inline double longitudeAsRadians() const { 40 return toRadians(longitude_); 41 } 42 43 double latitude() const; 44 double longitude() const; 45 46 bool operator==(const Location& that); 47 bool operator!=(const Location& that); 48 49 Location go(double meters, double bearing) const; 50 double distanceInMeters(const Location& there) const; 51 bool isUnknown() const; 52 bool isVeryCloseTo(const Location& there) const; 53 54 private: 55 double latitude_; 56 double longitude_; 57 58 double haversineDistance(Location there) const; 59 }; 60 61 std::ostream& operator<<(std::ostream& output, const Location& location); 62 63 #endif
1 /* 2 Filename: GeoServer.h 3 */ 4 #ifndef GeoServer_h 5 #define GeoServer_h 6 7 #include <string> 8 #include <unordered_map> 9 10 #include "Location.h" 11 12 class GeoServer { 13 public: 14 void track(const std::string& user); 15 void stopTracking(const std::string& user); 16 void updateLocation(const std::string& user, const Location& location); 17 18 bool isTracking(const std::string& user) const; 19 Location locationOf(const std::string& user) const; 20 21 private: 22 std::unordered_map<std::string, Location> locations_; 23 24 std::unordered_map<std::string, Location>::const_iterator 25 find(const std::string& user) const; 26 }; 27 28 #endif
1 /* 2 Filename: Location.cpp 3 */ 4 #include "Location.h" 5 6 #include <ostream> 7 8 using namespace std; 9 10 ostream& operator<<(ostream& output, const Location& location) { 11 output << "(" << location.latitude() << "," << location.longitude() << ")"; 12 return output; 13 } 14 15 Location::Location() 16 : latitude_(std::numeric_limits<double>::infinity()) 17 , longitude_(std::numeric_limits<double>::infinity()) {} 18 19 Location::Location(double latitude, double longitude) 20 : latitude_(latitude), longitude_(longitude) {} 21 22 double Location::latitude() const { 23 return latitude_; 24 } 25 26 double Location::longitude() const { 27 return longitude_; 28 } 29 30 bool Location::operator==(const Location& that) { 31 return 32 longitude_ == that.longitude_ && 33 latitude_ == that.latitude_; 34 } 35 36 bool Location::operator!=(const Location& that) { 37 return !(*this == that); 38 } 39 40 // from williams.best.vwh.net/avform.htm#LL 41 Location Location::go(double meters, double bearing) const { 42 bearing = toRadians(bearing); 43 double distance { meters / RadiusOfEarthInMeters }; 44 double newLat { 45 asin(sin(latitudeAsRadians()) * cos(distance) + 46 cos(latitudeAsRadians()) * sin(distance) * cos(bearing)) }; 47 48 double newLong = longitudeAsRadians(); 49 if (cos(latitudeAsRadians()) != 0) 50 newLong = 51 fmod(longitudeAsRadians() - asin(sin(bearing) * sin(distance) / cos(newLat)) + Pi, 52 2 * Pi) - Pi; 53 54 return Location(toCoordinate(newLat), toCoordinate(newLong)); 55 } 56 57 double Location::distanceInMeters(const Location& there) const { 58 return RadiusOfEarthInMeters * haversineDistance(there); 59 } 60 61 bool Location::isUnknown() const { 62 return latitude_ == std::numeric_limits<double>::infinity(); 63 } 64 65 bool Location::isVeryCloseTo(const Location& there) const { 66 return distanceInMeters(there) <= CloseMeters; 67 } 68 69 double Location::haversineDistance(Location there) const { 70 double deltaLongitude { longitudeAsRadians() - there.longitudeAsRadians() }; 71 double deltaLatitude { latitudeAsRadians() - there.latitudeAsRadians() }; 72 73 double aHaversine { 74 pow( 75 sin(deltaLatitude / 2.0), 2.0) + 76 cos(latitudeAsRadians()) * cos(there.latitudeAsRadians()) * pow(sin(deltaLongitude / 2), 77 2) }; 78 return 2 * atan2(sqrt(aHaversine), sqrt(1.0 - aHaversine)); 79 }
1 /* 2 Filename: GeoServer.cpp 3 */ 4 #include "GeoServer.h" 5 #include "Location.h" 6 using namespace std; 7 void GeoServer::track(const string& user) { 8 locations_[user] = Location(); 9 } 10 11 void GeoServer::stopTracking(const string& user) { 12 locations_.erase(user); 13 } 14 15 bool GeoServer::isTracking(const string& user) const { 16 return find(user) != locations_.end(); 17 } 18 19 void GeoServer::updateLocation(const string& user, const Location& location) { 20 locations_[user] = location; 21 } 22 Location GeoServer::locationOf(const string& user) const { 23 if (!isTracking(user)) return Location{}; // TODO performance cost? 24 return find(user)->second; 25 } 26 27 std::unordered_map<std::string, Location>::const_iterator 28 GeoServer::find(const std::string& user) const { 29 return locations_.find(user); 30 }
1 /* 2 Filename: LocationTest.cpp 3 */ 4 #include "CppUTest/TestHarness.h" 5 6 #include <sstream> 7 8 #include "Location.h" 9 10 using namespace std; 11 12 SimpleString StringFrom(const Location& location) { 13 return SimpleString( 14 StringFromFormat("(%d, %d)", 15 location.latitude(), location.longitude())); 16 } 17 18 TEST_GROUP(ALocation) { 19 const double Tolerance { 0.005 }; 20 const Location ArbitraryLocation { 38.2, -104.5 }; 21 }; 22 23 TEST(ALocation, AnswersLatitudeAndLongitude) { 24 Location location{10, 20}; 25 26 LONGS_EQUAL(10, location.latitude()); 27 LONGS_EQUAL(20, location.longitude()); 28 } 29 30 TEST(ALocation, IsNotUnknownWhenLatitudeAndLongitudeProvided) { 31 Location location{1, 1}; 32 33 CHECK_FALSE(location.isUnknown()); 34 } 35 36 TEST(ALocation, IsUnknownWhenLatitudeAndLongitudeNotProvided) { 37 Location location; 38 39 CHECK_TRUE(location.isUnknown()); 40 } 41 42 TEST(ALocation, AnswersDistanceFromAnotherInMeters) { 43 Location point1{ 38.017, -104.84 }; 44 Location point2{ 38.025, -104.99 }; 45 46 // verified at www.ig.utexas.edu/outreach/googleearth/latlong.html 47 DOUBLES_EQUAL(13170, point1.distanceInMeters(point2), 5); 48 } 49 50 TEST(ALocation, IsNotEqualToAnotherWhenLatDiffers) { 51 Location point1{ 10, 11 }; 52 Location point2{ 11, 11 }; 53 54 CHECK_TRUE(point1 != point2); 55 } 56 57 TEST(ALocation, IsNotEqualToAnotherWhenLongDiffers) { 58 Location point1{ 10, 11 }; 59 Location point2{ 10, 12 }; 60 61 CHECK_TRUE(point1 != point2); 62 } 63 64 TEST(ALocation, IsNotEqualToAnotherWhenLatAndLongMatch) { 65 Location point1{ 10, 11 }; 66 Location point2{ 10, 11 }; 67 68 CHECK_TRUE(point1 == point2); 69 } 70 71 TEST(ALocation, AnswersNewLocationGivenDistanceAndBearing) { 72 Location start{0, 0}; 73 74 auto newLocation = start.go(MetersPerDegreeAtEquator, East); 75 76 Location expectedEnd{0, 1}; 77 DOUBLES_EQUAL(1, newLocation.longitude(), Tolerance); 78 DOUBLES_EQUAL(0, newLocation.latitude(), Tolerance); 79 } 80 81 TEST(ALocation, AnswersNewLocationGivenDistanceAndBearingVerifiedByHaversine) { 82 double distance{ 100 }; 83 Location start{ 38, -78 }; 84 85 auto end = start.go(distance, 35); 86 87 DOUBLES_EQUAL(distance, start.distanceInMeters(end), Tolerance); 88 } 89 90 TEST(ALocation, CanBeAPole) { 91 Location start{ 90, 0 }; 92 93 auto end = start.go(MetersPerDegreeAtEquator, South); 94 95 DOUBLES_EQUAL(0, end.longitude(), Tolerance); 96 DOUBLES_EQUAL(89, end.latitude(), Tolerance); 97 } 98 99 TEST(ALocation, IsVeryCloseToAnotherWhenSmallDistanceApart) { 100 Location threeMetersAway { ArbitraryLocation.go(3, South) }; 101 102 CHECK_TRUE(ArbitraryLocation.isVeryCloseTo(threeMetersAway)); 103 } 104 105 TEST(ALocation, IsNotVeryCloseToAnotherWhenNotSmallDistanceApart) { 106 Location fourMetersAway { ArbitraryLocation.go(4, South) }; 107 108 CHECK_FALSE(ArbitraryLocation.isVeryCloseTo(fourMetersAway)); 109 } 110 111 TEST(ALocation, ProvidesPrintableRepresentation) { 112 Location location{-32, -105}; 113 stringstream s; 114 115 s << location; 116 117 CHECK_EQUAL("(-32,-105)", s.str()); 118 }
1 /* 2 Filename: CppUTestExtensions.h 3 */ 4 #ifndef CppUTestExtensions_h 5 #define CppUTestExtensions_h 6 7 #include <string> 8 #include <vector> 9 #include <sstream> 10 #include <functional> 11 12 #include "CppUTest/TestHarness.h" 13 14 template<typename T> 15 SimpleString StringFrom(const std::vector<T>& list, std::function<std::string(T)> func) { 16 std::stringstream stream; 17 for (auto each: list) { 18 if (stream.str().length() > 0) stream << ","; 19 stream << func(each); 20 } 21 return SimpleString(stream.str().c_str()); 22 } 23 24 SimpleString StringFrom(const std::vector<std::string>& list); 25 26 #endif
1 /* 2 Filename: CppUTestExtensions.cpp 3 */ 4 #include "CppUTest/TestHarness.h" 5 #include "CppUTestExtensions.h" 6 7 TEST_GROUP(StringFrom_ForAVector) { 8 }; 9 10 TEST(StringFrom_ForAVector, AnswersEmptyStringWhenVectorEmpty) { 11 std::vector<std::string> strings {}; 12 13 CHECK_EQUAL("", StringFrom(strings)); 14 } 15 16 TEST(StringFrom_ForAVector, AnswersCommaSeparatedList) { 17 std::vector<std::string> strings {"alpha", "beta", "gamma"}; 18 19 CHECK_EQUAL("alpha,beta,gamma", StringFrom(strings)); 20 } 21 22 struct TestItem { 23 TestItem(int number) : Number(number) {} 24 int Number; 25 }; 26 27 TEST(StringFrom_ForAVector, AcceptsTransformLambdaSoYouCanBuildYourOwnEasily) { 28 std::vector<TestItem> items { TestItem(1), TestItem(2), TestItem(3) }; 29 30 auto string = StringFrom<TestItem>(items, 31 [](TestItem item) { return std::to_string(item.Number); }); 32 33 CHECK_EQUAL("1,2,3", string); 34 }
1 /* 2 Filename: GeoServerTest.cpp 3 */ 4 #include "CppUTest/TestHarness.h" 5 #include "CppUTestExtensions.h" 6 #include "GeoServer.h" 7 8 using namespace std; 9 10 TEST_GROUP(AGeoServer) { 11 GeoServer server; 12 13 const string aUser{"auser"}; 14 const double LocationTolerance{0.005}; 15 }; 16 TEST(AGeoServer, TracksAUser) { 17 server.track(aUser); 18 19 CHECK_TRUE(server.isTracking(aUser)); 20 } 21 22 TEST(AGeoServer, IsNotTrackingAUserNotTracked) { 23 CHECK_FALSE(server.isTracking(aUser)); 24 } 25 26 TEST(AGeoServer, TracksMultipleUsers) { 27 server.track(aUser); 28 server.track("anotheruser"); 29 30 CHECK_FALSE(server.isTracking("thirduser")); 31 CHECK_TRUE(server.isTracking(aUser)); 32 CHECK_TRUE(server.isTracking("anotheruser")); 33 } 34 35 TEST(AGeoServer, IsTrackingAnswersFalseWhenUserNoLongerTracked) { 36 server.track(aUser); 37 server.stopTracking(aUser); 38 39 CHECK_FALSE(server.isTracking(aUser)); 40 } 41 42 TEST(AGeoServer, UpdatesLocationOfUser) { 43 server.track(aUser); 44 server.updateLocation(aUser, Location{38, -104}); 45 46 auto location = server.locationOf(aUser); 47 DOUBLES_EQUAL(38, location.latitude(), LocationTolerance); 48 DOUBLES_EQUAL(-104, location.longitude(), LocationTolerance); 49 } 50 51 TEST(AGeoServer, AnswersUnknownLocationForUserNotTracked) { 52 CHECK_TRUE(server.locationOf("anAbUser").isUnknown()); 53 } 54 55 TEST(AGeoServer, AnswersUnknownLocationForTrackedUserWithNoLocationUpdate) { 56 server.track(aUser); 57 CHECK_TRUE(server.locationOf(aUser).isUnknown()); 58 } 59 60 TEST(AGeoServer, AnswersUnknownLocationForUserNoLongerTracked) { 61 server.track(aUser); 62 server.updateLocation(aUser, Location(40, 100)); 63 server.stopTracking(aUser); 64 CHECK_TRUE(server.locationOf(aUser).isUnknown()); 65 }
好了,开始我们的testmain函数吧:
1 #include "CppUTest/CommandLineTestRunner.h" 2 3 int main(int argc, char** argv) { 4 return CommandLineTestRunner::RunAllTests(argc, argv); 5 }
全部的代码都已经好了,准备编译程序吧,为了方便编译,提供cmake文件:
1 project(Extras) 2 cmake_minimum_required(VERSION 2.6) 3 4 include_directories($ENV{CPPUTEST_HOME}/include) 5 link_directories($ENV{CPPUTEST_HOME}/lib) 6 7 add_definitions(-g -std=c++0x) 8 9 set(CMAKE_CXX_FLAGS "${CMAXE_CXX_FLAGS} -Wall") 10 set(sources 11 GeoServer.cpp 12 Location.cpp) 13 set(testSources 14 CppUTestExtensions.cpp 15 CppUTestExtensionsTest.cpp 16 GeoServerTest.cpp 17 LocationTest.cpp) 18 add_executable(utest testmain.cpp ${testSources} ${sources}) 19 20 target_link_libraries(utest CppUTest)
编译程序,运行结果如下图:
三、 程序分析及小结
做测试的时候,需要建立一个TEST_GROUP和TEST方法,TEST_GROUP的内部定义自己测试中需要用到的变量和一些自己的函数(变量和函数只有定义在这个里面,属于这一组的测试才能使用这些变量和函数),而且在TEST_GROUP中还可以继承两个CppUTest的函数:
1 void setup(){} //这个函数中对变量进行初始化 2 void teardown(){} //对一些变量进行销毁
TEST部分中就填入我们想要做的测试用例,CppUTest提供了很多的宏,如CHECK(bool),LONGS_EQUAL(excepted,actual)…等等宏,就行一些检测,而不需要去关心C++语言的类的那些问题,所以CppUTest也可以用于C语言。
CppUTest的关键设计之一就是容易添加和删除测试。想要运行测试,main函数是不可或缺的。
感谢大家阅读!