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つのデータの入力」の入力チェック、テストを考えてみた (2)

0
Posted at

本記事は、以下の記事を新人プログラマ応援用に改訂したつもりの版です。


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


問題


提出コード


#include <stdio.h>

int main(int argc, char *argv[]){
	char s[102];
	scanf("%s", s);
	printf("%s\n", s);
	return 0;
}

実務では、上記の様なコードに対して、上司や先輩から指摘を受けるでしょう。

  • 引数argc, argvが未使用である
  • 入力チェックがされていない
  • バッファオーバーランしてバッファオーバーフローになる
  • その他、変数名sが分かりにくい、エラーチェックがされていない、等

等々。

本記事では、入力チェック及びそのテストに重点を置いて記述します。


入力チェック

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

を満たすか、チェックする関数を作成します。
入力チェック関数は、エントリポイントmain関数とは分けて記述することにします。


プロトタイプ

input.h
#ifndef INPUT_H_
#define INPUT_H_

#include <stdbool.h>

bool input(const char*);

#endif /* INPUT_H_ */

テスト

今回は、先にテストコードを記述します。

末尾の改行を除いた入力値が

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

場合はNGとなることを確認します。


同値分割

同値分割とは、出力が同じになるような入力をそれぞれグループにまとめ、グループの中から代表を選んで行うテストです。
(例)

グループ 代表値 期待値
英小文字 m
英大文字 N
数字 5
その他 _

境界値分析

境界値分析とは、出力が同じになるような入力をそれぞれグループにまとめ、グループが隣接する境界やその前後の値を入力として行うテストです。
(例)$S$は1文字以上100文字以下の文字列

グループ 代表値 期待値
1文字未満 0文字
1文字以上 1文字
100文字以下 100文字
100文字超え 101文字

その他

文字列チェックについては

  • NULL
  • 0文字(空文字)
  • 1文字:ここで「同値分割」テストを実施する
  • 2文字
  • 3文字

はテストするようにしましょう。尚、配列についても文字列(文字配列)と同様のことが言えます。


以上を踏まえると、以下の様なテストケースが考えられます。

# 期待値
1 NULL
2 0文字 空文字列
3 1文字 英小文字
4 英大文字
5 数字
6 その他
7 2文字 正常文字列
8 先頭が不正
9 末尾が不正
10 3文字 正常文字列
11 先頭が不正
12 中間が不正
13 末尾が不正
14 100文字
15 101文字

今回は、エラー内容に応じて<errno.h>errno

  • 字数が範囲外の場合はERANGE
  • (1~100文字で)英小文字、英大文字、数字以外の文字を含む場合はEINVAL
    に設定することにします。
    また、単体テストツールとしてminunit.hを使用します。


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

int tests_run = 0;

static char* message(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) {
	mu_assert("Error: expected: <true> but was: <false>", input(str));
	mu_assert(message(0, errno), errno == 0);
	return 0;
}

static char* test_input_out_of_range(const char *str) {
	mu_assert("Error: expected: <false> but was: <true>", !input(str));
	mu_assert(message(ERANGE, errno), errno == ERANGE);
	return 0;
}

static char* test_input_invalid(const char *str) {
	mu_assert("Error: expected: <false> but was: <true>", !input(str));
	mu_assert(message(EINVAL, errno), errno == EINVAL);
	return 0;
}

static char* test_input_1() {return test_input_out_of_range(NULL);}
static char* test_input_2() {return test_input_out_of_range("");}
static char* test_input_3() {return test_input("m");}
static char* test_input_4() {return test_input("N");}
static char* test_input_5() {return test_input("5");}
static char* test_input_6() {return test_input_invalid("_");}
static char* test_input_7() {return test_input("Mn");}
static char* test_input_8() {return test_input_invalid("-0");}
static char* test_input_9() {return test_input_invalid("0.");}
static char* test_input_10() {return test_input("A2z");}
static char* test_input_11() {return test_input_invalid("!00");}
static char* test_input_12() {return test_input_invalid("0_0");}
static char* test_input_13() {return test_input_invalid("00~");}
static char* test_input_14() {return test_input("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZab");}
static char* test_input_15() {return test_input_out_of_range("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabc");}

static char* all_tests() {
	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);
	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の場合はerrnoERANGEを設定し、falseを返す
  • 入力文字列が0文字または101文字以上の場合はerrnoERANGEを設定し、falseを返す
  • 入力文字列が英小文字、英大文字、数字以外の文字を含む場合はerrnoEINVALを設定し、falseを返す
  • 最後trueを返す

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


問題自体は条件分岐ループも使わずに解けるのに出してしまって申し訳ない💦

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

bool input(const char *str) {
	errno = 0;
	if (!str) {
		errno = ERANGE;
		return false;
	}
	size_t len = strlen(str);
	if (len < 1 || len > 100) {
		errno = ERANGE;
		return false;
	}
	for (const char *c = str; *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

エントリポイント

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


main.c
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include "input.h"

int main(void) {
	char str[128];
	if (scanf("%127s", str) != 1) {
		return EXIT_FAILURE;
	}
	if (!input(str)) {
		if (errno == ERANGE) {
			perror("The input value must be at least 1 character and no more than 100 characters.");
		} else if (errno == EINVAL) {
			perror("Each character of the input value must be a lowercase letter, an uppercase letter, or a digit.");
		} else {
			perror(NULL);
		}
		return EXIT_FAILURE;
	}
	if (puts(str) < 0) {
		perror(NULL);
		return EXIT_FAILURE;
	}
	return EXIT_SUCCESS;
}

このプログラムをコンパイルするコマンドは以下の様になります。

main.bat
gcc -Wall -Wextra -Werror -std=c99 main.c input.c -o main 1>main.txt 2>&1
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?