0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

paiza問題集の「1つのデータの入力」の入力チェック、テストを考えてみた

0
Last updated at Posted at 2026-03-16

paizaラーニングレベルアップ問題集の1つのデータの入力をやってみました。


問題


提出コード

良い子は真似しないでください。


#include <stdio.h>
int main(){
	char s[102];
	return fputs(fgets(s, sizeof(s), stdin), stdout) == EOF;
}

入力チェック

  • $S$は1文字以上100文字以下の文字列
  • $S$の各文字は英小文字または大文字または数字

を満たすか、チェックします。


プロトタイプ

input.h
#ifndef INPUT_H_
#define INPUT_H_

#include <stdbool.h>

bool input(const char*, char*);

#endif /* INPUT_H_ */

テスト

入力値に

  • 0文字の文字列
  • 101文字以上の文字列
  • 英小文字、英大文字、数字以外の文字

が含まれる場合はNGとなることを確認します。
今回は、単体テストツールとしてminunit.hを使用します。


inputTest.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "minunit.h"
#include "input.h"

int tests_run = 0;

static char* message_s(const char *expected, const char *actual) {
	static char msg[256];
	snprintf(msg, sizeof(msg), "Error: expected: <%s> but was: <%s>", expected, actual);
	return msg;
}

static char* message_d(int expected, int actual) {
	static char msg[72];
	snprintf(msg, sizeof(msg), "Error: expected: <%d> but was: <%d>", expected, actual);
	return msg;
}

static char* test_input(const char *str, const char *expected) {
	char actual[strlen(str) + 1];
	mu_assert("Error: expected: <true> but was: <false>", input(str, actual));
	mu_assert(message_s(expected, actual), !strcmp(actual, expected));
	mu_assert(message_d(0, errno), errno == 0);
	return 0;
}

static char* test_input_invalid(const char *str) {
	char actual[str ? strlen(str) + 1 : 0];
	mu_assert("Error: expected: <false> but was: <true>", !input(str, actual));
	mu_assert(message_d(EINVAL, errno), errno == EINVAL);
	return 0;
}

static char* test_input_0() {return test_input_invalid(NULL);}
static char* test_input_1() {return test_input_invalid("");}
static char* test_input_2() {return test_input_invalid("\n");}
static char* test_input_3() {return test_input("0\n", "0");}
static char* test_input_4() {return test_input("9\n", "9");}
static char* test_input_5() {return test_input("A\n", "A");}
static char* test_input_6() {return test_input("Z\n", "Z");}
static char* test_input_7() {return test_input("a\n", "a");}
static char* test_input_8() {return test_input("z\n", "z");}

static char* test_input_9() {
	const char str[] = { '0' - 1, '\n', '\0' };
	return test_input_invalid(str);
}

static char* test_input_10() {
	const char str[] = { '9' + 1, '\n', '\0' };
	return test_input_invalid(str);
}

static char* test_input_11() {
	const char str[] = { 'A' - 1, '\n', '\0' };
	return test_input_invalid(str);
}

static char* test_input_12() {
	const char str[] = { 'Z' + 1, '\n', '\0' };
	return test_input_invalid(str);
}

static char* test_input_13() {
	const char str[] = { 'a' - 1, '\n', '\0' };
	return test_input_invalid(str);
}

static char* test_input_14() {
	const char str[] = { 'z' + 1, '\n', '\0' };
	return test_input_invalid(str);
}

static char* test_input_15() {return test_input("00\n", "00");}
static char* test_input_16() {return test_input_invalid("_0\n");}
static char* test_input_17() {return test_input_invalid("0_\n");}
static char* test_input_18() {return test_input("000\n", "000");}
static char* test_input_19() {return test_input_invalid("_00\n");}
static char* test_input_20() {return test_input_invalid("0_0\n");}
static char* test_input_21() {return test_input_invalid("00_\n");}

static char* test_input_22() {
	char str[102] = { };
	memset(str, '0', 100);
	str[100] = '\n';
	char expected[101] = { };
	memset(expected, '0', 100);
	return test_input(str, expected);
}

static char* test_input_23() {
	char str[103] = { };
	memset(str, '0', 101);
	str[101] = '\n';
	return test_input_invalid(str);
}

static char* all_tests() {
	mu_run_test(test_input_0);
	mu_run_test(test_input_1);
	mu_run_test(test_input_2);
	mu_run_test(test_input_3);
	mu_run_test(test_input_4);
	mu_run_test(test_input_5);
	mu_run_test(test_input_6);
	mu_run_test(test_input_7);
	mu_run_test(test_input_8);
	mu_run_test(test_input_9);
	mu_run_test(test_input_10);
	mu_run_test(test_input_11);
	mu_run_test(test_input_12);
	mu_run_test(test_input_13);
	mu_run_test(test_input_14);
	mu_run_test(test_input_15);
	mu_run_test(test_input_16);
	mu_run_test(test_input_17);
	mu_run_test(test_input_18);
	mu_run_test(test_input_19);
	mu_run_test(test_input_20);
	mu_run_test(test_input_21);
	mu_run_test(test_input_22);
	mu_run_test(test_input_23);
	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;
}

実装
  • 入力値がNULLの場合はfalseを返す
  • 入力文字列末尾の改行コードを削除する
  • 入力文字列が末尾改行コード除いて101文字以上の場合はfalseを返す
  • 入力文字列が末尾改行コード除いて0文字の場合はfalseを返す
  • 入力文字列が英小文字、英大文字、数字以外の文字を含む場合はfalseを返す
  • 最後trueを返す

ように実装します。このとき、上述のテストコードも全てパスします。


input.c
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include "input.h"

char* chomp(char *s) {
	if (!s)
		return NULL;
	size_t n = strlen(s);
	while (n--)
		if (isspace(s[n]))
			s[n] = '\0';
		else
			break;
	return s;
}

bool input(const char *str, char *s) {
	errno = 0;
	if (!(str && *str)) {
		errno = EINVAL;
		return false;
	}
	if (strlen(chomp(strcpy(s, str))) > 100) {
		errno = EINVAL;
		return false;
	}
	if (!*s) {
		errno = EINVAL;
		return false;
	}
	for (const char *c = s; *c; c++) {
		if (!isalnum(*c)) {
			errno = EINVAL;
			return false;
		}
	}
	return true;
}

Windows環境の場合、以下のバッチファイルを作成するとテストしやすいと思います。

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関数の実装及びテストコード記述をします。

テスト

まず、main関数のテストコードを書きます。


test.c
#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 result[1][128];

static char* message_zu(size_t expected, size_t actual) {
	static char msg[72];
	snprintf(msg, sizeof(msg), "Error: expected: <%zu> but was: <%zu>", expected, actual);
	return msg;
}

static char* message_s(const char *expected, const char *actual) {
	static char msg[256];
	snprintf(msg, sizeof(msg), "Error: expected: <%s> but was: <%s>", expected, actual);
	return msg;
}

static char* message_d(int expected, int actual) {
	static char msg[72];
	snprintf(msg, sizeof(msg), "Error: expected: <%d> but was: <%d>", expected, actual);
	return msg;
}

static size_t test(size_t n, const char *lines[]) {
	FILE *file;
	if (!(file = fopen(IN_FILE, "w"))) {
		perror(NULL);
		exit(errno);
	}
	for (size_t i = 0; i < n; i++) {
		fputs(lines[i], file);
		fputc('\n', file);
	}
	fclose(file);
	clock_t clockt = clock();
	if (!!(errno = system(".\\" SOURCE " <" IN_FILE " 1>" OUT_FILE " 2>" ERR_FILE))) {
		perror(NULL);
		exit(errno);
	}
	fprintf(stderr, "#%d %f sec\n", tests_run, (float) (clock() - clockt) / CLOCKS_PER_SEC);
	fflush(stderr);
	if (!(file = fopen(OUT_FILE, "r"))) {
		perror(NULL);
		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"))) {
		perror(NULL);
		exit(errno);
	}
	for (size_t i = 0; i < n; i++) {
		fputs(lines[i], file);
		fputc('\n', file);
	}
	fclose(file);
	clock_t clockt = clock();
	int e = system(".\\" SOURCE " <" IN_FILE " 1>" OUT_FILE " 2>" ERR_FILE);
	fprintf(stderr, "#%d %f sec\n", tests_run, (float) (clock() - clockt) / CLOCKS_PER_SEC);
	fflush(stderr);
	return e;
}

static char* test0() {
	int e = test_err(0, NULL);
	mu_assert(message_d(EXIT_FAILURE, e), e == EXIT_FAILURE);
	return 0;
}

static char* test1() {
	char s[64] = { };
	size_t i = 0;
	for (char c = '0'; c <= '9'; c++)
		s[i++] = c;
	for (char c = 'A'; c <= 'Z'; c++)
		s[i++] = c;
	for (char c = 'a'; c <= 'z'; c++)
		s[i++] = c;
	char t[64] = { };
	strcpy(t, s);
	strcat(t, "\n");
	const char *lines[] = { s };
	size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
	mu_assert(message_zu(1, n), n == 1);
	mu_assert(message_s(t, result[0]), !strcmp(result[0], t));
	return 0;
}

static char* test2() {
	char s[4][128] = { };
	size_t i = 0;
	for (char c = 0x20; c < 0x7F; c++)
		s[1][i++] = c;
	memset(s[2], '0', 101);
	memset(s[3], '0', 100);
	const char *lines[] = { s[0], s[1], s[2], s[3] };
	char t[128] = { };
	memset(t, '0', 100);
	t[100] = '\n';
	size_t n = test(sizeof(lines) / sizeof(lines[0]), lines);
	mu_assert(message_zu(1, n), n == 1);
	mu_assert(message_s(t, result[0]), !strcmp(result[0], t));
	return 0;
}

static char* all_tests() {
	mu_run_test(test0);
	mu_run_test(test1);
	mu_run_test(test2);
	return 0;
}

int main() {
	if (!!(errno = system("gcc -Wall -Wextra -Werror -DNDEBUG -std=c99 " SOURCE ".c input.c util.c -o " SOURCE " 1>" SOURCE ".txt 2>&1"))) {
		perror(NULL);
		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;
	}
}

実装

上述のinput関数を使ってmain関数を実装します。

  • EOFが入力されたらプログラムを終了します(Windowsなら「Ctrl+Z」)
  • 入力に不備のある場合は再入力となります
  • コンパイル時に-NDEBUGオプションを付けなければ時間計測モードになります(今回は何も実装していません)
  • (オプション)コマンドライン引数がある場合は、(特に入力チェックをせず)コマンドライン引数を出力します

main.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef NDEBUG
#include "input.h"
#include <errno.h>
const char END[] = { EOF, '\n', 0 };
#else
#include <time.h>
#define PRINT(format, ...)                      \
	do {                                        \
		fprintf(stderr, format, ##__VA_ARGS__); \
		fflush(stderr);                         \
	} while (0)
#endif

int main(int argc, char *argv[]) {
#ifndef NDEBUG
	clock_t clockt = clock();
#endif
	char str[101] = { };
	if (argc > 1) {
		strncpy(str, argv[1], sizeof(str) - 1);
	} else {
#ifdef NDEBUG
		do {
			errno = 0;
			char s[102];
			if (!fgets(s, sizeof(s), stdin)) {
				return EXIT_FAILURE;
			}
			if (!strchr(s, '\n')) {
				for (int c = getchar(); !strchr(END, c); c = getchar());
				errno = EINVAL;
			} else if (input(s, str)) {
				break;
			}
			perror(
				"The input must be at least 1 character and no more than 100 characters."
				"Each character of the input value must be a lowercase letter, an uppercase letter, or a digit."
			);
		} while (true);
#endif
	}
	puts(str);
#ifndef NDEBUG
	PRINT("%f sec.\n", (float) (clock() - clockt) / CLOCKS_PER_SEC);
#endif
	return EXIT_SUCCESS;
}

test.bat
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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?