« [お知らせ] NUnit の使いかた記事、 2本追加しました | トップページ | [お知らせ] ただいま暫定公開中… »

2009年6月16日 (火)

[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_01

回答例のソリューション一式はこちら → 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();
  }
}

|

« [お知らせ] NUnit の使いかた記事、 2本追加しました | トップページ | [お知らせ] ただいま暫定公開中… »

*TDD の練習」カテゴリの記事

<NUnit>」カテゴリの記事

コメント

この記事へのコメントは終了しました。

トラックバック


この記事へのトラックバック一覧です: [TDD の練習] 文字を変換する:

« [お知らせ] NUnit の使いかた記事、 2本追加しました | トップページ | [お知らせ] ただいま暫定公開中… »