C#

C#テスト駆動開発ユニットテスト編 TDD Visual Studio 2015

 

Microsoft MVPである当社技術顧問の増田による、C#によるテスト駆動開発の講習会がありました。

 

テスト駆動開発 (てすとくどうかいはつ、test-driven development; TDD)

プログラム開発手法の一種で、プログラムに必要な各機能について、最初にテストを書き(これをテストファーストと言う)、そのテストが動作する必要最低限な実装をとりあえず行った後、コードを洗練させる、という短い工程を繰り返すスタイルである。多くのアジャイルソフトウェア開発手法、例えばエクストリーム・プログラミングにおいて強く推奨されている。近年はビヘイビア駆動開発へと発展を遂げている。

@see Wikipedia

 

テスト駆動開発のメリット

  • テストファーストとして、自分が最初の使用者となりテストを行い、バグが少ないように少しずつ作っていくことが出来る。
  • テストしやすさを意識してクラス設計をする為シンプルな構造になりやすい。
  • 何度でも繰り返しテストすることが出来る
  • テストコードを残しておくことで、ソース自体がドキュメントになる。

 

IPv4 を渡されたときに、分解するして、以下のテストを通るように、
CheckIP#Check のメソッドの中身を書き替えよ
- 文字列が正しくないときは、例外ではなくて false を返すこと

CheckIP.cs内のCheckメソッドを書き換えて、UnitTest1.csのユニットテストをクリアするようにします。

CheckIP.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sgUnitTest
{
    // IPをチェックするクラス
    public class CheckIP
    {
        private int _ip1 = 0;
        private int _ip2 = 0;
        private int _ip3 = 0;
        private int _ip4 = 0;

        public int IP1 { get { return _ip1; } }
        public int IP2 { get { return _ip2; } }
        public int IP3 { get { return _ip3; } }
        public int IP4 { get { return _ip4; } }
        public override string ToString()
        {
            return $"{_ip1}.{_ip2}.{_ip3}.{_ip4}";
        }
        public bool Check( string ipaddr )
        {
            var ips = ipaddr.Split(new string[]{ "." }, StringSplitOptions.None);
            _ip1 = int.Parse(ips[0]);
            _ip2 = int.Parse(ips[1]);
            _ip3 = int.Parse(ips[2]);
            _ip4 = int.Parse(ips[3]);
            return true;
        }
    }
}

 

 

UnitTest1.cs

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;


namespace sgUnitTest
{
    [TestClass]
    public class UnitTest1
    {
        /* 
         * IPv4 を渡されたときに、分解するして、以下のテストを通るように、
         * CheckIP#Check のメソッドの中身を書き替えよ
         * 
         * - 文字列が正しくないときは、例外ではなくて false を返すこと
         */

        [TestMethod]
        public void TestMethod1()
        {
            string ipaddr = "127.0.0.1";

            var ip = new CheckIP();
            Assert.AreEqual(true, ip.Check(ipaddr));
            Assert.AreEqual(127, ip.IP1);
            Assert.AreEqual(0, ip.IP2);
            Assert.AreEqual(0, ip.IP3);
            Assert.AreEqual(1, ip.IP4);
            Assert.AreEqual("127.0.0.1", ip.ToString());
        }


        [TestMethod]
        public void 空欄の場合()
        {
            string ipaddr = "";
            var ip = new CheckIP();
            Assert.AreEqual(false, ip.Check(ipaddr));
        }

        [TestMethod]
        public void ピリオドが足りない場合()
        {
            string ipaddr = "255.255.255";
            var ip = new CheckIP();
            Assert.AreEqual(false, ip.Check(ipaddr));
        }

        [TestMethod]
        public void ピリオドが多すぎる場合()
        {
            string ipaddr = "255.255.255.255.255.255";
            var ip = new CheckIP();
            Assert.AreEqual(false, ip.Check(ipaddr));
        }

        [TestMethod]
        public void 数値が255を超える場合()
        {
            string ipaddr = "255.255.256.255";
            var ip = new CheckIP();
            Assert.AreEqual(false, ip.Check(ipaddr));
        }

        [TestMethod]
        public void 文字列を含む場合()
        {
            string ipaddr = "255.255.xxx.255";
            var ip = new CheckIP();
            Assert.AreEqual(false, ip.Check(ipaddr));
        }

        [TestMethod]
        public void 途中で空欄があっても大丈夫()
        {
            string ipaddr = "127. 0     .0.1   ";

            var ip = new CheckIP();
            Assert.AreEqual(true, ip.Check(ipaddr));
            Assert.AreEqual(127, ip.IP1);
            Assert.AreEqual(0, ip.IP2);
            Assert.AreEqual(0, ip.IP3);
            Assert.AreEqual(1, ip.IP4);
            Assert.AreEqual("127.0.0.1", ip.ToString());
        }

        [TestMethod]
        public void localhostを有効にする()
        {
            string ipaddr = "localhost";

            var ip = new CheckIP();
            Assert.AreEqual(true, ip.Check(ipaddr));
            Assert.AreEqual(127, ip.IP1);
            Assert.AreEqual(0, ip.IP2);
            Assert.AreEqual(0, ip.IP3);
            Assert.AreEqual(1, ip.IP4);
            Assert.AreEqual("127.0.0.1", ip.ToString());
        }
    }
}

 

visual_studio_unittest_20161103_1

空欄の場合メソッド部分で右クリックを行い【テスト実行】を選択します。

 

 

visual_studio_unittest_20161103_2

テストエクスプローラが表示され、空欄の場合メソッドがテストに失敗したことが表示されます。

 

 

 

visual_studio_unittest_20161103_3

ユニットテストが通るようにCheckメソッドを修正していきます。

 

 

 

 

visual_studio_unittest_20161103_5

空欄の場合のチェック処理を追加しました。IPv4アドレスが空欄の場合はfalseを返すようにします。

 

 

visual_studio_unittest_20161103_6

再度空欄の場合メソッドにて【テストの実行】を行います。

 

 

visual_studio_unittest_20161103_7

空欄の場合メソッドがテストを通過しました!

 

visual_studio_unittest_20161103_9_3

 

同じようにCheckメソッドをユニットテストそれぞれのメソッドを通るように追加、修正を行います。

 

visual_studio_unittest_20161103_10

全てのユニットテストに通りました。

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace sgUnitTest
{
    // IPをチェックするクラス
    public class CheckIP
    {
        private int _ip1 = 0;
        private int _ip2 = 0;
        private int _ip3 = 0;
        private int _ip4 = 0;

        public int IP1 { get { return _ip1; } }
        public int IP2 { get { return _ip2; } }
        public int IP3 { get { return _ip3; } }
        public int IP4 { get { return _ip4; } }
        public override string ToString()
        {
            return $"{_ip1}.{_ip2}.{_ip3}.{_ip4}";
        }
        public bool Check(string ipaddr)
        {

            //空欄の場合 false
            if (ipaddr == null || ipaddr == "")
            {
                return false;
            }

            //localhostを有効にする localhostの場合 127.0.0.1を返す
            if (ipaddr == "localhost")
            {
                return Check("127.0.0.1");
            }


            var ips = ipaddr.Split(new string[] { "." }, StringSplitOptions.None);

            //文字列を含む場合 try catchでfalse
            try
            {
                _ip1 = int.Parse(ips[0]);
                _ip2 = int.Parse(ips[1]);
                _ip3 = int.Parse(ips[2]);
                _ip4 = int.Parse(ips[3]);
            }
            catch { return false; }

            //ピリオドが多すぎる場合
            if (ips.Count() > 4)
            {
                return false;
            }

            //ピリオドが足りない場合
            if (ips.Count() < 4)
            {
                return false;
            }

            //数値が255を超える場合
            if (int.Parse(ips[0]) > 255) { return false; }
            if (int.Parse(ips[1]) > 255) { return false; }
            if (int.Parse(ips[2]) > 255) { return false; }
            if (int.Parse(ips[3]) > 255) { return false; }



            //上記バリデーションを通過でtrue
            return true;
        }
    }
}

 

 

このようにテスト用のクラスを作り、テストしながら少しずつ作っていく『テストファースト』によりバグの少ないスムーズな開発が出来ます。

 

 

//Checkメソッド追記部分は私のソースで恐縮です。

 

優 ( エンジニア )

 

システムガーディアン爆弾処理班
アクセス負荷対策などNginxへの移行案件が多いこの頃。IBM SoftLayerやAWSなどクラウド案件も多くなってきました。

Amazonおすすめ

iPad 9世代 2021年最新作

iPad 9世代出たから買い替え。安いぞ!🐱 初めてならiPad。Kindleを外で見るならiPad mini。ほとんどの人には通常のiPadをおすすめします><

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)