如何对PostgreSQL C语言函数进行单元测试

时间:2019-04-02 17:18:08

标签: c postgresql unit-testing

我正在用C开发PostgreSQL的用户定义函数。该函数将用作用户定义集合的一部分。我正在关注User-defined AggregatesC-Language Funtions上的文档。我知道PostgreSQL安装提供的扩展构建基础结构(记录在here中)也提供了测试扩展的方法。但是,为了简化PostgreSQL扩展的开发,我想知道是否有任何方法可以使用常规C单元测试技术来测试用户功能。

我正在尝试使用CMocka进行单元测试。 PG_FUNCTION_ARGS中需要使用PG_FUNCTION_INFO_V1中的fmgr.hC宏来实现这些功能,这些宏将与PostgreSQL接口。这些宏趋向于抽象一些参数处理,结果使该功能变得模糊。结果,我很难在单元测试中调用我的PostgreSQL用户函数。我遇到的错误与预期的参数类型有关。我不确定如何通过宏为函数手动构造参数列表。以下是我尝试运行单元测试时遇到的GCC错误。

    gcc -fprofile-arcs -ftest-coverage -I '/usr/include/postgresql/10/server' ./tests/test_max_pos_min_neg.c -o ./build/tests/test_max_pos_min_neg
./tests/test_max_pos_min_neg.c: In function ‘test_get_max_pos_min_neg’:
./tests/test_max_pos_min_neg.c:21:25: warning: passing argument 1 of ‘get_max_pos_min_neg’ from incompatible pointer type [-Wincompatible-pointer-types]
     get_max_pos_min_neg((float4 *) init_cond, 10.0, -2.5, 7.5);
                         ^
In file included from ./tests/test_max_pos_min_neg.c:6:0:
./tests/../src/max_pos_min_neg.h:8:7: note: expected ‘FunctionCallInfo {aka struct FunctionCallInfoData *}’ but argument is of type ‘float4 * {aka float *}’
 Datum get_max_pos_min_neg(PG_FUNCTION_ARGS);
       ^~~~~~~~~~~~~~~~~~~
./tests/test_max_pos_min_neg.c:21:5: error: too many arguments to function ‘get_max_pos_min_neg’
     get_max_pos_min_neg((float4 *) init_cond, 10.0, -2.5, 7.5);
     ^~~~~~~~~~~~~~~~~~~
In file included from ./tests/test_max_pos_min_neg.c:6:0:
./tests/../src/max_pos_min_neg.h:8:7: note: declared here
 Datum get_max_pos_min_neg(PG_FUNCTION_ARGS);
       ^~~~~~~~~~~~~~~~~~~
./tests/test_max_pos_min_neg.c:24:5: warning: implicit declaration of function ‘assert_float_equal’; did you mean ‘assert_int_equal’? [-Wimplicit-function-declaration]
     assert_float_equal(init_cond[0], 10.0, EPSILON);
     ^~~~~~~~~~~~~~~~~~
     assert_int_equal
Makefile:59: recipe for target 'build/tests/test_max_pos_min_neg' failed
make: *** [build/tests/test_max_pos_min_neg] Error 1

这是我的源代码:

#include "postgres.h"
#include "fmgr.h"
#include "utils/array.h"

PG_MODULE_MAGIC;

Datum get_max_pos_min_neg(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(get_max_pos_min_neg);

// Computes the maximum positive and mininum negative sentiment scores.
Datum
get_max_pos_min_neg(PG_FUNCTION_ARGS)
{
    ArrayType *state_array;
    float4 *state;
    float4 pos, neg, score;

    state_array = PG_GETARG_ARRAYTYPE_P(0);
    pos = PG_GETARG_FLOAT4(1);
    neg = PG_GETARG_FLOAT4(2);
    score = PG_GETARG_FLOAT4(3);

    state = (float4 *) ARR_DATA_PTR(state_array);

    if (state[2] < score)
    {
        state[0] = pos;
        state[1] = neg;
        state[2] = score;
    }
    if (score < state[5])
    {
        state[3] = pos;
        state[4] = neg;
        state[5] = score;
    }
    PG_RETURN_ARRAYTYPE_P(state);
}

我的头文件:

#include "postgres.h"
#include "fmgr.h"
#include "utils/array.h"

Datum get_max_pos_min_neg(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(get_max_pos_min_neg);

我的单元测试:

#include "../src/max_pos_min_neg.h"
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include "cmocka.h"

const float4 EPSILON = 0.001;

void 
test_get_max_pos_min_neg(void **state)
{
    (void) state; /* unused */
    // This is the initial condition used by the SQL stored procedure
    float4 init_cond[] = {0, 0, -1, 0, 0, 100};

    get_max_pos_min_neg(init_cond, 10.0, -2.5, 7.5);

    // init_cond should now be {10.0, -2.5, 7.5, 10.0, -2.5, 7.5}
    assert_float_equal(init_cond[0], 10.0, EPSILON);
    assert_float_equal(init_cond[1], -2.5, EPSILON);
    assert_float_equal(init_cond[2], 7.5, EPSILON);
    assert_float_equal(init_cond[3], 10.0, EPSILON);
    assert_float_equal(init_cond[4], -2.5, EPSILON);
    assert_float_equal(init_cond[5], 7.5, EPSILON);
}

const struct CMUnitTest tests[] = { 
    cmocka_unit_test(test_get_max_pos_min_neg)
};

int 
main(void)
{
    return cmocka_run_group_tests(tests, NULL, NULL);
}

任何能为您提供如何对PostgreSQL之外的PostgreSQL C语言功能进行单元测试的帮助,将不胜感激。谢谢!

1 个答案:

答案 0 :(得分:2)

该错误告诉您,该参数实际上必须是FunctionCallInfo(隐藏在宏后面)。

要在PostgreSQL外部测试功能,您必须为PostgreSQL服务器(功能调用接口,以及您在代码中碰巧使用的所有服务器功能)构建一个模型。

这不仅非常困难,而且您将不得不使用每个新的服务器版本来更新模型(内部API有时会发生变化),而且您还面临引入一些新bug的不可忽视的风险。放入您的测试代码中,这会使此类测试的价值产生疑问。

我的建议是不要走这条路。

PostgreSQL支持单用户模式(postgres --single -D /data/directory dbname),您可以将其嵌入到测试框架中。您可以使用管道与服务器进行通信,一旦关闭PostgreSQL的标准输入,服务器就会关闭。