[TDD の練習] 文字を変換する
ネタ元 ⇒ わんくま掲示板 : 以下について教えてください
もうタイトルからして、 学校の課題だか宿題だかって感じがビンビンしますけど。
仕様としては、 こんな感じらしい。
・ 大文字は小文字に、 数字は '0', '1', '2' ... を '9', '8', '7' ... と反転させる。
・ ただし、 '#' 以降の文字は変換しない。
・ 例として "Abc012_59F_#012Gh" を渡した場合の戻り値は "abc987_40f_#012Gh" となる。
で。 みきぬ さんが VB の Select ~ Case で綺麗に条件分岐を書いてくれたり、 επιστημηさんが華麗なワンライナーのワザを披露してくれたりしてます。 なんかもう、 短さ競争になってますが… ここはひとつ、 TDD やって長くする方向で。
まずは、 仕様をちゃんと把握しましょう。
入力: string 型の引数が 1つ
出力: string 型のメソッドの返値 ( 1つ )
…でよさそうですね。
では、 入力パターンは?
入力には、 4種類の文字があります。
1. 英大文字 (ただし、ASCII 互換のみ)
2. 数字 (ただし、ASCII 互換のみ)
3. '#'
4. それ以外
入力される文字は 0個以上。 上記 1.~4. の組み合わせは任意です。
…こんな感じかな?
最初に提示された仕様で不明なところを、 補ってます。
さあて。 入力される文字は 4種類ですが、 その組み合わせは?
文字の長さに限定がありませんから、 string 型に格納できるまでだとすると、 もう無限大といっていいほどの組み合わせが出てきます。
※ 文字列の処理 ( 変換やフィルタリングなど ) には、 たいてい、 この全組み合わせを網羅しきれないという問題が出てきます。 そこで代表的なものに絞ってやるわけですが、 それに失敗したり、 あるいは最初からいいかげんにやってたりすると、 脆弱性のある Web アプリが出来上がっちゃったりします。
※※ 余談の余談。 難しいとわかってるからこそ、 「文字列エスケープ処理を噛ますことで脆弱性に対処するというのは、 間違った方針だ」 と言われるのです。
では、 どうやってテストケースを絞り込むのでしょう?
TDD の場合、 答は 「開発中のコードを見ながら」 です。
例えば、 for ループを回していて、 ループ内の処理が毎回同じなら、 インデックス ( 文字列中の位置 ) の違いによってメソッドの挙動が変わることはないでしょう。 であれば、 入力文字列が 1文字でテストしようと 100文字でテストしようと同じことです。 そうではなくて、 1文字目と 2文字目以降で異なる処理をしているなら、 それぞれをテストしないといけません。
※ それが漏れてると、 こういうバグが出たりするわけです。
⇒ Microsoft Connect ID=381451 「"愛々,123".IndexOf(",")が不正な結果を返す」
それでは、 入出力表です。
【入出力表-1】 入力が 0文字のとき
入力 出力
---- ----
null null
"" "" ( 空文字 )
【入出力表-2】 入力が 1文字
A a … 入力文字種 1. → 半角に変換
0 9 … 入力文字種 2. → (9 - 入力された数字)
# # … 入力文字種 3. → 変換無し
x x … 入力文字種 4. → 変換無し
【入出力表-3】 入力が 2文字
1文字目によって、 2文字目に対する振る舞いが変わる可能性があるもののみ。
→ 1文字目が '#' のときだけ。
※ 上述したように、 for ループ内で 1文字目と 2文字目以降の処理が同じ、 という前提に立っている。
※ もし総当りをやると、 4 x 4 = 16 通りになる。
#A #A … 入力文字種 3. + 1. → 変換無し
#1 #1 … 入力文字種 3. + 2. → 変換無し
【入出力表-4】 入力が 3文字
1文字目・2文字目によって、 3文字目に対する振る舞いが変わる可能性があるもののみ。
→ '#' が複数あってもトグル動作はしない。
##A ##A … 入力文字種 3. + 1. → 変換無し
以上から、 ユニットテストを書いてみてください。
ただし、 【入出力表-2】 のところ。 表には代表する文字ひとつだけしか書いてありませんが、 境界値のテストも必要でしょう。
回答例のソリューション一式はこちら → WankumaHomework20090610_20090616.zip
[6,666バイト]
※ C# 2008 Express + NUnit 2.5 用
以下は、 完成した Homework20090610 クラスです。 50行近くになっちゃいました。
public class Homework20090610 {
private static char ReverseNumber(char c) {
int p = "0123456789".IndexOf(c);
if (p >= 0)
return "9876543210"[p];
return c;
}
private static char ToLower(char c) {
//if (char.IsUpper(c)) … これでは全角英字も含まれてしまう
if ('A' <= c && c <= 'Z')
return c.ToString().ToLowerInvariant()[0];
return c;
}
public static string Homework(string input) {
if (string.IsNullOrEmpty(input))
return input;
StringBuilder buf = new StringBuilder(input.Length);
bool isEscaped = false;
const char EscapeChar = '#';
for (int i = 0; i < input.Length; i++) {
char c = input[i];
if (!isEscaped) {
if (c == EscapeChar)
isEscaped = true;
else {
c = ReverseNumber(c);
c = ToLower(c);
}
}
buf.Append(c);
}
return buf.ToString();
}
}
| 固定リンク
「*TDD の練習」カテゴリの記事
- TDD 最初の一歩 (C#編) #tddadventjp(2013.12.08)
- [TDD の練習] WinForm を改造したい ~ GUI に埋もれたロジックを分離して、ユニットテストを書く(2009.08.17)
- [TDD の練習] 文字を変換する(2009.06.16)
「<NUnit>」カテゴリの記事
- [NEWS] NUnit Test Adapter for VS 2012 and 2013 1.0 RC(2013.09.17)
- [NEWS] NUnit 2.6.2 リリース ~ async/await に対応!(2012.11.16)
- [NEWS] NUnit 2.6.1 リリース ~ NuGet に対応(2012.08.17)
- [記事紹介] CodeZine ~ C#で始めるテスト駆動開発 第4回/第5回(2012.07.10)
- [記事紹介] CodeZine ~ C#で始めるテスト駆動開発 第2回/第3回(2012.04.13)
この記事へのコメントは終了しました。
コメント