paizaラーニングレベルアップ問題集の静的メンバをやってみました。
問題
提出コード
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
int main() {
int N, k;
scanf("%d %d", &N, &k);
int A[N];
int sum[N];
bool B[N];
for (int i = 0; i < N; i++) {
scanf("%d", &A[i]);
sum[i] = 0;
B[i] = 0;
}
int C = 0;
while (k--) {
int n;
char s[] = "softdrink";
scanf("%d %s", &n, s);
n -= 1;
if (!strcmp(s, "A")) {
printf("%d\n", sum[n]);
C++;
} else if (!strcmp(s, "0")) {
if (A[n] >= 20) {
sum[n] += 500;
B[n] = true;
}
} else {
int m;
scanf("%d", &m);
if (!strcmp(s, "food")) {
if (B[n]) {
sum[n] += (m - 200);
} else {
sum[n] += m;
}
} else if (!strcmp(s, "softdrink")) {
sum[n] += m;
} else if (!strcmp(s, "alcohol")) {
if (A[n] >= 20) {
sum[n] += m;
B[n] = true;
}
}
}
}
printf("%d\n", C);
return 0;
}
「クラス・構造体メニュー」なのにクラスも構造体も使っていませんが、まずはmain関数に全ての処理を詰め込んで、「AC(Accepted)なら正義」という方針で書きました。
ちなみに、char s[] = "softdrink";で、入力されうる最大文字配列領域を確保しております。
提出しないコード
ソースファイル分割
ここからは、居酒屋で働きながらクラスの勉強をしていた「あなた」と一緒に、お客さんをクラスに見立ててコーディングしていきたいと思います。
競技プログラミングでは分割したファイルを提出することはできませんが、ここではクラスごとにファイルを分割することをしてみたいと思います。
複数人で開発する時はソースコードを分割して、最後に結合させますよね。
客クラス
まずは「お客さん」クラスです。お客さんの行動は
- 食事を頼む
- ソフトドリンクを頼む
- お酒を頼む
- ビールを頼む
- 会計をして退店する
とあるので、これらをメソッドとして(関数ポインタで)実装します。
また、お客さんが退店する場合はそのお客さんの会計を出力するため、会計金額を構造体に入れます。一例として、ヘッダファイルではプライベート変数用の構造体を前方宣言しておき、ソースファイルでプライベート変数を実装してみます。
クラスの継承のヒントの通り、注文について、20歳未満のお客さんにできて、20歳以上のお客さんにできないことはないので、20歳未満のお客さんのクラスを作成して、それを継承して20歳以上のお客さんのクラスを作成することで効率よく実装することができます。
#ifndef CUSTOMER_H_
#define CUSTOMER_H_
#include <stdint.h>
#include <stdbool.h>
typedef struct __Customer _Customer;
typedef struct Customer {
_Customer *_customer;
void (*set_amount)(struct Customer*, uint32_t);
uint32_t (*get_amount)(struct Customer*);
void (*take_food)(struct Customer*, uint32_t);
void (*take_softdrink)(struct Customer*, uint32_t);
void (*take_alcohol)(struct Customer*, uint32_t);
void (*take_beer)(struct Customer*);
void (*accounting)(struct Customer*);
} Customer;
bool Customer_init(Customer*);
Customer* new_Customer(void);
size_t get_num_of_left(void);
void del_Customer(Customer**);
#endif /* CUSTOMER_H_ */
#include <stdio.h>
#include <stdlib.h>
#include "Customer.h"
const uint32_t BEER_PRICE = 500U;
static size_t num_of_left = 0;
struct __Customer {
uint32_t amount;
};
static void Customer_set_amount(Customer *self, uint32_t value) {
self->_customer->amount = value;
}
static uint32_t Customer_get_amount(Customer *self) {
return self->_customer->amount;
}
static void Customer_take_food(Customer *self, uint32_t price) {
self->set_amount(self, self->get_amount(self) + price);
}
static void Customer_take_softdrink(Customer *self, uint32_t price) {
self->set_amount(self, self->get_amount(self) + price);
}
static void Customer_take_alcohol(Customer *self, uint32_t price) {
(void) self;
(void) price;
}
static void Customer_take_beer(Customer *self) {
self->take_alcohol(self, BEER_PRICE);
}
static void Customer_accounting(Customer *self) {
printf("%u\n", (unsigned) self->get_amount(self));
num_of_left++;
}
bool Customer_init(Customer *customer) {
if (!(customer->_customer = (_Customer*) malloc(sizeof(_Customer)))) return false;
customer->_customer->amount = 0;
customer->set_amount = Customer_set_amount;
customer->get_amount = Customer_get_amount;
customer->take_food = Customer_take_food;
customer->take_softdrink = Customer_take_softdrink;
customer->take_alcohol = Customer_take_alcohol;
customer->take_beer = Customer_take_beer;
customer->accounting = Customer_accounting;
return true;
}
Customer* new_Customer(void) {
Customer *customer = NULL;
if (!(customer = (Customer*) malloc(sizeof(Customer)))) return NULL;
if (!Customer_init(customer)) {
del_Customer(&customer);
return NULL;
}
return customer;
}
size_t get_num_of_left(void) {
return num_of_left;
}
void del_Customer(Customer **customer) {
if (*customer) {
if ((*customer)->_customer) {
free((*customer)->_customer);
(*customer)->_customer = NULL;
}
free(*customer);
*customer = NULL;
}
}
成人クラス
次に、20歳以上のお客さんのクラスを作成を作成します。
20歳未満のお客さんとの違いは
- お酒(ビールを含む)を頼むことができる
- お酒を頼んだ場合、以降の全ての食事の注文が毎回200円引きになる
- 但し、200円未満の注文については今回は考慮対象外
ので、そのように関数ポインタを付け替えます。
#ifndef ADULT_H_
#define ADULT_H_
#include "Customer.h"
typedef struct __Adult Adult;
Adult* new_Adult(void);
#endif /* ADULT_H_ */
#include <stdlib.h>
#include <stdbool.h>
#include "Adult.h"
const uint32_t DISCOUNT_PRICE = 200U;
struct __Adult {
Customer super;
bool alcohol;
void (*take_food)(struct Customer*, uint32_t);
};
static void Adult_take_food(Customer *self, uint32_t price) {
Adult *adult = (Adult*) self;
if (adult->alcohol) {
adult->take_food(self, price - DISCOUNT_PRICE);
} else {
adult->take_food(self, price);
}
}
static void Adult_take_alcohol(Customer *self, uint32_t price) {
Adult *adult = (Adult*) self;
adult->alcohol = true;
self->set_amount(self, self->get_amount(self) + price);
}
bool Adult_init(Adult *adult) {
if (!Customer_init(&adult->super)) return false;
adult->alcohol = false;
adult->take_food = adult->super.take_food;
adult->super.take_food = Adult_take_food;
adult->super.take_alcohol = Adult_take_alcohol;
return true;
}
Adult* new_Adult(void) {
Adult *adult = NULL;
if (!(adult = (Adult*) malloc(sizeof(Adult)))) return NULL;
if (!Adult_init(adult)) {
del_Customer((Customer**) &adult);
return NULL;
}
return adult;
}
居酒屋クラス
今回、
-
main関数で受け取った入力値はすぐにクラスや関数の引数として渡したい - 出力値はメソッドや関数に任せて、
main関数は戻り値を出力するだけにしたい
という方針(mainはなるべく入出力専用)のもと、居酒屋クラスを作成し、入力値をすぐ居酒屋クラスのメソッドの引数として渡せるようにしたいと思います。
(標準入出力以外の入出力に移植したり、#ifndef NDEBUGを用いてテスト用の引数を渡したりするのに都合がいいと思います)
主に
- お客さんの年齢を受け取ってお客さんインスタンスを作成する
- 頼んだお客さんの番号、注文の種類、値段を受け取って注文を受け付ける
メソッドを作成します。但し、$n>N$(実装では添字が1つズレてn>=N)の場合や、不正な注文文字列が入ってきたときのエラーチェックをしておきます。
#ifndef PUB_H_
#define PUB_H_
#include <stdbool.h>
#include <stdint.h>
#include "Customer.h"
typedef struct __Pub _Pub;
typedef struct Pub {
_Pub *_pub;
Customer* (*add_customer)(struct Pub*, uint8_t);
bool (*order)(struct Pub*, size_t, const char*, uint32_t);
} Pub;
Pub* new_Pub(void);
void del_Pub(Pub**);
#endif /* PUB_H_ */
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <errno.h>
#include "Pub.h"
#include "Adult.h"
const uint8_t ADULT_AGE = 20;
struct __Pub {
size_t size;
Customer **customers;
};
static Customer* Pub_add_customer(Pub *self, uint8_t age) {
errno = 0;
size_t n = self->_pub->size;
Customer** tmp = (Customer**) realloc(self->_pub->customers, sizeof(Customer*) * (n + 1));
if (!tmp) {
while (n--) {
del_Customer(&(self->_pub->customers[n]));
}
self->_pub->size = 0;
free(self->_pub->customers);
self->_pub->customers = NULL;
return NULL;
}
self->_pub->customers = tmp;
self->_pub->customers[n] = (age < ADULT_AGE) ? new_Customer() : (Customer*) new_Adult();
if (self->_pub->customers[n] == NULL) return NULL;
self->_pub->size += 1;
return self->_pub->customers[n];
}
static bool Pub_order(Pub *self, size_t n, const char* s, uint32_t m) {
errno = 0;
if (n >= self->_pub->size) {
errno = ERANGE;
return false;
} else if (!s) {
errno = EINVAL;
return false;
}
Customer *customer = self->_pub->customers[n];
if (!strcmp(s, "food")) {
customer->take_food(customer, m);
} else if (!strcmp(s, "softdrink")) {
customer->take_softdrink(customer, m);
} else if (!strcmp(s, "alcohol")) {
customer->take_alcohol(customer, m);
} else if (!strcmp(s, "0")) {
customer->take_beer(customer);
} else if (!strcmp(s, "A")) {
customer->accounting(customer);
} else {
errno = EINVAL;
return false;
}
return true;
}
Pub* new_Pub(void) {
Pub *pub = NULL;
if (!(pub = (Pub*) malloc(sizeof(Pub)))) return NULL;
if (!(pub->_pub = (_Pub*) malloc(sizeof(_Pub)))) {
del_Pub(&pub);
return NULL;
}
pub->_pub->size = 0;
if (!(pub->_pub->customers = (Customer**) malloc(sizeof(Customer*)))) {
del_Pub(&pub);
return NULL;
}
pub->add_customer = Pub_add_customer;
pub->order = Pub_order;
return pub;
}
void del_Pub(Pub **pub) {
if (*pub) {
if ((*pub)->_pub) {
size_t *n = &((*pub)->_pub->size);
if ((*pub)->_pub->customers) {
while (*n) {
*n -= 1;
del_Customer(&((*pub)->_pub->customers[*n]));
}
free((*pub)->_pub->customers);
(*pub)->_pub->customers = NULL;
} else {
*n = 0;
}
free((*pub)->_pub);
(*pub)->_pub = NULL;
}
free(*pub);
*pub = NULL;
}
}
入力チェック
問題を解くだけならば、入力値は制約条件どおりであることが保証されていますが、実際は何が入力されるか分かりません。正の整数を入力すべきところにゼロ、小数、マイナス、数字以外の文字、何も入ってこないことだってあります。(例えて言うと、湯沸かしポットやケトルに生卵を入れる人がいるとかいないとか)実務では入力チェックは必須です。
今回は
- 他にも使えそうなチェック、他
- この問題の制約条件をチェック
する関数に分けておきます。
非負整数チェック、等
#ifndef UTIL_H_
#define UTIL_H_
#include <stdint.h>
#include <stdbool.h>
char* chomp(char*);
bool parse_ubyte(const char*, uint8_t*);
bool parse_ushort(const char*, uint16_t*);
bool parse_uint(const char*, uint32_t*);
bool parse_ulong(const char*, size_t*);
#endif /* UTIL_H_ */
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "util.h"
char* chomp(char *str) {
if (!str) return NULL;
size_t len = strlen(str);
while (len--) {
if (isspace(str[len])) {
str[len] = '\0';
} else {
break;
}
}
return str;
}
static bool is_positive_number(const char *s) {
if (!(s && *s)) return false;
for (const char *c = s; *c; c++) {
if (!isdigit(*c)) return false;
}
return true;
}
static bool parse_unsigned(const char *str, uintmax_t *n, uintmax_t max) {
errno = 0;
if (!is_positive_number(str)) {
errno = EINVAL;
return false;
}
char *s = NULL;
unsigned long long ull = strtoull(str, &s, 10);
if (errno) {
return false;
} else if (s && *s) {
errno = EINVAL;
return false;
} else if (ull > max) {
errno = ERANGE;
return false;
}
*n = (uintmax_t)ull;
return true;
}
bool parse_ubyte(const char *str, uint8_t *n) {
uintmax_t m = 0;
if (!parse_unsigned(str, &m, UINT8_MAX)) return false;
*n = (uint8_t)m;
return true;
}
bool parse_ushort(const char *str, uint16_t *n) {
uintmax_t m = 0;
if (!parse_unsigned(str, &m, UINT16_MAX)) return false;
*n = (uint16_t)m;
return true;
}
bool parse_uint(const char *str, uint32_t *n) {
uintmax_t m = 0;
if (!parse_unsigned(str, &m, UINT32_MAX)) return false;
*n = (uint32_t)m;
return true;
}
bool parse_ulong(const char *str, size_t *n) {
uintmax_t m = 0;
if (!parse_unsigned(str, &m, SIZE_MAX)) return false;
*n = (size_t)m;
return true;
}
制約条件読込&チェック
今回は
- $1\le N\le 1000$
- $1\le K\le 1000$
- $1\le a_i\le 100$ ($1\le i\le N$)
- $1\le n_i\le N$ ($1\le i\le K$)
- $S_i$は
"food","softdrink","alcohol","0","A"のいずれか - $S_i$が
"food","softdrink","alcohol"のいずれかの場合- $300\le m_i\le 5000$ ($1\le i\le K$)
という制約条件がありますので、$1\le n_i\le N$以外のチェック関数を集めてみました。尚、$S_i$のチェックはPub.cとのダブルチェックになります。
#ifndef INPUT_H_
#define INPUT_H_
#include <stdbool.h>
#include <stdint.h>
bool read_numbers(const char*, size_t*, uint16_t*);
bool read_age(const char*, uint8_t*);
bool read_query(const char*, size_t*, char*, uint32_t*);
#endif /* INPUT_H_ */
#include <string.h>
#include <errno.h>
#include "input.h"
#include "util.h"
bool read_numbers(const char *str, size_t *n, uint16_t *k) {
if (!str || !n || !k) {
errno = EINVAL;
return false;
}
errno = 0;
*n = 0;
*k = 0;
char s[strlen(str) + 1];
strcpy(s, str);
if (!parse_ulong(strtok(s, " "), n)) {
return false;
}
if (*n < 1 || 1000 < *n) {
errno = ERANGE;
return false;
}
if (!parse_ushort(strtok(NULL, " "), k)) {
return false;
}
if (*k < 1 || 1000 < *k) {
errno = ERANGE;
return false;
}
if (strtok(NULL, " ")) {
errno = EINVAL;
return false;
}
return true;
}
bool read_age(const char *str, uint8_t *a) {
if (!str || !a) {
errno = EINVAL;
return false;
}
errno = 0;
*a = 0;
if (!parse_ubyte(str, a)) {
return false;
}
if (*a < 1 || 100 < *a) {
errno = ERANGE;
return false;
}
return true;
}
bool read_query(const char *str, size_t *n, char *s, uint32_t *m) {
if (!str || !n || !s || !m) {
errno = EINVAL;
return false;
}
errno = 0;
*n = 0;
*s = '\0';
*m = 0;
char t[strlen(str) + 1];
strcpy(t, str);
if (!parse_ulong(strtok(t, " "), n)) {
return false;
}
if (*n < 1 || 1000 < *n) {
errno = ERANGE;
return false;
}
char *u = NULL;
if (!(u = strtok(NULL, " "))) {
errno = EINVAL;
return false;
}
strcpy(s, u);
if (!strcmp(s, "food") || !strcmp(s, "softdrink") || !strcmp(s, "alcohol")) {
if (!parse_uint(strtok(NULL, " "), m)) {
return false;
}
if (*m < 300 || 5000 < *m) {
errno = ERANGE;
return false;
}
if (strtok(NULL, " ")) {
errno = EINVAL;
return false;
}
} else if (!strcmp(s, "0") || !strcmp(s, "A")) {
if (strtok(NULL, " ")) {
errno = EINVAL;
return false;
}
} else {
errno = EINVAL;
return false;
}
return true;
}
エントリポイント
そして、main関数を書き換えます。
scanf関数の代わりに、標準入力から行単位で受け取って入力チェックをして、OKならば受け取った入力値をすぐ居酒屋インスタンスのメソッドに渡し、NGならばエラーメッセージを出力して(英語がテキトーだが)、直ちにエラーコードを返してプログラムを終了させます。
(改訂)
エラーメッセージ出力にperror関数を使用することにしました。但し、perror関数を使用するとerrnoがEINVALに設定されてしまうようなので、perror関数を使う前にerrnoを別の変数に預けておきます。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include "util.h"
#include "input.h"
#include "Pub.h"
#include "Customer.h"
int main() {
char line[] = "1000 softdrink 5000\n";
size_t N = 0;
uint16_t K = 0;
if (!read_numbers(chomp(fgets(line, sizeof(line), stdin)), &N, &K)) {
int err_no = errno;
if (err_no == EINVAL) {
if (!N) perror("The number of customers must be a positive integer.");
else if (!K) perror("The number of orders must be a positive integer.");
else perror(NULL);
} else if (err_no == ERANGE) {
if (!N) perror("The number of customers must be a positive integer.");
else if (N > 1000) perror("The number of customers must be 1000 or less.");
else if (!K) perror("The number of orders must be a positive integer.");
else if (K > 1000) perror("The number of orders must be 1000 or less.");
else perror(NULL);
} else {
perror(NULL);
}
return err_no;
}
Pub *pub = new_Pub();
Customer *customers[N];
for (size_t i = 0; i < N; i++) {
uint8_t a = 0;
if (!read_age(chomp(fgets(line, sizeof(line), stdin)), &a)) {
del_Pub(&pub);
int err_no = errno;
if (err_no == EINVAL) {
perror("The age must be a positive integer.");
} else if (err_no == ERANGE) {
if (!a) perror("The age must be a positive integer.");
else perror("The age must be 100 or less.");
} else {
perror(NULL);
}
return err_no;
}
if (!(customers[i] = pub->add_customer(pub, a))) {
del_Pub(&pub);
int err_no = errno;
perror(NULL);
return err_no;
}
}
while (K--) {
size_t n = 0;
char s[] = "softdrink";
uint32_t m = 0;
if (!read_query(chomp(fgets(line, sizeof(line), stdin)), &n, s, &m)) {
del_Pub(&pub);
int err_no = errno;
if (err_no == EINVAL) {
perror(NULL);
fprintf(stderr, "Usage: n <food|softdrink|alcohol> m\n");
fprintf(stderr, " or n <0|A>\n");
fprintf(stderr, "where n: The customer number\n");
fprintf(stderr, " and m: The price of the order.");
} else if (err_no == ERANGE) {
if (!n) perror("The customer number must be a positive integer.");
else if (n > 1000) perror("The customer number must be 1000 or less.");
else if (m < 300 || 5000 < m) perror("The price must be an integer between 300 and 5000.");
else perror(NULL);
} else {
perror(NULL);
}
return err_no;
}
if (!pub->order(pub, n - 1, s, m)) {
del_Pub(&pub);
int err_no = errno;
if (err_no == EINVAL) {
perror("The type of order must be either food, softdrink, alcohol, 0, or A.");
} else if (err_no == ERANGE) {
perror("The customer number must be less than the number of customers.");
} else {
perror(NULL);
}
return err_no;
}
}
del_Pub(&pub);
printf("%zu\n", get_num_of_left());
return EXIT_SUCCESS;
}
テストコード
「あなた」がお客さんをクラスに見立てて実装している間、テスト実施担当者がテストケース、場合によっては意地悪なテストコードを作成しているかもしれません。
最初は、バグが発生した際「誰が悪いんだ(怒)」となりがちですが、後々自信をもってリファクタリングやメンテナンスを行うことができるでしょう。
今回はminunitを使うことにします。
minunit
#ifndef MINUNIT_H_
#define MINUNIT_H_
#define mu_assert(message, test) do { if (!(test)) return message; } while (0)
#define mu_run_test(test) do { char *message = test(); tests_run++; if (message) return message; } while (0)
extern int tests_run;
#endif /* MINUNIT_H_ */
「お客さん」クラスのテスト
コンストラクタを含め、一通りのメソッドを通します。
- 初めての注文(会計金額が0の状態)の時と、2回目以降の注文(会計金額が正の状態)の時をテストします
- 会計金額はちゃんと足し合わされていますよね?
- 未成年のお客さんがお酒(ビールを含む)を頼もうとした場合、その注文は取り消されることに注意します
- 今回は(ある程度)任意の値でテストしたいので、敢えて
rand関数を使っておりますが、テストコードにバグを潜ませることになりますので、テストコードの場合は定数をハードコーディングでよいと思います- 但し、異なる値を使う方がよいでしょう。例:ソフトドリンクの値段が300円なら食事は1000円でテストする、等
- 今回のように、テストコードに
300 + rand() % 4701やexpected = amount + priceという数式があると、上司から「これもテスト(テストコードのテスト)しなさいっ!」と言われるかも、です
- テスト結果を受け取ってから、メモリを解放するようにします。順番を間違えると、テストに失敗した場合、メモリを解放せずに試験停止、となってしまします
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "minunit.h"
#include "Customer.h"
int tests_run = 0;
char msg[] = "Error: expected: <18446744073709551615> but was: <18446744073709551616>"; //領域確保用の仮のエラーメッセージ
static char* message_u(uint32_t expected, uint32_t actual) {
sprintf(msg, "Error: expected: <%u> but was: <%u>", expected, actual);
return msg;
}
static char* message_zu(size_t expected, size_t actual) {
sprintf(msg, "Error: expected: <%zu> but was: <%zu>", expected, actual);
return msg;
}
static char* test_num_of_left_init() {
size_t actual = get_num_of_left();
mu_assert(message_zu(0ULL, actual), actual == 0ULL);
return 0;
}
static char* test_constructor() {
Customer *customer = new_Customer();
mu_assert("Error: expected: not <null> but was: <null>", customer);
size_t size = sizeof(*customer);
uint32_t amount = customer->get_amount(customer);
del_Customer(&customer);
mu_assert("Error: expected: <null> but was: not <null>", !customer);
mu_assert(message_zu(sizeof(Customer), size), size == sizeof(Customer));
mu_assert(message_u(amount, 0U), amount == 0U);
return 0;
}
static char* test_amount() {
uint32_t amount = (uint32_t) rand();
uint32_t expected = amount;
Customer *customer = new_Customer();
customer->set_amount(customer, amount);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_food_init() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Customer *customer = new_Customer();
customer->take_food(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_food() {
uint32_t amount = (uint32_t) rand();
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = amount + price;
Customer *customer = new_Customer();
customer->set_amount(customer, amount);
customer->take_food(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_softdrink_init() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Customer *customer = new_Customer();
customer->take_softdrink(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_softdrink() {
uint32_t amount = (uint32_t) rand();
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = amount + price;
Customer *customer = new_Customer();
customer->set_amount(customer, amount);
customer->take_softdrink(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_alcohol_init() {
Customer *customer = new_Customer();
customer->take_alcohol(customer, (uint32_t) (300 + rand() % 4701));
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(0U, actual), actual == 0U);
return 0;
}
static char* test_take_alcohol() {
uint32_t amount = (uint32_t) rand();
uint32_t expected = amount;
Customer *customer = new_Customer();
customer->set_amount(customer, amount);
customer->take_alcohol(customer, (uint32_t) (300 + rand() % 4701));
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_beer_init() {
Customer *customer = new_Customer();
customer->take_beer(customer);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(0U, actual), actual == 0U);
return 0;
}
static char* test_take_beer() {
uint32_t amount = (uint32_t) rand();
uint32_t expected = amount;
Customer *customer = new_Customer();
customer->set_amount(customer, amount);
customer->take_beer(customer);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_accounting() {
size_t before = get_num_of_left();
Customer *customer = new_Customer();
customer->accounting(customer);
del_Customer(&customer);
size_t after = get_num_of_left();
mu_assert(message_zu(before + 1, after), after == before + 1);
return 0;
}
static char* test_num_of_left() {
size_t actual = get_num_of_left();
mu_assert(message_zu(1ULL, actual), actual == 1ULL);
return 0;
}
static char* all_tests() {
mu_run_test(test_num_of_left_init);
mu_run_test(test_constructor);
mu_run_test(test_amount);
mu_run_test(test_take_food_init);
mu_run_test(test_take_food);
mu_run_test(test_take_softdrink_init);
mu_run_test(test_take_softdrink);
mu_run_test(test_take_alcohol_init);
mu_run_test(test_take_alcohol);
mu_run_test(test_take_beer_init);
mu_run_test(test_take_beer);
mu_run_test(test_accounting);
mu_run_test(test_num_of_left);
return 0;
}
int main() {
srand((unsigned) time(NULL));
char *result = all_tests();
if (result != 0) {
fprintf(stderr, "%s\n", result);
fprintf(stderr, "Tests run: %d\n", tests_run);
} else {
fprintf(stdout, "ALL TESTS PASSED\n");
fprintf(stdout, "Tests run: %d\n", tests_run);
}
return result != 0;
}
で、Windows環境の場合、以下の様なバッチファイルを作っておけばワンクリックで、とはいきませんが、ダブルクリックでテストを流せます。
- テスト後、コード修正⇒コンパイルしたら失敗⇒修正前の
.exe起動、ということを避けるため、テスト終了後は.exeを削除することにします - 最後に
pauseを入れないと、テスト結果を確認する間もなくコンソール画面が閉じてしまいます
echo off
gcc -Wall -Wextra -Werror -std=c99 CustomerTest.c Customer.c -o CustomerTest 1>CustomerTest.txt 2>&1
if exist CustomerTest.exe (
.\CustomerTest
echo %ERRORLEVEL%
del CustomerTest.exe
del CustomerTest.txt
) else (
type CustomerTest.txt
)
pause
Linux環境の場合はお馴染みのMakefileでしょうか。
「成人」クラスのテスト
成人の場合は、お酒(ビールを含む)を頼んだ場合、以降の全ての食事の注文が毎回200円引きになりますので、食事の注文についてはお酒を頼む前後のケースでテストします。
ちなみに入力例2を見ると、退店後も注文をするお客さんがいるらしい・・・
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "minunit.h"
#include "Adult.h"
int tests_run = 0;
char msg[] = "Error: expected: <18446744073709551615> but was: <18446744073709551616>";
static char* message_u(uint32_t expected, uint32_t actual) {
sprintf(msg, "Error: expected: <%u> but was: <%u>", expected, actual);
return msg;
}
static char* message_zu(size_t expected, size_t actual) {
sprintf(msg, "Error: expected: <%zu> but was: <%zu>", expected, actual);
return msg;
}
static char* test_constructor() {
Adult *adult = new_Adult();
mu_assert("Error: expected: not <null> but was: <null>", adult);
Customer *customer = (Customer*) adult;
uint32_t amount = customer->get_amount(customer);
del_Customer((Customer**) &adult);
mu_assert("Error: expected: <null> but was: not <null>", !adult);
mu_assert(message_u(0U, amount), amount == 0U);
return 0;
}
static char* test_amount() {
uint32_t amount = (uint32_t) rand();
uint32_t expected = amount;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->set_amount(customer, amount);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_food_init() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->take_food(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_food() {
uint32_t amount = (uint32_t) rand();
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = amount + price;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->set_amount(customer, amount);
customer->take_food(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_softdrink_init() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->take_softdrink(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_softdrink() {
uint32_t amount = (uint32_t) rand();
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = amount + price;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->set_amount(customer, amount);
customer->take_softdrink(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_alcohol_init() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->take_alcohol(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_alcohol() {
uint32_t amount = (uint32_t) rand();
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = amount + price;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->set_amount(customer, amount);
customer->take_alcohol(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_beer_init() {
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->take_beer(customer);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(500U, actual), actual == 500U);
return 0;
}
static char* test_take_beer() {
uint32_t amount = (uint32_t) rand();
uint32_t expected = amount + 500;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->set_amount(customer, amount);
customer->take_beer(customer);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_food_after_alcohol() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price - 200;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->take_alcohol(customer, 0);
customer->take_food(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_take_food_after_beer() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price + 300;
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->take_beer(customer);
customer->take_food(customer, price);
uint32_t actual = customer->get_amount(customer);
del_Customer(&customer);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_accounting() {
size_t before = get_num_of_left();
Adult *adult = new_Adult();
Customer *customer = (Customer*) adult;
customer->accounting(customer);
del_Customer(&customer);
size_t after = get_num_of_left();
mu_assert(message_zu(before + 1, after), after == before + 1);
return 0;
}
static char* all_tests() {
mu_run_test(test_constructor);
mu_run_test(test_amount);
mu_run_test(test_take_food_init);
mu_run_test(test_take_food);
mu_run_test(test_take_softdrink_init);
mu_run_test(test_take_softdrink);
mu_run_test(test_take_alcohol_init);
mu_run_test(test_take_alcohol);
mu_run_test(test_take_beer_init);
mu_run_test(test_take_beer);
mu_run_test(test_take_food_after_alcohol);
mu_run_test(test_take_food_after_beer);
mu_run_test(test_accounting);
return 0;
}
int main(void) {
srand((unsigned) time(NULL));
char *result = all_tests();
if (result != 0) {
fprintf(stderr, "%s\n", result);
fprintf(stderr, "Tests run: %d\n", tests_run);
} else {
fprintf(stdout, "ALL TESTS PASSED\n");
fprintf(stdout, "Tests run: %d\n", tests_run);
}
return result != 0;
}
echo off
gcc -Wall -Wextra -Werror -std=c99 AdultTest.c Adult.c Customer.c -o AdultTest 1>AdultTest.txt 2>&1
if exist AdultTest.exe (
.\AdultTest
echo %ERRORLEVEL%
del AdultTest.exe
del AdultTest.txt
) else (
type AdultTest.txt
)
pause
「居酒屋」クラスのテスト
-
paiza国の法律では、20歳以上のお客さんは成人、20歳未満のお客さんは未成年とみなされるので、それぞれのインスタンスを作成した後、ビールを注文してみます
-
不正な入力
- お客さん番号$n$がお客さんの人数$N$より大きい(実装上お客さん番号$n$はズレるので、$n=N$の場合も含む)
-
food,softdrink,alcohol,0,A以外の注文の種類
についての挙動も確認します
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <errno.h>
#include "minunit.h"
#include "Pub.h"
int tests_run = 0;
char msg[] = "Error: expected: <18446744073709551615> but was: <18446744073709551616>";
static char* message_d(int expected, int actual) {
sprintf(msg, "Error: expected: <%d> but was: <%d>", expected, actual);
return msg;
}
static char* message_u(uint32_t expected, uint32_t actual) {
sprintf(msg, "Error: expected: <%u> but was: <%u>", expected, actual);
return msg;
}
static char* message_zu(size_t expected, size_t actual) {
sprintf(msg, "Error: expected: <%zu> but was: <%zu>", expected, actual);
return msg;
}
static char* test_constructor() {
Pub *pub = new_Pub();
mu_assert("Error: expected: not <null> but was: <null>", pub);
size_t size = sizeof(*pub);
del_Pub(&pub);
mu_assert("Error: expected: <null> but was: not <null>", !pub);
mu_assert(message_zu(sizeof(Pub), size), size == sizeof(Pub));
return 0;
}
static char* test_add_customer() {
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 19);
customer->take_beer(customer);
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: not <null> but was: <null>", customer);
mu_assert(message_u(0U, actual), actual == 0U);
return 0;
}
static char* test_add_adult() {
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 20);
customer->take_beer(customer);
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: not <null> but was: <null>", customer);
mu_assert(message_u(500U, actual), actual == 500U);
return 0;
}
static char* test_customer_take_food() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 19);
bool result = pub->order(pub, 0, "food", price);
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_adult_take_food() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 20);
bool result = pub->order(pub, 0, "food", price);
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_customer_take_softdrink() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 19);
bool result = pub->order(pub, 0, "softdrink", price);
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_adult_take_softdrink() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 20);
bool result = pub->order(pub, 0, "softdrink", price);
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_customer_take_alcohol() {
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 19);
bool result = pub->order(pub, 0, "alcohol", (uint32_t) (300 + rand() % 4701));
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_u(0U, actual), actual == 0U);
return 0;
}
static char* test_adult_take_alcohol() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price;
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 20);
bool result = pub->order(pub, 0, "alcohol", price);
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_customer_take_beer() {
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 19);
bool result = pub->order(pub, 0, "0", (uint32_t) (300 + rand() % 4701));
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_u(0U, actual), actual == 0U);
return 0;
}
static char* test_adult_take_beer() {
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 20);
bool result = pub->order(pub, 0, "0", (uint32_t) (300 + rand() % 4701));
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_u(500U, actual), actual == 500U);
return 0;
}
static char* test_adult_take_alcohol_and_food() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price - 200;
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 20);
pub->order(pub, 0, "alcohol", 0);
pub->order(pub, 0, "food", price);
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_adult_take_beer_and_food() {
uint32_t price = (uint32_t) (300 + rand() % 4701);
uint32_t expected = price + 300;
Pub *pub = new_Pub();
Customer *customer = pub->add_customer(pub, 20);
pub->order(pub, 0, "0", 0);
pub->order(pub, 0, "food", price);
uint32_t actual = customer->get_amount(customer);
del_Pub(&pub);
mu_assert(message_u(expected, actual), actual == expected);
return 0;
}
static char* test_customer_accounting() {
size_t before = get_num_of_left();
Pub *pub = new_Pub();
pub->add_customer(pub, 19);
bool result = pub->order(pub, 0, "A", (uint32_t) (300 + rand() % 4701));
del_Pub(&pub);
size_t after = get_num_of_left();
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_zu(before + 1, after), after == before + 1);
return 0;
}
static char* test_adult_accounting() {
size_t before = get_num_of_left();
Pub *pub = new_Pub();
pub->add_customer(pub, 20);
bool result = pub->order(pub, 0, "A", (uint32_t) (300 + rand() % 4701));
del_Pub(&pub);
size_t after = get_num_of_left();
mu_assert("Error: expected: <true> but was: <false>", result);
mu_assert(message_zu(before + 1, after), after == before + 1);
return 0;
}
static char* test_customer_out_of_range() {
Pub *pub = new_Pub();
bool result = pub->order(pub, 0, "0", 0);
del_Pub(&pub);
mu_assert("Error: expected: <false> but was: <true>", !result);
mu_assert(message_d(ERANGE, errno), errno == ERANGE);
return 0;
}
static char* test_customer_null() {
Pub *pub = new_Pub();
pub->add_customer(pub, 0);
bool result = pub->order(pub, 0, NULL, 0);
del_Pub(&pub);
mu_assert("Error: expected: <false> but was: <true>", !result);
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* test_customer_empty() {
Pub *pub = new_Pub();
pub->add_customer(pub, 0);
bool result = pub->order(pub, 0, "", 0);
del_Pub(&pub);
mu_assert("Error: expected: <false> but was: <true>", !result);
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* test_customer_invalid() {
Pub *pub = new_Pub();
pub->add_customer(pub, 0);
pub->order(pub, 0, "a", 0);
del_Pub(&pub);
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* all_tests() {
mu_run_test(test_constructor);
mu_run_test(test_add_customer);
mu_run_test(test_add_adult);
mu_run_test(test_customer_take_food);
mu_run_test(test_adult_take_food);
mu_run_test(test_customer_take_softdrink);
mu_run_test(test_adult_take_softdrink);
mu_run_test(test_customer_take_alcohol);
mu_run_test(test_adult_take_alcohol);
mu_run_test(test_customer_take_beer);
mu_run_test(test_adult_take_beer);
mu_run_test(test_adult_take_alcohol_and_food);
mu_run_test(test_adult_take_beer_and_food);
mu_run_test(test_customer_accounting);
mu_run_test(test_adult_accounting);
mu_run_test(test_customer_out_of_range);
mu_run_test(test_customer_null);
mu_run_test(test_customer_empty);
mu_run_test(test_customer_invalid);
return 0;
}
int main() {
srand((unsigned) time(NULL));
char *result = all_tests();
if (result != 0) {
fprintf(stderr, "%s\n", result);
fprintf(stderr, "Tests run: %d\n", tests_run);
} else {
fprintf(stdout, "ALL TESTS PASSED\n");
fprintf(stdout, "Tests run: %d\n", tests_run);
}
return result != 0;
}
gcc -Wall -Wextra -Werror -std=c99 PubTest.c Pub.c Adult.c Customer.c -o PubTest 1>PubTest.txt 2>&1
.\PubTest
echo %ERRORLEVEL%
del PubTest.exe
pause
入力チェックのテスト
入力された文字列が0以上の数値を表す文字列であるかどうかをテストするには
- null
- 0文字(空文字列:nullとは異なります)
- 1文字
- 数字
- 数字以外
- 2文字
- 数字
- 数字以外+数字
- 数字+数字以外
- 3文字
- 3文字すべて数字
- 先頭が数字以外
- 途中が数字以外
- 末尾が数字以外
少なくともこれらのケースについてテストするとよいでしょう。あと、
- 最大値
- 最大値+1
の、境界値分析も忘れないようにしましょう。
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "minunit.h"
#include "util.h"
int tests_run = 0;
char msg[] = "Error: expected: <18446744073709551615> but was: <18446744073709551616>";
static char* message_hhu(uint8_t expected, uint8_t actual) {
sprintf(msg, "Error: expected: <%hhu> but was: <%hhu>", expected, actual);
return msg;
}
static char* message_us(uint16_t expected, uint16_t actual) {
sprintf(msg, "Error: expected: <%hu> but was: <%hu>", expected, actual);
return msg;
}
static char* message_d(int expected, int actual) {
sprintf(msg, "Error: expected: <%d> but was: <%d>", expected, actual);
return msg;
}
static char* message_u(uint32_t expected, uint32_t actual) {
sprintf(msg, "Error: expected: <%u> but was: <%u>", expected, actual);
return msg;
}
static char* message_zu(size_t expected, size_t actual) {
sprintf(msg, "Error: expected: <%zu> but was: <%zu>", expected, actual);
return msg;
}
static char* message_s(const char *expected, const char *actual) {
sprintf(msg, "Error: expected: <%s> but was: <%s>", expected, actual);
return msg;
}
static char* test_chomp(const char *str, const char *expected) {
char s[strlen(str) + 1];
strcpy(s, str);
char *actual = chomp(s);
mu_assert(message_s(expected, s), !strcmp(s, expected));
mu_assert(message_s(expected, actual), !strcmp(s, expected));
return 0;
}
static char* test_chomp_0() {
mu_assert("Error: expected: <null> but was: not <null>", !chomp(NULL));
return 0;
}
static char* test_chomp_1() {return test_chomp("", "");}
static char* test_chomp_2() {return test_chomp("\n", "");}
static char* test_chomp_3() {return test_chomp("0", "0");}
static char* test_chomp_4() {return test_chomp(" 0", " 0");}
static char* test_parse_ubyte(const char *str, uint8_t expected) {
uint8_t n = 0;
mu_assert("Error: expected: <true> but was: <false>", parse_ubyte(str, &n));
mu_assert(message_d(0, errno), errno == 0);
mu_assert(message_hhu(expected, n), n == expected);
return 0;
}
static char* test_parse_ubyte_fail(const char *str, int expected) {
uint8_t n = 0;
mu_assert("Error: expected: <false> but was: <true>", !parse_ubyte(str, &n));
mu_assert(message_d(expected, errno), errno == expected);
return 0;
}
static char* test_parse_ubyte_0() {return test_parse_ubyte_fail(NULL, EINVAL);}
static char* test_parse_ubyte_1() {return test_parse_ubyte_fail("", EINVAL);}
static char* test_parse_ubyte_2() {return test_parse_ubyte("0", 0U);}
static char* test_parse_ubyte_3() {return test_parse_ubyte_fail(" ", EINVAL);}
static char* test_parse_ubyte_4() {return test_parse_ubyte("90", 90U);}
static char* test_parse_ubyte_5() {return test_parse_ubyte_fail("-1", EINVAL);}
static char* test_parse_ubyte_6() {return test_parse_ubyte_fail("1.", EINVAL);}
static char* test_parse_ubyte_7() {return test_parse_ubyte("255", 255U);}
static char* test_parse_ubyte_8() {return test_parse_ubyte_fail("/90", EINVAL);}
static char* test_parse_ubyte_9() {return test_parse_ubyte_fail("9:0", EINVAL);}
static char* test_parse_ubyte_10() {return test_parse_ubyte_fail("90~", EINVAL);}
static char* test_parse_ubyte_11() {return test_parse_ubyte_fail("256", ERANGE);}
static char* test_parse_ushort() {
uint16_t n = 0;
mu_assert("Error: expected: <true> but was: <false>", parse_ushort("65535", &n));
mu_assert(message_d(0, errno), errno == 0);
mu_assert(message_us(65535U, n), n == 65535U);
return 0;
}
static char* test_parse_ushort_fail() {
uint16_t n = 0;
mu_assert("Error: expected: <false> but was: <true>", !parse_ushort("65536", &n));
mu_assert(message_d(ERANGE, errno), errno == ERANGE);
return 0;
}
static char* test_parse_uint() {
uint32_t n = 0;
mu_assert("Error: expected: <true> but was: <false>", parse_uint("4294967295", &n));
mu_assert(message_d(0, errno), errno == 0);
mu_assert(message_u(4294967295U, n), n == 4294967295U);
return 0;
}
static char* test_parse_uint_fail() {
uint32_t n = 0;
mu_assert("Error: expected: <false> but was: <true>", !parse_uint("4294967296", &n));
mu_assert(message_d(ERANGE, errno), errno == ERANGE);
return 0;
}
static char* test_parse_ulong() {
size_t n = 0;
mu_assert("Error: expected: <true> but was: <false>", parse_ulong("18446744073709551615", &n));
mu_assert(message_d(0, errno), errno == 0);
mu_assert(message_zu(18446744073709551615ULL, n), n == 18446744073709551615ULL);
return 0;
}
static char* test_parse_ulong_fail() {
size_t n = 0;
mu_assert("Error: expected: <false> but was: <true>", !parse_ulong("18446744073709551616", &n));
mu_assert(message_d(ERANGE, errno), errno == ERANGE);
return 0;
}
static char* all_tests() {
mu_run_test(test_chomp_0);
mu_run_test(test_chomp_1);
mu_run_test(test_chomp_2);
mu_run_test(test_chomp_3);
mu_run_test(test_chomp_4);
mu_run_test(test_parse_ubyte_0);
mu_run_test(test_parse_ubyte_1);
mu_run_test(test_parse_ubyte_2);
mu_run_test(test_parse_ubyte_3);
mu_run_test(test_parse_ubyte_4);
mu_run_test(test_parse_ubyte_5);
mu_run_test(test_parse_ubyte_6);
mu_run_test(test_parse_ubyte_7);
mu_run_test(test_parse_ubyte_8);
mu_run_test(test_parse_ubyte_9);
mu_run_test(test_parse_ubyte_10);
mu_run_test(test_parse_ubyte_11);
mu_run_test(test_parse_ushort);
mu_run_test(test_parse_ushort_fail);
mu_run_test(test_parse_uint);
mu_run_test(test_parse_uint_fail);
mu_run_test(test_parse_ulong);
mu_run_test(test_parse_ulong_fail);
return 0;
}
int main() {
char *result = all_tests();
if (result != 0) {
fprintf(stderr, "%s\n", result);
fprintf(stderr, "Tests run: %d\n", tests_run);
} else {
fprintf(stdout, "ALL TESTS PASSED\n");
fprintf(stdout, "Tests run: %d\n", tests_run);
}
return result != 0;
}
echo off
gcc -Wall -Wextra -Werror -std=c99 utilTest.c util.c -o utilTest 1>utilTest.txt 2>&1
if exist utilTest.exe (
.\utilTest
echo %ERRORLEVEL%
del utilTest.exe
del utilTest.txt
) else (
type utilTest.txt
)
pause
以下の入力読込・チェックのテストでは
- 項目数に過不足は無いか
- 数値の入るべき箇所に数字以外の文字は無いか
- 最小値-1
- 最小値
- 最大値
- 最大値+1
- 注文の種類として受け付けない文字列
に注目してテストコードを書いたつもりです。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <errno.h>
#include "minunit.h"
#include "input.h"
int tests_run = 0;
char msg[] = "Error: expected: <18446744073709551615> but was: <18446744073709551616>";
size_t n;
uint16_t k;
uint8_t a;
char s[] = "softdrink";
uint32_t m;
static char* message_hhu(uint8_t expected, uint8_t actual) {
sprintf(msg, "Error: expected: <%hhu> but was: <%hhu>", expected, actual);
return msg;
}
static char* message_d(int expected, int actual) {
sprintf(msg, "Error: expected: <%d> but was: <%d>", expected, actual);
return msg;
}
static char* message_u(uint32_t expected, uint32_t actual) {
sprintf(msg, "Error: expected: <%u> but was: <%u>", expected, actual);
return msg;
}
static char* message_zu(size_t expected, size_t actual) {
sprintf(msg, "Error: expected: <%zu> but was: <%zu>", expected, actual);
return msg;
}
static char* message_s(const char *expected, const char *actual) {
sprintf(msg, "Error: expected: <%s> but was: <%s>", expected, actual);
return msg;
}
static char* test_read_num(const char *str, size_t en, uint16_t ek) {
mu_assert("Error: expected: <true> but was: <false>", read_numbers(str, &n, &k));
mu_assert(message_d(errno, 0), errno == 0);
mu_assert(message_zu(en, n), n == en);
mu_assert(message_u(ek, k), k == ek);
return 0;
}
static char* test_read_num_null() {
mu_assert("Error: expected: <false> but was: <true>", !read_numbers(NULL, &n, &k));
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* test_read_num_invalid(const char *str) {
mu_assert("Error: expected: <false> but was: <true>", !read_numbers(str, &n, &k));
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* test_read_num_out_of_range(const char *str) {
mu_assert("Error: expected: <false> but was: <true>", !read_numbers(str, &n, &k));
mu_assert(message_d(ERANGE, errno), errno == ERANGE);
return 0;
}
static char* test_read_num_0() {return test_read_num_null();}
static char* test_read_num_1() {return test_read_num_invalid("");}
static char* test_read_num_2() {return test_read_num_invalid("a 1");}
static char* test_read_num_3() {return test_read_num_invalid("1");}
static char* test_read_num_4() {return test_read_num_invalid("1 a");}
static char* test_read_num_5() {return test_read_num_invalid("1 1 1");}
static char* test_read_num_6() {return test_read_num("1 1", 1, 1);}
static char* test_read_num_7() {return test_read_num("1000 1000", 1000, 1000);}
static char* test_read_num_8() {return test_read_num_out_of_range("0 1");}
static char* test_read_num_9() {return test_read_num_out_of_range("1001 1");}
static char* test_read_num_10() {return test_read_num_out_of_range("1 0");}
static char* test_read_num_11() {return test_read_num_out_of_range("1 1001");}
static char* test_read_age(const char *str, uint8_t ea) {
mu_assert("Error: expected: <true> but was: <false>", read_age(str, &a));
mu_assert(message_d(0, errno), errno == 0);
mu_assert(message_hhu(ea, a), a == ea);
return 0;
}
static char* test_read_age_null() {
mu_assert("Error: expected: <false> but was: <true>", !read_age(NULL, &a));
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* test_read_age_invalid(const char *str) {
mu_assert("Error: expected: <false> but was: <true>", !read_age(str, &a));
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* test_read_age_out_of_range(const char *str) {
mu_assert("Error: expected: <false> but was: <true>", !read_age(str, &a));
mu_assert(message_d(ERANGE, errno), errno == ERANGE);
return 0;
}
static char* test_read_age_0() {return test_read_age_null();}
static char* test_read_age_1() {return test_read_age_invalid("");}
static char* test_read_age_2() {return test_read_age_invalid("a");}
static char* test_read_age_3() {return test_read_age_invalid("1 1");}
static char* test_read_age_4() {return test_read_age("1", 1);}
static char* test_read_age_5() {return test_read_age("100", 100);}
static char* test_read_age_6() {return test_read_age_out_of_range("0");}
static char* test_read_age_7() {return test_read_age_out_of_range("101");}
static char* test_read_query_3_args(const char *str, size_t en, const char* es, uint32_t em) {
mu_assert("Error: expected: <true> but was: <false>", read_query(str, &n, s, &m));
mu_assert(message_d(0, errno), errno == 0);
mu_assert(message_zu(en, n), n == en);
mu_assert(message_s(es, s), !strcmp(s, es));
mu_assert(message_u(em, m), m == em);
return 0;
}
static char* test_read_query_2_args(const char *str, size_t en, const char* es) {
mu_assert("Error: expected: <true> but was: <false>", read_query(str, &n, s, &m));
mu_assert(message_d(0, errno), errno == 0);
mu_assert(message_zu(en, n), n == en);
mu_assert(message_s(es, s), !strcmp(s, es));
return 0;
}
static char* test_read_query_null() {
mu_assert("Error: expected: <false> but was: <true>", !read_query(NULL, &n, s, &m));
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* test_read_query_invalid(const char *str) {
mu_assert("Error: expected: <false> but was: <true>", !read_query(str, &n, s, &m));
mu_assert(message_d(EINVAL, errno), errno == EINVAL);
return 0;
}
static char* test_read_query_out_of_range(const char *str) {
mu_assert("Error: expected: <false> but was: <true>", !read_query(str, &n, s, &m));
mu_assert(message_d(ERANGE, errno), errno == ERANGE);
return 0;
}
static char* test_read_query() {
size_t n = (size_t) (1 + rand() % 1000);
uint32_t m = (uint32_t) (300 + rand() % 4701);
char line[] = "1000 softdrink 5000";
sprintf(line, "%zu food %u", n, m);
return test_read_query_3_args(line, n, "food", m);
}
static char* test_read_query_0() {return test_read_query_null();}
static char* test_read_query_1() {return test_read_query_invalid("");}
static char* test_read_query_2() {return test_read_query_invalid("a food 300");}
static char* test_read_query_3() {return test_read_query_invalid("1");}
static char* test_read_query_4() {return test_read_query_invalid("1 a 300");}
static char* test_read_query_5() {return test_read_query_invalid("1 food");}
static char* test_read_query_6() {return test_read_query_invalid("1 softdrink");}
static char* test_read_query_7() {return test_read_query_invalid("1 alcohol");}
static char* test_read_query_8() {return test_read_query_invalid("1 food a");}
static char* test_read_query_9() {return test_read_query_invalid("1 softdrink a");}
static char* test_read_query_10() {return test_read_query_invalid("1 beer a");}
static char* test_read_query_11() {return test_read_query_invalid("1 0 500");}
static char* test_read_query_12() {return test_read_query_invalid("1 A 500");}
static char* test_read_query_13() {return test_read_query_invalid("1 food 300 0");}
static char* test_read_query_14() {return test_read_query_3_args("1 food 300", 1, "food", 300);}
static char* test_read_query_15() {return test_read_query_3_args("1 softdrink 300", 1, "softdrink", 300);}
static char* test_read_query_16() {return test_read_query_3_args("1000 alcohol 5000", 1000, "alcohol", 5000);}
static char* test_read_query_17() {return test_read_query_2_args("1 0", 1, "0");}
static char* test_read_query_18() {return test_read_query_2_args("1000 A", 1000, "A");}
static char* test_read_query_19() {return test_read_query_out_of_range("0 0");}
static char* test_read_query_20() {return test_read_query_out_of_range("1001 A");}
static char* test_read_query_21() {return test_read_query_out_of_range("1 food 299");}
static char* test_read_query_22() {return test_read_query_out_of_range("1 softdrink 299");}
static char* test_read_query_23() {return test_read_query_out_of_range("1000 alcohol 5001");}
static char* all_tests() {
mu_run_test(test_read_num_0);
mu_run_test(test_read_num_1);
mu_run_test(test_read_num_2);
mu_run_test(test_read_num_3);
mu_run_test(test_read_num_4);
mu_run_test(test_read_num_5);
mu_run_test(test_read_num_6);
mu_run_test(test_read_num_7);
mu_run_test(test_read_num_8);
mu_run_test(test_read_num_9);
mu_run_test(test_read_num_10);
mu_run_test(test_read_num_11);
mu_run_test(test_read_age_0);
mu_run_test(test_read_age_1);
mu_run_test(test_read_age_2);
mu_run_test(test_read_age_3);
mu_run_test(test_read_age_4);
mu_run_test(test_read_age_5);
mu_run_test(test_read_age_6);
mu_run_test(test_read_age_7);
mu_run_test(test_read_query);
mu_run_test(test_read_query_0);
mu_run_test(test_read_query_1);
mu_run_test(test_read_query_2);
mu_run_test(test_read_query_3);
mu_run_test(test_read_query_4);
mu_run_test(test_read_query_5);
mu_run_test(test_read_query_6);
mu_run_test(test_read_query_7);
mu_run_test(test_read_query_8);
mu_run_test(test_read_query_9);
mu_run_test(test_read_query_10);
mu_run_test(test_read_query_11);
mu_run_test(test_read_query_12);
mu_run_test(test_read_query_13);
mu_run_test(test_read_query_14);
mu_run_test(test_read_query_15);
mu_run_test(test_read_query_16);
mu_run_test(test_read_query_17);
mu_run_test(test_read_query_18);
mu_run_test(test_read_query_19);
mu_run_test(test_read_query_20);
mu_run_test(test_read_query_21);
mu_run_test(test_read_query_22);
mu_run_test(test_read_query_23);
return 0;
}
int main() {
srand((unsigned) time(NULL));
char *result = all_tests();
if (result != 0) {
fprintf(stderr, "%s\n", result);
fprintf(stderr, "Tests run: %d\n", tests_run);
} else {
fprintf(stdout, "ALL TESTS PASSED\n");
fprintf(stdout, "Tests run: %d\n", tests_run);
}
return result != 0;
}
echo off
gcc -Wall -Wextra -Werror -std=c99 inputTest.c input.c util.c -o inputTest 1>inputTest.txt 2>&1
if exist inputTest.exe (
.\inputTest
echo %ERRORLEVEL%
del inputTest.exe
del inputTest.txt
) else (
type inputTest.txt
)
pause
エントリポイントのテスト
main関数は、標準入力から値を受け取って標準出力に表示しますので、一例として簡易的に(あくまで簡易的に)system関数を使って、system("main <in.txt 1>out.txt 2>err.txt")として、出力されたout.txtが期待値通りかどうかをチェックする方針を取ってみたいと思います。
ざっと
- 未成年
- 成人(ビール以外のお酒を頼む前後で食事を注文)
- 成人(ビールを頼む前後で食事を注文)
- 最大件数入力(CPUベースで処理時間を計測)
- 制約条件外入力(エラーコードチェック)
- その他、不正な入力(エラーコードチェック)
- 今回、細かいエラーメッセージのチェックは省略させていただいております
こんなところでしょうか。
あくまでWindows環境で動かす想定のコードになっています。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <assert.h>
#include <errno.h>
#include "minunit.h"
#define SOURCE "main"
#define IN_FILE "in.txt"
#define OUT_FILE "out.txt"
#define ERR_FILE "err.txt"
int tests_run = 0;
char msg[] = "Error: expected: <18446744073709551615> but was: <18446744073709551616>";
char result[1024][16];
static char* message_zu(size_t expected, size_t actual) {
sprintf(msg, "Error: expected: <%zu> but was: <%zu>", expected, actual);
return msg;
}
static char* message_s(const char *expected, const char *actual) {
sprintf(msg, "Error: expected: <%s> but was: <%s>", expected, actual);
return msg;
}
static char* message_d(int expected, int actual) {
sprintf(msg, "Error: expected: <%d> but was: <%d>", expected, actual);
return msg;
}
static size_t test(size_t n, const char *lines[]) {
static unsigned k = 0;
FILE *file;
if (!(file = fopen(IN_FILE, "w"))) {
fprintf(stderr, "%s\n", strerror(errno));
exit(errno);
}
for (size_t i = 0; i < n; i++) {
fputs(lines[i], file);
fputc('\n', file);
}
fclose(file);
clock_t start = clock();
if (!!(errno = system(".\\" SOURCE " <" IN_FILE " 1>" OUT_FILE " 2>" ERR_FILE))) {
fprintf(stderr, "%s\n", strerror(errno));
exit(errno);
}
fprintf(stderr, "#%u %f sec\n", ++k, (float) (clock() - start) / CLOCKS_PER_SEC);
fflush(stderr);
if (!(file = fopen(OUT_FILE, "r"))) {
fprintf(stderr, "%s\n", strerror(errno));
exit(errno);
}
size_t m = 0;
while (fgets(result[m], sizeof(result[m]), file)) m++;
fclose(file);
return m;
}
static int test_err(size_t n, const char *lines[]) {
FILE *file;
if (!(file = fopen(IN_FILE, "w"))) {
fprintf(stderr, "%s\n", strerror(errno));
exit(errno);
}
for (size_t i = 0; i < n; i++) {
fputs(lines[i], file);
fputc('\n', file);
}
fclose(file);
return system(".\\" SOURCE " <" IN_FILE " 1>" OUT_FILE " 2>" ERR_FILE);
}
static char* test0() {
const char *lines[] = {""};
int e = test_err(sizeof(lines) / sizeof(lines[0]), lines);
mu_assert(message_d(ERANGE, e), e == EINVAL);
return 0;
}
static char* test1() {
const char *lines[] = { "0 0" };
int e = test_err(sizeof(lines) / sizeof(lines[0]), lines);
mu_assert(message_d(ERANGE, e), e == ERANGE);
return 0;
}
static char* test2() {
const char *lines[] = { "1 1", "1", "1 0" };
size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
mu_assert(message_zu(1, n), n == 1);
mu_assert(message_s("0\n", result[0]), !strcmp(result[0], "0\n"));
return 0;
}
static char* test3() {
const char *lines[] = {
"3 21",
"19",
"20",
"20",
"1 softdrink 300",
"1 food 340",
"1 alcohol 380",
"1 softdrink 420",
"1 food 460",
"1 0",
"1 softdrink 540",
"1 food 580",
"1 A",
"2 softdrink 1000",
"2 food 2000",
"2 alcohol 3000",
"2 softdrink 4000",
"2 food 5000",
"2 A",
"3 softdrink 300",
"3 food 400",
"3 0",
"3 softdrink 600",
"3 food 700",
"3 A",
};
size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
mu_assert(message_zu(4, n), n == 4);
mu_assert(message_s("2640\n", result[0]), !strcmp(result[0], "2640\n"));
mu_assert(message_s("14800\n", result[1]), !strcmp(result[1], "14800\n"));
mu_assert(message_s("2300\n", result[2]), !strcmp(result[2], "2300\n"));
mu_assert(message_s("3\n", result[3]), !strcmp(result[3], "3\n"));
return 0;
}
static char* test4() {
char chars[2001][10] = { "1000 1000" };
for (size_t i = 0; i < 1000; i++) {
sprintf(chars[i + 1], "%zu", 1 + i % 100);
sprintf(chars[i + 1001], "%zu A", i + 1);
}
char *lines[2001];
for (size_t i = 0; i < 2001; i++) {
lines[i] = chars[i];
}
size_t n = test(sizeof(lines) / sizeof(lines[0]), (const char**) lines);
mu_assert(message_zu(1001, n), n == 1001);
for (size_t i = 0; i < 1000; i++) {
mu_assert(message_s("0\n", result[i]), !strcmp(result[i], "0\n"));
}
mu_assert(message_s("1000\n", result[1000]), !strcmp(result[1000], "1000\n"));
return 0;
}
static char* all_tests() {
mu_run_test(test0);
mu_run_test(test1);
mu_run_test(test2);
mu_run_test(test3);
mu_run_test(test4);
return 0;
}
int main() {
if (!!(errno = system("gcc -Wall -Wextra -Werror -std=c99 " SOURCE ".c input.c util.c Pub.c Adult.c Customer.c -o " SOURCE " 1>" SOURCE ".txt 2>&1"))) {
fprintf(stderr, "%s\n", strerror(errno));
FILE* file = fopen(SOURCE ".txt", "r");
if (file) {
int c = 0;
while ((c = fgetc(file)) != EOF) {
putchar(c);
}
fclose(file);
}
exit(errno);
}
char *result = all_tests();
if (result != 0) {
fprintf(stderr, "%s\n", result);
fprintf(stderr, "Tests run: %d\n", tests_run);
} else {
fprintf(stdout, "ALL TESTS PASSED\n");
fprintf(stdout, "Tests run: %d\n", tests_run);
}
remove(ERR_FILE);
remove(OUT_FILE);
remove(IN_FILE);
remove(SOURCE ".exe");
remove(SOURCE ".txt");
if (errno) {
return errno;
} else {
return result != 0;
}
}
echo off
gcc -Wall -Wextra -Werror -std=c99 test.c -o test 1>test.txt 2>&1
if exist test.exe (
.\test
echo %ERRORLEVEL%
del test.exe
del test.txt
) else (
type test.txt
)
pause
あとは、入力例2のような、退店した客の注文をチェックしたり、アルコール類注文後の200円未満の食事の扱い(今回の制約条件上ないが)、あるいは、お客さんクラスに値段のチェックを入れたり(その際、アルコール類注文後の499円の食事の注文を弾いたり、未成年の299円の食事の注文を受け入れたりしないよう)色々考える余地はありそうです。