C++ Programming with TDD之二:CppUTest单元测试

在之前一篇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函数是不可或缺的。

感谢大家阅读!

原文地址:https://www.cnblogs.com/berlin-sun/p/cpputest.html