先日、TDD(テスト駆動開発)をやってみる会に参加してきました。
JUnitの存在は知っていたんだけど、どういう風にテストコードを書いていけばいいのかよくわかってなかったので、手っ取り早く知ってる人がいるイベントに参加してしまおうというモチベーションで参加。最近はもっぱらC#での開発ばかりなのでC#で参加しました。ちなみに20人ちょい参加者がいた気がしますがC#使いが自分を含めて二人でした。
PHP,Java使いが多く、Python,Objective-Cなどもチラホラ。そもそもPHPでテスト環境があることを知らなかった(そんな自分も実はC#を使い始めてからまだ2ヶ月と少し)。研究室のレガシーシステムの書き換えでハードウェア制御の関係でVBかC#(orC++)を選ぶことになり、なんとなくJavaに構文が似てそうなC#を選んでせかせか書いてます。
☆まずはテスト駆動開発についての解説がありました。
1.(失敗する)テストを書く(RED)
2.テストが成功するような実装を書く(GREEN)
3.リファクタリングする(REFACTOR)
という流れで開発を行っていくことを基本とするそうです。
テストする内容は、不安なところ、そもそも動くかわからないところ、特定の入力が来たらどうなるかといったような点から始めていくと良いとのこと。こうしていくことで安心できるコードを書けたり、API設計について考えることができるようになるようです。ひと通り説明が終わったところで定番のFizzBuzzの実装を例にテスト駆動開発のデモをしてくださいました。デモはPHPで行われたんですが、PHPしばらく読み書きしてなかったので違和感が…笑。若干必死に画面に書かれるコードを追っていってテスト駆動開発の進め方を眺めました。
☆続いて、同じ言語使用者同士でペアを作りTDDの実践をしました。
お題は「身長と体重を入力して、横に大きいかどうかを判定するサービスを実装せよ」といったもの。C#チームの我々はVisual Studio 2012 Professionalについているテスト環境を使って作業を始めました。ちなみにExpress Editionだとテスト環境がついていないのでご注意。学生の特権でMicrosoftが無料で提供してくれているのを使ってます。ありがとうMicrosoftさん。
まずはテストコードから書き始めます。こんな感じ。
BMI指数を計算するメソッドのテスト
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SampleLibrary.Test
{
[TestClass]
public class SampleTest
{
[TestMethod]
public void TestBmi()
{
BmiCalc bmi = new BmiCalc();
//Wikipediaより身長1.6m 体重50kgのときBMI指数は19.5になるとのことで
//まずはこうなるように実装することを目指す
Assert.AreEqual(bmi.Calc(1.6, 50), 19.5);
}
}
この状態でまずはテストを実行すると当然メソッドを実装していないので結果はRED(失敗)です。
実装に移ります。今回はあんまり深いことを考えずまず適当に動くコードを書き始めちゃいました。こんな感じ。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleLibrary
{
public class BmiCalc
{
public double bmi = 0d;
public double Calc(double height, double weight)
{
return bmi = Math.Round(weight / (height * height), 1);
}
}
}
実装が終わったところでテストを実行するとGREEN(成功)が点灯、結構嬉しい!笑。計算が絡むとPHPとかだと数値の扱いとか結構面倒くさそうで実際、終了後の発表で入力値として整数を仮定していたチームがありました。BMI指数計算が無事実装できたのでいろいろ追加していって結局こんなかんじになりました。
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace SampleLibrary.Test
{
[TestClass]
public class SampleTest
{
[TestMethod]
public void TestBmi()
{
BmiCalc bmi = new BmiCalc();
Assert.AreEqual(bmi.Calc(1.6, 50), 19.5);
}
[TestMethod]
public void TestAreFat()
{
BmiCalc bmi = new BmiCalc();
bmi.Calc(1.6, 50);
Assert.AreEqual(false, bmi.AreFat);
}
[TestMethod]
public void TestBmiState()
{
BmiCalc bmi = new BmiCalc();
bmi.Calc(1.6, 50);
Assert.AreEqual(BmiState.普通, bmi.State);
bmi.bmi = 18.5;
Assert.AreEqual(BmiState.普通, bmi.State);
bmi.bmi = 25;
Assert.AreEqual(BmiState.肥満1度, bmi.State);
bmi.bmi = 30;
Assert.AreEqual(BmiState.肥満2度, bmi.State);
bmi.bmi = 35;
Assert.AreEqual(BmiState.肥満3度, bmi.State);
bmi.bmi = 39.99999999999999999999999999999999999999999;
Assert.AreEqual(BmiState.肥満4度, bmi.State);
bmi.bmi = 40;
Assert.AreEqual(BmiState.肥満4度, bmi.State);
}
[TestMethod]
public void TestNinpu()
{
Assert.AreEqual("","");
}
}
}
実装コード
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SampleLibrary
{
public class BmiCalc
{
public double bmi = 0d;
public bool AreFat
{
get { return bmi >= 25; }
}
public virtual BmiState State
{
get
{
if (bmi < 18.5d)
return BmiState.低体重;
else if (bmi < 25.0d)
return BmiState.普通;
else if (bmi < 30)
return BmiState.肥満1度;
else if (bmi < 35)
return BmiState.肥満2度;
else if (bmi < 40)
return BmiState.肥満3度;
else
return BmiState.肥満4度;
}
}
public double Calc(double height, double weight)
{
return bmi = Math.Round(weight / (height * height), 1);
}
}
public enum BmiState
{
低体重,
普通,
肥満,
肥満1度,
肥満2度,
肥満3度,
肥満4度
}
}
妊婦と乳幼児はBMI指数の扱いが代わるようでそこも実装しようと思ったのですが時間切れでした。最初からそういうものだとわかっていたら、Personクラスを作って身長、体重、種別(一般・乳児・妊婦)といったデータを保持させ、BMI計算関連はstaticクラスにまとめたほうがスッキリしそうだったので反省。ただ実装コードでBmiState列挙型をつかったあたりは良い設計だとエンジニアの方から褒めて頂けたのですごい嬉しかったです。結構褒められると嬉しい。
それとC#使いのペアの方がすごい優秀でいろいろ教えてもらいました。TDDの実践が終わる頃にはドミノ・ピザが会場に到着し、アサヒビールとともに充実した時間をすごせました。特にみんな自分の使った言語に愛着を凄い持ってて、みんなPython使おうとか、Erlangを使おうとかいう話で盛り上がりました。勿論自分はC#を推しておきました。あと研究室にある8インチフロッピーとか5インチフロッピーの話をしたら若干盛り上がりました。まさかこんなところで役に立つとは…笑
非常にためになったので、コーディングする人は(次回がもしあったら)是非足を運んでみてください。