c++unit-testinggoogletestgooglemockglog

Detecting a glog output in a Google UnitTest


The following is a function I have in my source file

cv::Mat* Retina::Preprocessing::create_mask(const cv::Mat *img, const uint8_t threshold) {

    LOG_IF(ERROR, img->empty()) << "The input image is empty. Terminating Now!!";

    cv::Mat green_channel(img->rows(), img->cols(), CV_8UC1); /*!< Green channel. */

    /// Check number of channels in img and based on that find out the green channel.
    switch (img->channels()){
        /// For 3 channels, it is an RGB image and we use cv::mixChannels().
        case(3): {
            int from_to[] =  {1,0};
            cv::mixChannels(img, 1, &green_channel, 1, from_to, 1);
            break;
        }
        /// For a single channel, we assume that it is the green channel.
        case(1): {
            green_channel = *img;
            break;
        }
        /// Otherwise we are out of clue and throw an error.
        default: {
            LOG(ERROR)<<"Number of image channels found = "<< img->channels()<<". Terminating Now!!";
            return nullptr; /*!< (unreachable code) Only given for completion */
        }

    }

    cv::Mat mask(img->rows(), img->cols(), CV_8UC1);/*!< Empty mask image */

    if (threshold == 0)/// Otsu's threshold is used
        cv::threshold(green, mask, threshold, 255, CV_THRESH_BINARY | CV_THRESH_OTSU);
    else /// We can use the provided threshold value
        cv::threshold(green, mask, threshold, 255, CV_THRESH_BINARY);

    LOG_IF(ERROR, mask.empty())<< "After thresholding, image became empty. Terminating Now!!";



    std::vector<std::vector<cv::Point>> contours;

    /// Get the contours in the binary mask.
    cv::findContours(mask, *contours, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
    size_t max_index{};
    double prev_area{};
    size_t index{};

    /// Lambda function for a convenient one-liner std::for_each. SEE BELOW.
    lambda_max_index = [max_index, prev_area, index] (std::vector<cv::Point> c){
        double new_area { cv::contourArea(c) };
        max_index = (new_area > prev_area) ? max_index = index : max_index;
        ++index;
    };

    /// For each contour compute its area, and over the loop find out the largest contour.
    std::for_each(contours.begin(), contours.end(), lambda_max_index());

    /// Iterate over each point and test if it lies inside the contour, and drive in it.
    for(size_t row_pt = 0; row_pt < mask_out.rows(); ++row_pt){
        for(size_t col_pt = 0; col_pt < mask_out.cols(); ++col_pt){
            if (cv::pointPolygonTest(contours[max_index], cv::Point(row_pt, col_pt))>=0)
                mask[row_pt, col_pt] = 255;
            else
                mask[row_pt, col_pt] = 0;
        }
    }
    return &mask;
}

The following is a google Unit Test file I have written. This is my very first attempt with GoogleTest.

#include"PreProcessing.hxx"
#include<opencv2/highgui/highgui.hpp>
#include<gtest/gtest.h>

TEST(Fundus_Mask_Creation, NO_THRESHOLD) {

    cv::Mat img(cv::imread(std::string("../data/test_img.jpg")));

    cv::Mat* mask = Retina::Preprocessing::create_mask(&img);

    ASSERT_TRUE(!(mask->empty()));

    std::string winname("Input Image");
    cv::namedWindow(winname);

    cv::imshow(winname, img);
    cv::waitKey(800);

    cv::destroyWindow(winname);

    winname = "Fundus Mask";
    cv::namedWindow(winname);

    cv::imshow(winname, *mask);
    cv::waitKey(800);
}

    TEST(Fundus_Mask_Creation, THRESHOLD) {

        cv::Mat img(cv::imread(std::string("../data/test_img.jpg")));

        std::uint8_t threshold{10};
        cv::Mat* mask = Retina::Preprocessing::create_mask(&img, threshold);

        ASSERT_TRUE(!(mask->empty()));

        std::string winname("Input Image");
        cv::namedWindow(winname);

        cv::imshow(winname, img);
        cv::waitKey(800);

        cv::destroyWindow(winname);

        winname = "Fundus Mask";
        cv::namedWindow(winname);

        cv::imshow(winname, *mask);
        cv::waitKey(800);
    }

I would also like to test for the case, where glog logs that "Number of image channels found = "<< img->channels()<<". Terminating Now!!!";

How can I write a unit test, which would run successfully (i.e the original source file would log a FATAL error), when I provide it an input that leads to the above message correctly logged ?


Solution

  • You need mocking (googlemock) to achieve that. To enable mocking, create a thin wrapper class for glog calls, along with an interface that this class will inherit from (you need this to enable mocking). You can use this as a guideline:

    class IGlogWrapper {
    public:
        ...
        virtual void LogIf(int logMessageType, bool condition, const std::string &message) = 0;
        ...
    };
    
    class GlogWrapper : public IGlogWrapper {
    public:
        ...
        void LogIf(int logMessageType, bool condition, const std::string &message) override {
            LOG_IF(logMessageType, condition) << message;
        }
        ...
    };
    

    Now create a mock class:

    class GlogWrapperMock : public IGlogWrapper {
    public:
        ...
        MOCK_METHOD3(LogIf, void(int, bool, const std::string &));
        ...
    };
    

    and make your Retina::Preprocessing class hold a pointer to IGlogWrapper and make the logging calls through this interface. Now you can use dependency injection to pass to Retina::Preprocessing class a pointer to a real logger class GlogWrapper that will use glog, while in tests you pass a pointer to a mock object (instance of GlogWrapperMock). This way you can create an image in a test that will have two channels, and set expectations on this mock object to call function LogIf in that case. The following example uses a public method to inject a logger to be used:

    using namespace testing;
    
    TEST(Fundus_Mask_Creation, FAILS_FOR_TWO_CHANNEL_IMAGES) {
    
        // create image with two channels
        cv::Mat img( ... );
    
        GlogWrapperMock glogMock;
        Retina::Preprocessing::setLogger(&glogMock);
    
        std::string expectedMessage = "Number of image channels found = 2. Terminating Now!!";
        EXPECT_CALL(glogMock, LogIf(ERROR, _, expectedMessage).WillOnce(Return());
    
        cv::Mat* mask = Retina::Preprocessing::create_mask(&img);
    
        // Make additional assertions if necessary
        ...
    }
    

    For more info about mocking check the docs: https://github.com/google/googletest/blob/master/googlemock/docs/ForDummies.md#getting-started Also, please note that the two unit tests you've posted do not make much sense as you are loading an image from disk and also making additional calls after the assertion. Unit test should be concise, fast and insulated from the environment. I would suggest additional reading about unit tests, the internet is full of resources. Hope this helps!