Microsoft MVPである当社技術顧問の増田による、C#によるテスト駆動開発の講習会がありました。
システムガーディアン技術顧問
趣味はセミアコでブルースを弾くことと農園でラズベリーを育てることとF#でプログラミングすること。仕事は、ええ、びしびしとプログラミング技術を鍛えることと、ばしばしとC#でプログラムコードを書くことです。
テスト駆動開発 (てすとくどうかいはつ、test-driven development; TDD)
プログラム開発手法の一種で、プログラムに必要な各機能について、最初にテストを書き(これをテストファーストと言う)、そのテストが動作する必要最低限な実装をとりあえず行った後、コードを洗練させる、という短い工程を繰り返すスタイルである。多くのアジャイルソフトウェア開発手法、例えばエクストリーム・プログラミングにおいて強く推奨されている。近年はビヘイビア駆動開発へと発展を遂げている。
@see Wikipedia
テスト駆動開発のメリット
- テストファーストとして、自分が最初の使用者となりテストを行い、バグが少ないように少しずつ作っていくことが出来る。
- テストしやすさを意識してクラス設計をする為シンプルな構造になりやすい。
- 何度でも繰り返しテストすることが出来る
- テストコードを残しておくことで、ソース自体がドキュメントになる。
命題
1 2 3 |
IPv4 を渡されたときに、分解するして、以下のテストを通るように、 CheckIP#Check のメソッドの中身を書き替えよ - 文字列が正しくないときは、例外ではなくて false を返すこと |
CheckIP.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
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()); } } } |
空欄の場合メソッド部分で右クリックを行い【テスト実行】を選択します。
テストエクスプローラが表示され、空欄の場合メソッドがテストに失敗したことが表示されます。
ユニットテストが通るようにCheckメソッドを修正していきます。
空欄の場合のチェック処理を追加しました。IPv4アドレスが空欄の場合はfalseを返すようにします。
再度空欄の場合メソッドにて【テストの実行】を行います。
空欄の場合メソッドがテストを通過しました!
同じようにCheckメソッドをユニットテストそれぞれのメソッドを通るように追加、修正を行います。
全てのユニットテストに通りました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
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メソッド追記部分は私のソースで恐縮です。増田のソースではありません。