Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Info
titleThe source code can be find here:

https://gitlab.eufus.eupsnc.pl/bpogodzinskiach/ach-tutorials/-/tree/TDD-cpp/TDD-cpp/AdvancedCalculator


Info
titleNote

This project can be run in the same way as shown in basic use, please check this section: 2. Build the project  - CMake

...

Division and Division Errors - Testing Exceptions

This section is basically the same in terms of test suites as in the Calculator project, but was not covered on the previous page.

Inside The following test suites can be found in the tst directory in test_operation.cpp the following test suites can be found::

 Division operation - positive input

Code Block
languagecpp
titleTEST(DivideOperation, PositiveInput)
linenumberstrue
TEST(DivideOperation, PositiveInput) {
    // Integer arguments
    ASSERT_EQ(divide(10, 5), 2);
    ASSERT_FLOAT_EQ(divide(5, 10), 0.5);
    // Floating-point arguments
    ASSERT_FLOAT_EQ(divide(10.0f, 5.0f), 2.0f);
    ASSERT_FLOAT_EQ(divide(5.0f, 10.0f), 0.5f);
}

This is a very simple check that verifies that an input is producing the desired result. However, the division function itself has two forms, one for integer input data and one for floating point data. In such a situation, both cases should be checked.

Division operation - negative input

Code Block
languagecpp
titleTEST(DivideOperation, NegitiveInput)
linenumberstrue
TEST(DivideOperation, NegitiveInputNegativeInput) {
    ASSERT_EQ(divide(-10, -5), 2);
    ASSERT_FLOAT_EQ(divide(-5, -10), 0.5);
    // Floating-point arguments
    ASSERT_FLOAT_EQ(divide(-10.0f, -5.0f), 2.0f);
    ASSERT_FLOAT_EQ(divide(-5.0f, -10.0f), 0.5f);
}

Next (above) is the negative input verification, which has exactly the same operating logic as the previous test suite, but with negative signed arguments.

 Division operation - zero input

And last but not least in this section, below is the zero division error check:

Code Block
languagecpp
titleTEST(DivideOperation, ZerioInput)
linenumberstrue
TEST(DivideOperation, ZerioInputZeroInput) {
    EXPECT_THROW(divide(10, 0), std::overflow_error);
    EXPECT_THROW(divide(10.0f, 0.0f), std::overflow_error);
}

It is important to note that the assertion used is EXPECT_THROW, which has been specifically designed to catch and inspect a specific exception. It is also important to test for both types of input arguments.

 Tested division function

The divide function itself has a very simple body:

Code Block
languagecpp
titlefloat divide(int numerator, int denominator)
linenumberstrue
float divide(int numerator, int denominator){
    if(denominator == 0){
        throw std::overflow_error("Divide by zero exception!!! Verify the denominator value!");
    }
    return float(numerator) / float(denominator);
}

2. Testing whether the result is within range

...

The return type of this function is floating point because even if both of the arguments supplied are integers, the result need not be an integer. Basically the function takes two arguments and checks that the denominator is not zero in any case, otherwise an overflow_error exception will be thrown with the custom message.

Testing whether the result is within range

Checking whether the obtained result is within the expected range, it will be presented in two ways. This will be shown in the example get_pi() function, which task is to return only Pi with high decimal precision of 36 digits.

 Tested "get_pi" function

The get_pi() function body:

Code Block
languagecpp
titledouble get_pi()
linenumberstrue
double get_pi(){
    return 3.141592653589793238462643383279502884L;
}

 Range - Absolute Error

The EXPECT_NEAR assertion will be presented first. It is very elegant because it allows the determination of the expected absolute error between the values. It was mentioned earlier that the function returns a number with high decimal precision. However, a precision of up to 7 digits is expected. It is enough to provide the expected value and the maximum absolute error. 

Code Block
languagecpp
titleTEST(GetPiOperation, AbsoluteError)
linenumberstrue
TEST(GetPiOperation, AbsoluteError) {
    EXPECT_NEAR(get_pi(), 3.14159265, 1e-8);
    //EXPECT_NEAR(get_pi(), 3.14159265, 1e-9);  // Should FAIL
}

 Absolute error message

If a given absolute error is not selected correctly, as in the commented line above, the assertion will fail with the message shown below:

Code Block
languagebash
titleResult of the assertion: EXPECT_NEAR(get_pi(), 3.14159265, 1e-9)
linenumberstrue
The difference between get_pi() and 3.14159265 is 3.5897929073769319e-09, which exceeds 1e-9, where
get_pi() evaluates to 3.1415926535897931,
3.14159265 evaluates to 3.1415926500000002, and
1e-9 evaluates to 1.0000000000000001e-09.
[  FAILED  ] GetPiOperation.AbsoluteError (0 ms)

 Range - Expect (less, greater) assertion

The second way is to simply place two comparison value assertions as shown below:

Code Block
languagecpp
titleTEST(GetPiOperation, InRange)
linenumberstrue
TEST(GetPiOperation, InRange) {
    EXPECT_LE(get_pi(), 3.1416);
    EXPECT_GE(get_pi(), 3.1415);
}

Testing boundary conditions

This section presents several test suites for different boundary conditions. This section introduces several test suites for different boundary conditions based on the get_triangle_area() function. It takes two filenames as a parameter, one for the length of the base of the triangle and one for its height. It reads each line and calculates the correct area of the triangle for each line. The function expects to find both files inside data directory.

Check Type

At first, it would be appropriate to check that the return type is correct. In a given project, the function returns a vector of floats, and each of its elements is a calculated area.

Test suite below:

Code Block
languagecpp
titleTEST(GetTriangleArea, CheckType)
linenumberstrue
TEST(GetTriangleArea, CheckType) {
    std::string height_file = "triangle_height.txt";
    std::string base_file = "triangle_base.txt";
    EXPECT_EQ(typeid(std::vector<float>), typeid(get_triangle_area(base_file, height_file)));
}

The function under test is quite complex and takes up a lot of lines, so there's no room for it here, but it's worth learning how it works. In this test suite, it is important to provide valid file names, use typeid() to determine the argument type, and use the EXPECT_EQ assertion to  verify that it is the same as vector<float>.

Exceptions:

Negative Input

A triangle can only be positive lengths, so when the opposite happens, it should cover it somehow, like throwing an exception. Test suite below:

Code Block
languagecpp
titleTEST(GetTriangleArea, NegativeInput)
linenumberstrue
TEST(GetTriangleArea, NegativeInput) {
    std::string height_file = "negative_triangle_height.txt";
    std::string base_file = "triangle_base.txt";
    EXPECT_THROW(get_triangle_area(base_file, height_file), std::invalid_argument);
}

A purposely prepared file, named negative_triangle_height.txt, contains a value with a negative sign inside. The test suite is waiting to catch the invalid_argument exception.

The part of code responsible for raising an exception inside the function under test

Below is the part of the implementation inside the function that does this:

Code Block
languagecpp
firstline46
titleoperations.cpp :: get_triangle_area()
linenumberstrue
if(std::stof(line) <= 0){
                throw std::invalid_argument("The height value must be positive!");
            }

When reading a line from a file (because it is stored in a text file, the value must be converted using the stof() function), the condition must be that no number will be less than or equal to zero. Otherwise, an invalid_argument  exception will be thrown - it affects both files.

Range Error

It is expected to reach the same range of the number of arguments in both files, otherwise it would be impossible to calculate the area for all cases. Test suite below:

Code Block
languagecpp
titleTEST(GetTriangleArea, RangeError)
linenumberstrue
TEST(GetTriangleArea, RangeError) {
    std::string height_file = "extra_triangle_base.txt";
    std::string base_file = "triangle_base.txt";
    EXPECT_THROW(get_triangle_area(base_file, height_file), std::range_error);
}

If the files happen to have a different number of arguments, the test suite should catch a range_error exception.

The part of code responsible for raising an exception inside the function under test

Code Block
languagecpp
firstline56
titleoperations.cpp :: get_triangle_area()
linenumberstrue
if(bases.size() != heights.size()){
        throw std::range_error("Number of given bases and heights do not match!");
    }

Inside the function, after successfully reading the values from both files, the sizes of both containers that hold the read values are compared. If they do not match, a range_error exception is thrown.

Not Existing File

Sometimes it may happen that the given file cannot be opened due to a bad filename or nonexistent.

The test suite

Code Block
languagecpp
titleTEST(GetTriangleArea, NotExistingFile)
linenumberstrue
TEST(GetTriangleArea, NotExistingFile) {
    std::string height_file = "bad_file.txt";
    std::string base_file = "triangle_base.txt";
    EXPECT_THROW(get_triangle_area(base_file, height_file), std::runtime_error);
}

 Raising a run time error inside function under test

In this case, it is expected to throw a runtime_error exception with a message containing the name of the file that was not found.

Code Block
languagecpp
firstline32
titleoperations.cpp :: get_triangle_area()
linenumberstrue
if (base_file_stream.is_open()) {
     ...
}
else throw std::runtime_error("Could not open file: " + file_base);

Inside the function it just checks if the file could be opened and if not, throws a runtime_error exception with a message pointing to the name of the file not found.