• 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函数是不可或缺的。

    感谢大家阅读!

  • 相关阅读:
    Java 开发环境配置
    kettle脚本定时任务不执行
    python 列表之队列
    tensorflow训练过程中内存溢出
    关于 numpy.array和list之间的转换
    vscode 小笔记
    【python2/3坑】从gensim的Word2Vec.load()的中文vector模型输出时显示unicode码
    WingIDE用法笔记
    numpy.ndarray类型的数组元素输出时,保留小数点后4位
    tensorboard 用法
  • 原文地址:https://www.cnblogs.com/berlin-sun/p/cpputest.html
Copyright © 2020-2023  润新知