読者です 読者をやめる 読者になる 読者になる

Natural Software

KinectなどのDepthセンサーを中心に活動しています

TDD BootCamp の宿題・その1 #tddbc

C# テスト
  • 追記
    • 時間計算の部分を変えてきれいにした


TDD BootCamp でできなかった項目の一つ、時間のやつをやってみた。
システム時刻を変えることで、すぐにテストができるようにした。
# Windows 7 だと管理者権限で VisualStudio を起動しないと変わらない・・・


ソースの元ネタ

改編後

# VisualStudio で Git を扱うのをやってないので SVN で勘弁してください^^;

Before

[TestMethod]
public void 削除スパンを設定してそれを超えて設定すると前のは消える()
{
    LruCache lruCache = new LruCache( 5, 100 );
    lruCache.Put( "a", "aaa" );
    Thread.Sleep( 1000 ); // 1sec

    lruCache.Put("b", "bbb");

    Assert.AreEqual(1, lruCache._orderedList.Count);
    Assert.IsNull(lruCache.Get("a"));
    Assert.AreEqual("bbb", lruCache.Get("b"));
}

After

[TestMethod]
public void 削除スパンを設定してそれを超えて設定すると前のは消える()
{
    LruCache lruCache = new LruCache( 5, 1000 * 60 ); // 60sec
    lruCache.Put( "a", "aaa" );

    // 59 秒では消えない
    StepSeconds( 59 );

    Assert.AreEqual( "aaa", lruCache.Get( "a" ) );
    Assert.AreEqual( 1, lruCache._orderedList.Count );

    // もう一秒たつと消える
    StepSeconds( 1 );

    Assert.IsNull( lruCache.Get( "a" ) );
    Assert.AreEqual( 0, lruCache._orderedList.Count );
}

private static void StepSeconds( int seconds )
{
    DateTime stime = SystemTime.Get();
    stime += new TimeSpan( 0, 0, seconds );
    SystemTime.Set( stime );
}

あと

むりくり時間を変えているので、テストの開始時の時間をと取ってテストの終了時に戻すようにした

SystemTime.SYSTEMTIME stime;

// 各テストを実行する前に、TestInitialize を使用してコードを実行してください
[TestInitialize()]
public void MyTestInitialize()
{
    stime = SystemTime.Get();
}

// 各テストを実行した後に、TestCleanup を使用してコードを実行してください
[TestCleanup()]
public void MyTestCleanup()
{
    SystemTime.Set( stime );
}

システム時刻を変更するコード

ざっと作ってこんな感じ

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace kaorun
{
    /// <summary>
    /// 
    /// </summary>
    /// <see cref="http://dobon.net/vb/dotnet/system/setlocaltime.html"/>
    public class SystemTime
    {
        #region import
        [DllImport( "Kernel32.dll" )]
        private extern static void GetSystemTime( ref SYSTEMTIME lpSystemTime );

        [DllImport( "Kernel32.dll" )]
        private extern static uint SetSystemTime( ref SYSTEMTIME lpSystemTime );

        private struct SYSTEMTIME
        {
            public ushort wYear;
            public ushort wMonth;
            public ushort wDayOfWeek;
            public ushort wDay;
            public ushort wHour;
            public ushort wMinute;
            public ushort wSecond;
            public ushort wMilliseconds;
        }
        #endregion

        public static DateTime Get()
        {
            SYSTEMTIME stime = new SYSTEMTIME();
            GetSystemTime( ref stime );

            DateTime dateTime = new DateTime( stime.wYear,
                                              stime.wMonth,
                                              stime.wDay,
                                              stime.wHour,
                                              stime.wMinute,
                                              stime.wSecond,
                                              stime.wMilliseconds );
            return dateTime;
        }

        public static void Set( DateTime dateTime )
        {
            SYSTEMTIME stime = new SYSTEMTIME();
            stime.wYear = (ushort)dateTime.Year;
            stime.wMonth = (ushort)dateTime.Month;
            stime.wDay = (ushort)dateTime.Day;
            stime.wHour = (ushort)dateTime.Hour;
            stime.wMinute = (ushort)dateTime.Minute;
            stime.wSecond = (ushort)dateTime.Second;
            stime.wMilliseconds = (ushort)dateTime.Millisecond;

            SetSystemTime( ref stime );
        }
    }
}

もちろんテストコードも

最低限だけど

[TestMethod]
public void 現在時刻が取得できること()
{
    SystemTime.SYSTEMTIME stime = SystemTime.Get();
    Assert.AreEqual( stime.wHour, DateTime.UtcNow.Hour );
    Assert.AreEqual( stime.wMinute, DateTime.UtcNow.Minute );
}

[TestMethod]
public void 現在時刻を変更できること()
{
    DateTime before = SystemTime.Get();
    before += new TimeSpan( 1, 0, 0 );
    SystemTime.Set( before );

    DateTime after = SystemTime.Get();
    Assert.AreEqual( after.Hour, before.Hour );
    Assert.AreEqual( after.Minute, after.Minute );
}

まとめ

こんな感じでOKかな。
ホントは時間をだますところは関数として切り出さないといけないんだろうけど、時間の計算部分ができてないのでまずはこれで。
時間計算を DateTime と TimeSpan を使えるようにしたのでいいかもしれん。


実は今の過程でプロダクトコードにも手を入れたけど、テストで保護されているので変更が問題ないことを確認して、テストのリファクタリングを行った。
これも TDD の効果なのでしょう:)