有没有办法为使用scanf的函数编写单元测试?

7eumitmz  于 2023-04-29  发布在  其他
关注(0)|答案(3)|浏览(138)

我是一名计算机科学专业的学生,刚接触C,正在尝试为这个程序编写一个单元测试:

Board *get_board_sizes() {
    int row_count, column_count, mine_count = 0;
    Board *struct_to_return = (Board *)calloc(1, sizeof(Board));
    int rerun = 0;
    do {
        if (rerun == 1) {
            printf("Incorrect size");
        }
        rerun = 1;
        printf("Choose how many rows, columns, and mines you would like\n");
        printf("Note that maximum number of rows anc columns are 30 and maximum number of mines are rows*columns\n");
        scanf("%d %d %d", &row_count, &column_count, &mine_count);
        while (getchar() != '\n');
    } while ((row_count > 30 || row_count < 1) || (column_count > 30 || column_count < 1) || (mine_count < 1 || mine_count >= (row_count * column_count)));
    //while(getchar() != '\n');
    struct_to_return->row_count = row_count;
    struct_to_return->column_count = column_count;
    struct_to_return->mine_count = mine_count;
    return struct_to_return;
}

我的问题是有没有办法测试scanf函数?我的想法是,我也许可以将它应该扫描的数字保存到一个文件中,并尝试从那里获取它,但我正在努力将它放入单元测试中。
我想这样做:

TEST get_fitting_number_from_players() {
    char file[] = "tests/board_size/fitting_number";
    FILE *fp = fopen(file, "r");
    Board *size = get_board_sizes(fp);
    ASSERT(/*havent added yet*/);
}

但是我没有看到get_board_sizes函数能够从文件中获取数字。有什么办法可以做到这一点吗?

lmvvr0a8

lmvvr0a81#

有没有办法测试scanf函数?
是的,你可以从测试返回值开始:如果已经从用户读取了所有3个数字,则它必须是3。
函数中存在更多问题:

  • 您不检查calloc()故障。
  • 不检查scanf()的返回值:你在无效输入上有未定义的行为。
  • 循环while (getchar() != '\n');将永远不会停止一旦你到达文件的末尾。
  • 不要在do/while循环的while子句中填充所有值测试,而是使用for(;;)(aka *for ever循环 *)和带有有意义错误消息的显式测试。

以下是修改后的版本:

Board *get_board_sizes(void) {
    for (;;) {
        int c, count, row_count, column_count, mine_count;

        printf("Choose how many rows, columns, and mines you would like\n"
               "Note that the maximum number of rows and columns is 30\n"
               "and the maximum number of mines is rows*columns\n");
        // read user input
        count = scanf("%d %d %d", &row_count, &column_count, &mine_count);
        // discard the rest of the input line
        while ((c = getchar()) != EOF && c != '\n')
            continue;

        // check input validity
        if (count != 3) {
            printf("invalid input.\n");
        } else
        if (row_count < 1 || row_count > 30)
        ||  column_count < 1 || column_count > 30) {
            printf("invalid dimensions\n");
        } else
        if (mine_count < 1 || mine_count >= row_count * column_count) {
            printf("invalid number of mines\n");
        } else {
            // input is valid: allocate the board, initialize it and return it
            Board *board = (Board *)calloc(1, sizeof(*board));
            if (board == NULL) {
                fprintf(stderr, "allocation failure\n");
                return NULL;
            }
            board->row_count = row_count;
            board->column_count = column_count;
            board->mine_count = mine_count;
            return board;
        }
        // keep asking the user unless at end of file
        if (c == EOF) {
            fprintf(stderr, "Unexpected end of file\n");
            return NULL;
        }
    }
}

关于单元测试:您可以使用freopen()从文件重定向标准输入:

TEST get_fitting_number_from_players() {
    char file[] = "tests/board_size/fitting_number";
    freopen(file, "r", stdin);
    Board *size = get_board_sizes();
    ASSERT(/*havent added yet*/);
}
41ik7eoe

41ik7eoe2#

有没有办法为使用scanf的函数编写单元测试?
是的。
使用freopen(..., "r", stdin)stdin的阅读重定向到您选择的文件。在该文件中填充您想要的数据scanf(), getchar()等。接收并调用测试代码。

int test(size_t isz, const char *in) {
  const char *tmp = "t.txt";
  FILE *outf = fopen(tmp, "w");
  if (outf == NULL)
    return 1;
  if (fwrite(in, sizeof in[0], isz, outf) != isz)
    return 2;
  fclose(outf);
  if (freopen(tmp, "r", stdin) == NULL) {
    return 3;
  }

  // Perform your unit test.
  Board *size = get_board_sizes(fp);
  ASSERT(/*haven't added yet*/);
  return 0;
}

const char *data = "1 2 3\nNext line\n...\n";
if (test(strlen(data), data) == 0) {
  puts("Success");
} else {
  puts("Fail");
}
sg3maiej

sg3maiej3#

get_board_sizes函数进行单元测试的一种方法是修改函数,使其接受一个额外的参数来提供输入,而不是硬编码为从 stdin 读取。类似于:

Board *get_board_sizes(FILE *in) {
    /* ... */
    fscanf(in, "%d %d %d", &row_count, &column_count, &mine_count);
    /* ... */
}

然后在单元测试中,您可以为函数合成一个输入。使用cgreen进行单元测试,我编写了以下测试。在这段代码中:
1.我们创建临时文件作为get_board_sizes函数的输入。
1.我们用示例输入填充临时文件
1.我们倒回去。
1.我们将该文件作为输入参数传递给get_board_sizes
1.最后,我们验证我们得到了预期的响应

Ensure(GetBoard, parse_input) {
  char tempname[20];
  int fd;
  FILE *test_stdin;
  Board *board;

  sprintf(tempname, "stdinXXXXXX");
  fd = mkstemp(tempname);
  unlink(tempname);

  assert_that(fd, is_greater_than(-1));
  test_stdin = fdopen(fd, "r+");
  assert_that(test_stdin, is_not_null);

  fputs("10 15 5\n", test_stdin);
  fseek(test_stdin, 0, SEEK_SET);

  board = get_board_sizes(test_stdin);
  assert_that(board->row_count, is_equal_to(10));
  assert_that(board->column_count, is_equal_to(15));
  assert_that(board->mine_count, is_equal_to(5));
}

即使您使用的是不同的单元测试框架,其基本思想也是相同的。
在实际(非测试)代码中,您将调用get_board_sizes(stdin),而不是简单地调用get_board_sizes()
您可能需要考虑其他方法来重构get_board_sizes,以便为无效输入编写测试。

相关问题