Testing the function in C

How to check how a function behaves on different data in C? Let's say there is a function that determines from three sides that it is a triangle.

void tr(float a, float b, float c) {
  if(a+b>c && b+c>a && a+c>b) { printf("Треугольник!"); }
}

I assume that we need a function that will call the tr() function with different data.

UPD. Amendment by the Council @VladD.

int tr(float a, float b, float c) {
  if(c<=0 || b<=0 || a<=0) { return 2; }
  if(a+b>c && b+c>a && a+c>b) { return 0; } else { return 1; }
}

Show an example of the test.

Author: 2202100, 2014-02-24

2 answers

Generally speaking, it is necessary to distinguish between blackbox and whitebox testing: does the testing code have the right to rely on knowledge of how the function works?

In your case, the function is not pure: it does not output anything, but changes the external object itself (writes to the console). This is not true, such functions cannot be tested. Rewrite the function so that it produces the result, and in what form and where to output it (and whether to output it at all), let the external one take care code.

When you redo it, you can test it. In any case, blackbox or whitebox, it is worth checking how the function works on different input data:

  1. Unpleasant special cases: if one of the side lengths is zero or negative, or a special value of NaN, or infinity, how does the function behave? What value does it return? Does it report invalid input data?
  2. The magnitudes of the sides satisfy the triangle inequality: reports whether the function that from the data of the three sides it is possible to build a triangle? Try a different order of arguments: will the answer change?
  3. The values of the sides do not satisfy the triangle inequality, one of the sides is equal to the sum of the other two. Does the function tell you that you can't build a triangle from these three sides? Try a different order of arguments: the sum is in the first place, in the second, and in the third.
  4. The values of the sides do not satisfy the triangle inequality, one of the sides is greater than the sum the other two. Does the function tell you that you can't build a triangle from these three sides?

Example of a test that implements check 3:

// где-то в начале программы: srand(time(NULL)); или что-то похожее

float getRandomFloat(float from, float to)
{
    assert(from < to);
    return from + (to - from) * (float)rand()/(float)RAND_MAX;
}

int getRandomInt(int from, int to)
{
    assert(from < to);
    return (int)(from + (to - from + 1) * (double)rand()/(double)RAND_MAX);
}

int test3()
{
    float addend1 = getRandomFloat(1, FLT_MAX/2);
    float addend2 = getRandomFloat(1, FLT_MAX/2);
    float sum = addend1 + addend2;
    if (tr(addend1, addend2, sum) != 0)
        return 0; // test failed
    if (tr(addend1, sum, addend2) != 0)
        return 0; // test failed
    if (tr(sum, addend1, addend2) != 0)
        return 0; // test failed
    return 1; // test passed
}

Example of a test that implements part of the checks in point 1:

#include <math.h>

int test1_NaN()
{
    float args[3];
    args[0] = getRandomFloat(1, FLT_MAX);
    args[1] = getRandomFloat(1, FLT_MAX);
    args[2] = getRandomFloat(1, FLT_MAX);

    int numberOfNanEntries = getRandomInt(1, 3);
    // тут надо бы выборку, но мне лень
    switch (numberOfNanEntries)
    {
    case 1:
        args[getRandomInt(0, 2)] = NAN;
        break;
    case 2:
        {
        int goodIdx = getRandomInt(0, 2);
        for (int i = 0; i < 2; i++) if (i != goodIdx) args[i] = NAN;
        }
    case 3:
        for (int i = 0; i < 2; i++) args[i] = NAN;
    }

    if (tr(args[0], args[1], args[2]) != 2)
        return 0; // test failed
    return 1; // test passed
}

Similar tests for item 2 (for example, if both terms are greater than FLT_MAX/2, the code should work correctly.


struct test_entry {
    const char* description;
    int (*test)();
} entries[] = {
    { "NaN arguments", test1_NaN },
    { "One argument is a sum of other two", test3 }
    // ...
};
const size_t number_of_entries = sizeof(entries) / sizeof(*entries);

int test_coordinator(char*** failed_test_descriptions)
{
    char** failed_descriptions = malloc(number_of_entries * sizeof(char*));
    int failed_curr_idx = 0;
    for (int i = 0; i < number_of_entries; i++)
    {
        if (!entries[i]->test())
            failed_descriptions[failed_curr_idx++] = entries[i].description;
    }
    if (!failed_curr_idx)
    {
        *failed_test_descriptions = NULL;
        free(failed_descriptions);
        return 1;
    }
    else
    {
        *failed_test_descriptions =
                realloc(failed_descriptions, failed_curr_idx * sizeof(char*));
        return 0;
    }
}
 3
Author: VladD, 2014-02-24 14:30:32

I am strongly against returning errors through the data. I profess the "Let it crush" programming philosophy. If such data should not be sent to the function, then it is better to find out right away!

#include <stdbool.h>
#include <assert.h>
#include <math.h>

bool is_triangle(double a, double b, double b)
{
    assert(isnormal(a) && isnormal(b) && isnormal(c));
    assert(a > 0 && b > 0 && c > 0);
    return (a + b > c) && (b + c > a) && (c + a > b);
}

In general, it would be good to choose some test framework, so as not to suffer. However, it does not make sense to test this particular function, because its entry matches the formula. It's like testing a math textbook. Except for a couple of trivial examples to implement, just to make sure we didn't make a typo.

#include <stdio.h>

int main(void)
{
    bool result = is_triangle(3, 4, 5)
               && is_triangle(3, 3, 3)
               && is_triangle(10, 10, 15)
               && is_triangle(119, 120, 169)
               && !is_triangle(100, 100, 1000);

    puts( result? "OK" : "FAIL");

    return 0;
}
 1
Author: VadimTukaev, 2014-04-22 15:30:37