2014/11/01

【C#】paizaの煩わしいテスト(入力処理)を自動化したい

C# で paiza のテストをするのは非常に面倒。
なんせ「Console.ReadLine();」の入力は、コピペで入力できないし、手入力するにも限界がある。

ということで、テスト駆動開発を取り入れるために、入力処理を自動化する方法を考えた。


ちなみに、この記事はある問題に対する解答やヒントなどは一切書きません。
テスト方法だけなら、利用規則 第8条 10項には抵触しないですよね、paizaさん?


paiza (paiza)利用規約
第8条(禁止事項)
10項:ブログ・SNS等本サイト以外の媒体(インターネット媒体に限られず、不特定多数が閲覧可能なものを全て含む。)上において、当社が出題した問題の内容、当該問題に対する解答、解答へのヒント等の示唆及びカンニング等の不正を助長する内容等を掲載する行為.。

http://paiza.jp/guide/kiyaku



テスト(入力処理)を自動化する


// 0. メイン処理
static void Main()
{
    TestModule(@"C:\inputs.txt");
    System.Console.ReadKey();
}


// 1. テキストを読み込みキューに格納
static Queue<string> ReadFileIntoQueue(string file)
{
    var q = new Queue<string>();
    foreach (var line in File.ReadLines(file))
    {
        q.Enqueue(line.Trim());
    }
    return q;
}


// 2. テストモジュール
static int TestModule(string path = "")
{
#if DEBUG
    var inQ = ReadFileIntoQueue(path);
    var in1 = inQ.Dequeue();        // なんかして数値に変換
#else
    var in1 = Console.ReadLine();   // なんかして数値に変換
#endif

    int sum = 0;

    for (int i = 0; i < in1; i++)
    {
#if DEBUG
        var in2 = inQ.Dequeue();      // なんかして数値に変換
#else
        var in2 = Console.ReadLine(); // なんかして数値に変換
#endif
        sum += in2;
    }

#if DEBUG
    return sum;
#else
    Console.WriteLine(sum);
    return 0;
#endif
}

概要としては、Console.ReadLine() の代わりに、テキストファイルから読み込んだ文字列を使うことで自動化を実現している。

ちなみに、#if ディレクティブを入れたままで提出しても大丈夫!(確認済み)

性能を追い求めるなら、削除したほうが吉。
私は削除するのが面倒なので、そのまま提出している。


以降、処理内容をちょっとだけ説明する。



0. メイン処理


ただメソッドを呼んでいるだけ。
この部分をNUnitとかを使ってテストクラスに変えれば、テスト駆動開発ができる。



1. テキストを読み込みキューに格納


見ての通り、ファイルを読み込んでキュー(Queue)に格納している。

StreamReaderで毎回読み込むことも考えたけど、面倒だしコードを提出するときに不要な部分を削除しなければならない。
なので、ファイルの内容をキューに読み込み、それを使いまわすことで影響範囲を最低限にとどめている。


ちなみに、テキストファイルの中身はこんな感じになる。
以下の例は、「3 [Enter]」、「1 [Enter]」、「2 [Enter]」、「3 [Enter]」と入力したのと同じだ。

3
1
2
3



2. テストモジュール


ここがメイン。


#if ディレクティブを使うことで、デバッグモードのときだけテキストを読み込んだキューを使い(Dequeue)、それ以外はConsol.ReadLine()を使うようにしている。

結果の表示も同様に、デバッグモードの時はリターンで結果を返すだけで、リリースモードの時はコンソール画面に結果を出力している。


通常時の使用はオススメできないが、あえて「オプション引数」を使うことで、テスト以外も正常に動作するようになっている。

#if ディレクティブを使うことで可読性は犠牲にするものの、テストを自動化することができた。



(追記)コマンドライン引数を利用する


追記:2015/02/07
匿名さんから「お、なるほど!」というようなコメントをいただいたので、実際に試してみた。

方法は、VisualStudioの[ソリューションエクスプローラ]の[プロジェクト]を右クリック→[プロパティ]→[デバッグ]を選択。
開始オプションにある[コマンド ライン引数]に、inputs.txtの内容をコピペ


[コマンドライン引数]に設定した内容は、Mainメソッドに引数として渡される。
引数argsを使えば、この記事で紹介したことができる。

static void Main(string[] args)
{
    for (var i = 0; i < args.Length; i++)
    {
        var inputLine = args[i];
        Console.WriteLine(i + ":" + inputLine);
    }
}


ただ、ちょっと困ったことが…。
コマンドライン引数は、スペースが入るとそこまでで1つの入力(Console.ReadLine())とみなされてしまう。

何が問題かというと、実際にクイックウォッチで引数の内容を見ると…

このように、意図しないカタチになってしまう。


ダブルクォーテーションで囲んで、スペースで区切る


解決策として、コマンドライン引数にコピペするとき、1行ずつダブルクォーテーションで囲んで、それをスペースで区切ってあげると望むカタチの引数を得ることができる。

下図はみにくいけど、"-70 -35~"、"-69 4 ~"の前に半角スペースが入っている。



完成!!


以上

written by @bc_rikko

2 件のコメント :

  1. Visual Studio のプロジェクトの設定でファイルからリダイレクションした方が早いのでは?

    デバッグ → 開始オプション → コマンドライン引数を
    < input.txt
    にして、input.txt にテスト内容をコピペするだけです。

    返信削除
    返信
    1. コメントありがとうございます!
      さっそく試してみた結果を、この記事に反映しました。

      ちょっとインプットのデータを加工がありますが、こっちの方が簡単にできますね。

      削除