C#のコンソール入出力でテストする


概要

PaizaやCodeIQ,その他オンラインジャッジでC#を実行するときに,入出力の確認を楽に,かつちゃんとしたい.
毎回コンソールに貼り付けるのはもう面倒くさくて嫌だ.
かといって入力を埋め込むのはうっかりそのまま提出して死亡とかしそう.

あと他にも,コンソール入出力でテストしたいときって結構あるんじゃないかな.本当はモジュール化された単位で単体テストすべきだろうけど,そうともいかない要件も往々にしてあるだろう.

そこで,コンソール入出力を使って単体テストを構成する.

先行研究

  • [paiza]C#のテストを楽に行う方法
    • 非常に参考になるが,提出コードと同じファイルのMainの下の方にテスト用コードが増えてるのがイケてない.あと,出力が全部正しいかは結局手動で確認しないといけない.

環境

Visual Studio 2015 Community
まあ特に変なことやってないんで他でもできると思うが,確認したのはこれ.

プロジェクト構成

こんな感じのプロジェクトになってることを想定する.*.txtは全て「出力ディレクトリにコピー」を「常に」か「新しい場合はコピーする」にする.

ConsoleApplication1.sln
- ConsoleApplication1.csproj
    - Program.cs
        - class Program
            - Main(string[]) : void
- ConsoleApplication1Tests.csproj
    - Input1.txt
    - Input2.txt
    - Output1.txt
    - Output2.txt
    - ProgramTests.cs

ProgramクラスのMainメソッドが提出コード,ProgramTestsからProgram.Mainを呼び出してテストする.

テストコード

ProgramTests はこんな感じ.
ConsoleInOutをファイルからのStreamReaderStringWriterにセットして,吐き出されたコンソール出力と想定する出力を比較する.

ProgramTests.cs
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using ConsoleApplication1;

namespace ConsoleApplication1Tests
{
    [TestClass()]
    public class ProgramTests
    {
        [TestMethod()]
        public void MainTest()
        {
            TestInOut("Input1.txt", "Output1.txt");
        }

        [TestMethod()]
        public void MainTest2()
        {
            TestInOut("Input2.txt", "Output2.txt");
        }


        public void TestInOut(string inputFileName, string outputFileName)
        {
            using (var input = new StreamReader(inputFileName))
            using (var output = new StringWriter())
            {
                Console.SetOut(output);
                Console.SetIn(input);

                Program.Main(new string[0]);

                Assert.AreEqual(File.ReadAllText(outputFileName), output.ToString());
            }
        }
    }
}

例えば,Paizaのサンプル入出力なら,Program.csはそのまま提出用のコードを書いて,

Input1.txt
2 
2,5 
3,4

Output1.txt
hello = 2 , world = 5 
hello = 3 , world = 4

というふうにする.
Console.ReadLine() Console.WriteLine()を使う都合上,末尾に必ずひとつだけ改行を入れる.

しかしこのサンプルコード, using System; してるのに Console クラスを完全修飾で書いてたり Int32 だったりどうにかならなかったのだろうか.
大きなお世話だろうが using static System.Console; すれば(動作要件にはC#5と書いてあるけどmono 4.2.1なので動く)横スクロールも削減できるだろうに.

あとはこのテストを必要な分実行するなり,CIするなりすれば手軽にコード書きながらテストできる(はず).
別の問題に移るときも,入出力のテキストとMainだけ書き換えればいいので楽.

以上です.それでは,快適なC#ライフを.