Программа на языке C, реализующая простое тестовое


Я написал шаблон для программы на языке C, реализующая простую структуру теста.

Если программа запускается с параметром "тест", все последующие аргументы будут интерпретироваться как последовательность тестов (без test_ префикс), чтобы быть выполненным. Если никакие другие параметры не определяются после первый параметр "тест", все функции по имени test_* будет выполняться в последовательности, указанной в TEST_CASES макро.

Если один из тестов не удается, то программа сообщит об этом и остановить. В тестовый случай сбоя All test cases completed successfully будет регистрироваться.

Обычно я хотел разделить программу на заголовок, общие определения функции и дела, но и за се, Я думал, что будет легче после объединенного кодекса.

Что вы думаете?

/*template.c - program template with a basic test framework*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/*To add a new test case for function foo(), add a line "X(foo) \" to the 
  TEST_CASES macro and then define the concrete test case function as -
  result_t *test_foo(result_t *result){
// Code to test foo() goes here. If the test is successful, set 
// result->rc = SUCCESS. Messages should be added to result->string using 
// the cat_message function
}
*/
#define TEST_CASES \
  X(init_string) \
X(cat_message) \
X(parse_tc) \
X(get_all_tc)

/*Return codes*/
typedef enum {
  SUCCESS,
  ERR_MALLOC,
  ERR_REALLOC,
  ERR_GENERAL
} rc_t;

/*String object : array of char with allocated size*/
typedef struct {
  size_t size;
  char *str;
} string_t;

/*Test results object : string object with return code*/
typedef struct {
  string_t *string;
  rc_t rc;
} result_t;

/*tc_func_t : test case function signature */
typedef result_t *(*tc_func_t)(result_t *result);

/*Test case execution sequence*/
typedef struct {
  int num_tc; /* number in test cases in the tc_funcs array */
  tc_func_t *tc_funcs; /*pointer to an array of test case functions*/
} tc_seq_t;

/*report_func_t : report results function signature*/
typedef rc_t (*report_func_t)(result_t *result);

/*Function prototypes*/
/*Initialise string object */
string_t *init_string(size_t size);
/*Concatenate a message to the string object*/
string_t *cat_message(const char *message, string_t *string);
/*Count number of test cases in TEST_CASES*/
int count_tc(void);
/*Create a test case set from the command line parameters*/
tc_seq_t *parse_tc(int argc, char **argv);
/*Create a test case set with all test cases specified in the TEST_CASES macro*/
tc_seq_t *get_all_tc(void);
/*Run all test cases in the test case sequence and report results*/
result_t *run_tests(tc_seq_t *tc_seq, report_func_t report_func, 
    result_t *result);
/*Write results to STDOUT*/
rc_t print_stdout(result_t *result);
/*Test case prototypes are generated by the X macro expansion*/
#define X(name) result_t *test_ ## name(result_t *result);
TEST_CASES
#undef X

string_t *init_string(size_t size){
  string_t *string = malloc(sizeof *string);
  /*If an error occurs, return a NULL pointer*/
  if (string == NULL) return NULL;
  string->str = malloc(size);
  if (string->str == NULL){
    free(string);
    return NULL;
  }
  string->size = size;
  *string->str = '\0';
  return string;
}

string_t *cat_message(const char *message, string_t *string){
  /*If the caller does supply the correct parameters, return the string object 
   * unchanged*/
  if (message == NULL || string == NULL || string->str == NULL) return string;
  while (strlen(message) >= string->size - strlen(string->str)){
    /*If the memory allocated to the string object is insufficient, keep 
     * doubling the allocation until it is enough to concancenate the message*/
    string->str = realloc(string->str, string->size * 2);
    if (string->str == NULL) return string; 
    string->size *= 2;
  }
  memcpy(string->str+strlen(string->str), message, strlen(message)+1);
  return string;
}

int count_tc(void){
  int num_tc = 0;
#define X(name) num_tc++;
  TEST_CASES
#undef X
    return num_tc;
}

tc_seq_t *parse_tc(int argc, char **argv){
  /*If the first parameter is not "test", return a NULL pointer*/
  if (argc == 1 || strcmp(argv[1], "test")) return NULL;
  /*if the first and only parameter is "test", return all test cases */
  if (argc == 2) return get_all_tc();
  /*return test cases specified in the parameters after "test"*/
  tc_seq_t *tc_set = malloc(sizeof *tc_set);
  tc_set->tc_funcs = malloc((argc - 2) * sizeof *tc_set->tc_funcs);
  /*If an error occurs, return a NULL pointer*/
  if (tc_set->tc_funcs == NULL) {
    free(tc_set);
    return NULL;
  }
  int tc_ind = 0;
  /*Fill the array testcase->tc_funcs[] with pointers to test functions*/
  for (int i = 2; i < argc; i++){
#define X(name) if (!strcmp(argv[i], #name)) \
    {tc_set->tc_funcs[tc_ind] = test_ ## name; tc_ind++; break;}
    TEST_CASES
#undef X
  }
  tc_set->num_tc = argc - 2;
  return tc_set;
}

tc_seq_t *get_all_tc(void){
  tc_seq_t *tc_set = malloc(sizeof *tc_set);
  if (tc_set == NULL) return NULL;
  tc_set->tc_funcs = malloc(count_tc() * sizeof *tc_set->tc_funcs);
  /*If an error occurs, return a NULL pointer*/
  if (tc_set->tc_funcs == NULL) {
    free(tc_set);
    return NULL;
  }
  tc_set->num_tc = count_tc();
  /*Fill the array testcase->tc_funcs[] with pointers to test functions*/
  int tc = -1;
#define X(name) tc++; tc_set->tc_funcs[tc] = test_ ## name;
  TEST_CASES
#undef X
    return tc_set;
}

result_t *run_tests(tc_seq_t *tc_seq, report_func_t report_func, 
    result_t *result){
  for (int tc_ind = 0; tc_ind < tc_seq->num_tc; tc_ind++) {
    result = tc_seq->tc_funcs[tc_ind](result);
    if (result->rc != SUCCESS) break;
  }
  if (result->rc == SUCCESS) cat_message(
      "All test cases completed successfully\n", result->string);
  free(tc_seq->tc_funcs);
  free(tc_seq);
  report_func(result);
  return result;
}

rc_t print_stdout(result_t *result){
  if (result == NULL) return ERR_GENERAL;
  if (result->string == NULL) return ERR_GENERAL;
  if (result->string->str == NULL) return ERR_GENERAL;
  printf("%s", result->string->str);
  printf("Return code: %d\n", result->rc);
  return SUCCESS;
}

int main(int argc, char **argv){
  string_t *string = init_string(1024);
  if (string == NULL) return ERR_MALLOC;
  string = cat_message("Start log\n", string);
  if (string == NULL) return ERR_REALLOC;
  result_t *result = malloc(sizeof *result);
  if (result == NULL) { 
    free(string->str);
    free(string);
    return ERR_GENERAL;
  } 
  result->string = string;
  result->rc = SUCCESS;
  tc_seq_t *tc_seq = parse_tc(argc, argv);
  if (tc_seq != NULL) result = run_tests(tc_seq, print_stdout, result);
  free(string->str);
  free(string);
  free(result);
}

result_t *test_init_string(result_t *result){
  string_t *string = init_string(10);
  if (string == NULL){
    result->rc = ERR_MALLOC;
    result->string = cat_message("init_string failed\n", result->string);
    return result;
  }
  if (string->size == 10 && !strcmp(string->str, "")){
    result->rc = SUCCESS;
  } else {
    result->rc = ERR_GENERAL;
    result->string = cat_message("init_string failed\n", result->string);
  }
  free(string->str);
  free(string);
  return result;
}

result_t *test_cat_message(result_t *result){
  string_t *string = init_string(10);
  if (string == NULL){
    result->rc = ERR_MALLOC;
    return result;
  }
  string = cat_message("foo", string);
  if (string == NULL){
    result->rc = ERR_REALLOC;
    result->string = cat_message("cat_message failed\n", result->string);
    return result;
  }
  string = cat_message("bar", string);
  if (string == NULL){
    result->string = cat_message("cat_message failed\n", result->string);
    result->rc = ERR_REALLOC;
    return result;
  }
  if (string->size == 10 && !strcmp(string->str, "foobar")){
    result->rc = SUCCESS;
  } else {
    result->string = cat_message("cat_message failed\n", result->string);
    result->rc = ERR_GENERAL;
  }
  free(string->str);
  free(string);
  return result;
}

result_t *test_parse_tc(result_t *result){
  char *argv[] = {"template", "test", "parce_tc", "get_all_tc"};
  tc_seq_t *tc = parse_tc(4, argv);
  if (tc == NULL){
    result->string = cat_message("parse_tc failed\n", result->string);
    result->rc = ERR_GENERAL;
    return result;
  }
  if (tc->num_tc == 2) {
    result->rc = SUCCESS;
  } else {
    result->rc = ERR_GENERAL;
    result->string = cat_message("parse_tc failed\n", result->string);
  }
  free(tc->tc_funcs);
  free(tc);
  return result;
}

result_t *test_get_all_tc(result_t *result){
  tc_seq_t *tc = get_all_tc();
  if (tc == NULL){
    result->rc = ERR_GENERAL;
    result->string = cat_message("get_all_tc failed\n", result->string);
    return result;
  }
  if (tc->num_tc == count_tc()) {
    result->rc = SUCCESS;
  } else {
    result->rc = ERR_GENERAL;
    result->string = cat_message("get_all_tc failed\n", result->string);
  }
  free(tc->tc_funcs);
  free(tc);
  return result;
}


134
1
задан 12 февраля 2018 в 09:02 Источник Поделиться
Комментарии
1 ответ


Что вы думаете?

Хорошо изложены последовательно, но немного туго.
Хороший основной обработки ошибок.
Хорошее использование sizeof *obj при распределении вычислений.
Хорошо используется (void) С такой функцией деклараций.
Скомпилируется без предупреждений.


Интерфейс непонятно

Безусловно, комментарий должен был быть "... абонент ничего не поставить ..."

Когда cat_message() возвращает, как определить, это ему удалось или не дано:
"Если абонент не поставить правильные параметры, возвращает объект String
* без изменений"?
Рекомендуется возвращать ошибку флага, значение или bool.

Пространство имен

Функции имен init_string(), cat_message(), count_tc() и немного намекнуть, что они работают вместе. Рекомендуем общий префикс.

Удвоение непонятно.

В cat_message()зачем повторно вызывать realloc() если код может определить минимальные потребности? Просто выделить один раз: максимум 2х бывший размер и размер нужен.

Inefficientcy

В cat_message()код повторно вычисляет strlen(string->str) и strlen(message) неся бегут каждой строке. Просто сохраните длины и повторного использования. А конкретно strlen(string->str) проблема, как код может называться realloc() и код не может взять на 2-й вызов strlen(string->str) возвращает одинаковую длину.


Незначительные

0 угол

init_string(0) приводит к УБ за счет *string->str = '\0';. Рекомендовать код корректно обработать этот угол. Может быть, вернуться NULL.

неподписанные математике оберните вокруг

Следите за переполнение беззнаковых математике с size_t. Как ниже должен работать для ОП, НО 2-ая форма является более устойчивой к неожиданным string->size, string->str пар.

// while (strlen(message) >= string->size - strlen(string->str))
while (strlen(message) + strlen(string->str) >= string->size )

Предположение

Код предполагает argc == 0 никогда не возникает, и так argv[1] не охраняется.

// if (argc == 1 || strcmp(argv[1], "test")) return NULL;
if (argc <= 1 || strcmp(argv[1], "test")) return NULL;


Позже.

Сокрытие информации

Код не деталь, что может быть *.h части и части реализации. С этой целью:

typedef struct {
size_t size;
char *str;
} string_t;

и другие 3 typedefтолько нужно объявить указатель типа с typedef struct string_t *string_t; а потом объявлять функции. Определение типа может возникает позже, а не в общественных *.h раздел.

Сколько rc_t?

Часто код должен оценить диапазон перечислимого типа. Полезно добавить _N как в прошлый раз.

/*Return codes*/
typedef enum {
SUCCESS,
ERR_MALLOC,
ERR_REALLOC,
ERR_GENERAL,
ERR_N // add
} rc_t;

Неуникальные сообщения об ошибках

cat_message("cat_message failed\n", result->string); использовал 3 раза. Может cat_message("cat_message failed 1\n", result->string);и т. д. чтобы помочь отличить сообщила об ошибке.

1
ответ дан 13 февраля 2018 в 09:02 Источник Поделиться