カテゴリー別アーカイブ: TDD

書評 | テスト駆動開発(オーム社)

https://www.ohmsha.co.jp/book/9784274217883/

本書は、残念ながら絶版になってしまった「テスト駆動開発入門(ピアソン・エデュケーション)」を、日本におけるTDDの第一人者である和田卓人氏(@t_wada)が訳しなおしたものです。

しかし、本書はただ訳が新しくなっただけではありません。

  • サンプルコードの強化
    • 言語とテスティングフレームワークが執筆時の最新バージョンに!
    • サンプルコードの変更箇所がわかりやすく明示されるように!
    • TODOリストも箇条書きでわかりやすく!
    • 各章の最後にその省のサンプルコード全掲示!
  • 付録C「訳者解説:テスト駆動開発の現在」
    • 原著が出版されてからこれまでのTDDの経緯を和田さんが書き下ろし

といった特徴があります。この辺りは、和田さんのエントリや、ajito.fmの和田さん登場回などでも詳しく説明されていますので、そちらをどうぞ。

このエントリでは、これらのことを踏まえつつ、私が読んで思ったことを書いていきます。

訳註が最高!

本書を読んでいて、地味に素晴らしいと思ったのは、「訳註」の存在でした。

色々な人が本書のレビューを書いていますが、訳注に触れたものはあまりありませんでした。しかし、この訳註こそ、陰ながら読者を支える非常に重要な役割を果たしていると思います。

例えば、

  • 初出する用語について補足する
  • コードを変更してテストを実行した際の挙動を補足する
    (「ここで実行すると○○と××が失敗するが、□□を実施して実行すると成功するようになる」)
  • 過去のバージョンを対象とした記述について現在はどうであるのか補足する

といったケースがいくつも追加されています。これがあることで、読者は書籍を写経しながら読み進めながらも、迷いなくより現実にあった形を知ることができるようになっています。

おそらく、この訳註は本書の執筆中に著者、レビュアー、編集者とで何度も調整されたものでしょう。関係者の努力にただただ感謝するばかりです。

参考文献も最高!

本書は「テスト駆動開発」という「開発手法」を解説していることもあり、「設計原則」、「パターン」、「テスト手法」、「開発プロセス」など、多くのことを扱っています。そのため、それぞれについてより深く扱っている各種の書籍を、本書の「参考文献」から知ることができます。

また、より素晴らしいことに、前著出版後に改版されているものについても、しっかりと照会されています。本書の参考文献から、さらにたどっていくことで、膨大な先人の知識、ノウハウが得られることでしょう(このあたりは、TDDBCのクロージングセッションなどで、和田さんがよく言っていますね)。

全部書いてある!

前著を読んでからだいぶ経ち、久しぶりに本書を読んだのですが、それでもTDDに関する内容が全部書いてあることに驚かされました。

既にTDDを実践していたり前著を持っている人でも、本書の第Ⅲ部―中でも「第25章 テスト駆動開発のパターン」、「第26章 レッドバーのパターン」、「第27章 テスティングのパターン」、「第28章 グリーンバーのパターン」、「第29章 xUnitのパターン」、「第32章 TDDを身につける」―は読み返してみると、新たな発見や気付きが得られると思います。

 

本書は、昨今では当たり前になりつつある「テストを書く」というスキルを身に付けるのに、最初の一冊として非常に優れていると思います。ぜひ、書籍を手に取り、できれば和田さんも進めているように「写経」しながら読み進め、開発者として使える「武器」を増やしてほしいと思います。

テスト駆動開発(TDD) in .NET #ngtnet by @masaru_b_cl

はじめに

このエントリは2016/5/7に開催したNiigata.NET 2.0で行ったセッションを再構成したものです。

 

ライセンス

クリエイティブ・コモンズ・ライセンス
この 作品 は クリエイティブ・コモンズ 表示 – 継承 4.0 国際 ライセンスの下に提供されています。

 

Agenda

 

テスト駆動開発(TDD)とは

テスト駆動開発とは、Kent Beck(ケント・ベック)が提唱した開発手法です。彼の著書であり、「原典」とも呼ばれる「テスト駆動開発入門」(http://www.amazon.co.jp/dp/4894717115)(残念ながら絶版。再販望む!)にはこのように書いてあります。

「動作するきれいなコード」、ロン・ジェフリーズのこの簡潔な言葉は、TDD(テスト駆動開発)の目標である。動作するきれいなコードは、あらゆる理由で価値がある。

それでは、どのように「動作するきれいなコード」を目指していくのでしょう?

 

TDDの進み方

まず、「動作するきれいなコード」にたどり着くには2つの道があります。一つはきれいなコードを書きながら動作するように直していくもの。もう一つは動作するコードを書きながらきれいに直していくものです。

(図は日本におけるTDDの第一人者である「和田 卓人(@t_wada)」さんのスライド「TDDのこころ」より拝借しています。)

TDDはこのうち後者の「着実に一歩ずつ進む道」をとります。つまりまず動くようにしてからきれいにしていきます。なぜこの道かというと、何はともあれ「動作」していなければ、製品としての価値はないということが一つです。

他に、この「動かない」→「動く」の線を超えるときに多くの「気づき」が得られるということもあります。フィードバックは早ければ早いほど修正が容易ですので、まずは動くように書き、フィードバックを得て素早く修正していくのがTDDということになります。

 

TDDのサイクル

TDDはこの着実な道を次のようなサイクルで進んでいきます。

  1. 次の目標を考える
  2. その目標を示すテストを書く(テストファースト)
  3. そのテストを実行して失敗させる(Red)
  4. 目的のコードを書く
  5. 2で書いたテストを成功させる(Green)
  6. テストが通るままでリファクタリングを行う(Refactoring)
  7. 1~6を繰り返す

そしてこの手順を小さく回すことで、素早いフィードバックを得ることができます。

これをTDDの界隈では「黄金の回転」と呼んでいます。これはくるくると回りながらどんどんと高みを目指していくイメージから、荒木飛呂彦先生の「スティール・ボール・ラン」に出てくる「黄金の回転」が連想されるため、このように呼ぶようになったそうです。

(スティールボールラン、荒木飛呂彦著より)

 

動作するきれいなコード

では、「動作するきれいなコード」とは何でしょう?それは原典の該当箇所の原文を見ればわかります。

「動作するきれいなコード」は「Clean code that works」であり「Beautiful」ではないのです。つまりは整頓されたコードがきれいなコードであるということになります。

とはいえ、もう少し具体的な指針が欲しいところです。そこで、「きれい」なコードとは何かを知ることのできる書籍をいくつか紹介します。

まずは「リーダブルコード」です。サンプルコードはC#など.NET言語ではありませんが、「リーダブル≒読みやすい」コードを書くためのテクニックに「名前」を付けて完結にまとめているため、最初に読む1冊としては最適です。

次に「リファクタリング」です。コードの挙動を変えずに構成を直す「リファクタリング」のテクニックについて網羅的にまとまっています。「きれいなコード」にどのように直せばよいのかを学ぶのによいでしょう。なお、この本もTDD入門と同じく一度絶版になりましたが、オーム社により再販されています。

これまでの2冊は実際のコードに近い話でしたが、「きれいな」コードは「きれいな」設計から生まれます。そのため、オブジェクト指向プログラミング言語を使ったアプリケーションの「設計」に関する原則を学ぶことも重要です。それを学ぶための書籍を2つ紹介します。

まずは「C#実践開発手法」です。これはその名の通り、C#を使っていわゆる「SOLID原則」と呼ばれるような設計原則を、実際にどのように使えばよいのか実例とともに紹介しています。こういった分野の書籍には「アジャイルソフトウェア開発の奥義」がありましたが、コードがJava(もしくはC++、Smalltalk)だったので、C#でコードが読めるというだけでも本書はありがたいです。もちろん、内容もより新しいものになっています。

そして、オブジェクト指向プログラミング言語の設計原則について学ぶなら「Head First デザインパターン」がおすすめです。本書のタイトルは「デザインパターン」となっていますが、実際はSOLID原則やハリウッド原則といった基本原則を学ぶのに最適な本だと私は思っています。サンプルコードはJavaですが、C#を知っていればそれほど苦も無く翻訳して読めるでしょう。

また、きれいなコードは既存のライブラリ等との統一感も重要です。特に.NET Frameworkは標準ライブラリが巨大なので、勝手なルールでライブラリを作ってしまうと、利用者が混乱してしまいます。

そこで、標準ライブラリの設計ガイドラインを知るために、「.NETのクラスライブラリ設計」も強くお勧めします。この本は単にライブラリ設計のガイドラインが書いてあるだけでなく、過去の失敗についての.NETの中の人の議論が掲載されていることもおすすめポイントの一つです。

 

TDDの目的

さて、こういった書籍などを参考にしつつも「きれいなコード」を目指すことがTDDの目標であることはすでに述べたとおりです。では、目標のさらに先にある「TDDの目的」はなんでしょうか。それは、一言でいうと「健康」です。

TDDはテストを書きますが、それは大きな目的を実現するための手段でしかありません。TDDで目標とするのは「動作するきれいなコード」は、すなわち「変化に対応」しやすいコードであるともいえます。

変化に対応しやすいコードは、言い換えれば不安が少ないコードだともいえます。ユーザーの要望をかなえるためには、コードを変化させなければいけませんが、「動作するきれいなコード」ならば、安心して変更することができます。

そして、この「不安が少ない」状態を一言で表せば、それが「健康」であるということです。

そして、「健康」なコードは不安が少ないため、開発者自身の「健康」にもつながります。一人一人の開発者が健康なら、チーム全体も「健康」であり、ひいては開発、運用している製品、サービスとその開発者、ユーザーなど関係者全員の健康にもつながります。

この大きな目的のために、まずは目標として「動作するきれいなコード」を維持するよう、TDDを行っていきます。

 

.NETにおけるTDD

TDDがどういうものかわかりましたが、.NET開発においてTDDをどのように進めていけばよいのでしょうか?次の4つの項目に焦点を当て、順に説明していきます。

  • テスティングフレームワークおよびサポートツール
  • ソリューション構成
  • テストコード
  • テスト実行

 

テスティングフレームワーク

まずは、.NETで使える主なテスティングフレームワークをいくつか紹介します。

まず最初は「MSTest」です。MSTestはVisual Studioに標準搭載されたテスティングフレームワークで、現在はExpress Editionでも使用できるため、お手軽さはNo.1です。

次は「NUnit」です。JUnitをはじめとした、いわゆるxUnit系列の正統派オープンソーステスティングフレームワークで、MSTestがVSの下位エディションに搭載されるまでは、デファクトスタンダードでした。

最後は「xUnit.net」です。上記2つより新しく、テストコードの書き方等を一から見直したテスティングフレームワークで、MSのASP.NETチームなどでは、最近のオープンソースプロダクトのテストに使用されています。

ではどのテスティングワークを選べばよいのでしょうか?個人的にはMSTestかNUnitをお勧めします。

MSTestは何といってもVSを入れれば入っているというお手軽さが一番で、必要最小限の機能はあるので、入門には最適だといえるでしょう。また、NUnitは歴史があることもあり、Web上の日本語情報も多く、MSTestよりは高機能(パラメタライズド・テストなど)です。

サポートツール

次にサポートツールについて見ていきましょう。まずは「テストアダプター」です。

MSTestは標準でVS上からテストを実行することができますが、その他のテスティングフレームワークは、専用のテストランナーを使う必要がありました。それを改善し、VSからテストを実行できるようにするのが、「テストアダプター」です。

テストアダプターを導入すれば、NUnitやxUnit.netのテストコードも、VS上から実行して結果を確認できるようになります。

また、テストコードの書き味を改善するため、「Chaining Assertion」というライブラリもお勧めです。

Chaining Assertionはテストコードの書き方を、A.Is(B)の形式で書けるようにします。MSTestの他、NUnitやxUnit.netにも対応しています。後で実例を出すときに、Chaining Assertionを使ったコード例を使って説明します。

 

ソリューション構成

次にTDDを行うソリューションの構成を見ていきましょう。.NETでTDDする場合、テスト対象プロジェクトとテストプロジェクトの複数プロジェクト構成にするのが一般的です。

まずテスト対象プロジェクトは*.exe形式の実行ファイルや*.dllのクラスライブラリどちらでも大丈夫です。ただ、クラスライブラリにする方が一般的ではあります。

そして、テストプロジェクトが別になるため、テスト対象の型は原則publicとします。internalな型についても、テスト対象プロジェクトにアセンブリ属性InternalsVisibleToを使い、テストプロジェクトに対してのみ公開することもできます。

次にテストプロジェクトですが、MSTestを使う場合は「単体テストプロジェクト」を選択します。他のOSSテスティングフレームワークを使う場合は、クラスライブラリプロジェクトを作成した後、使用するテスティングフレームワークへの参照を追加します。

 

テストコード

次にテストコードの構成を見てみましょう。今回はMSTest+前述のChaining Assertionを使ったテストコードについて説明します。


using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Test.MSTest
{
[TestClass]
public class UnitTest1 //テストクラス
{
[TestInitialize]
public void SetUp() { /*テスト初期処理*/ }
[TestCleanup]
public void TearDown() { /*テスト後処理*/ }
[TestMethod]
public void TestSomething()
{
//テスト主処理
“実際の値”.Is(“期待する値");
}
}
}

まず、MSTestのテストクラスであることを示すため、TestClass属性をクラスに付けます。テストクラスはpublic出なければならないことに注意してください。

次に、実際のテストを行うメソッドにはTestMethod属性を付けます。テストメソッドもpublicとし、戻り値はvoid、引数はなしとします。テストメソッドは必要な数だけ複数個作ってかまいません。テストメソッド内では、実際の値と期待値が等しいことを、Isメソッドを使い検証します。

この他、それぞれのテストメソッドが実行される前に毎回実行したい処理、例えばテスト対象オブジェクトの初期化などは、TestInitialize属性のついた、いわゆるSetUpメソッドに書きます。同様に、テストメソッド実行後に毎回実行したい後処理は、TestCleanup属性のついた、いわゆるTearDownメソッドに書きます。

 

テスト実行

こうして作成したテストコードは、Visual Studio上から実行すると、「テストエクスプローラー」にテストメソッドごとに成功、失敗、そして失敗ならその失敗した箇所の情報が表示されます。

このようにして、VS上でテストを行っていきます。

 

TDDの実例

それでは、実際にTDDを行っている様子の実例を見せましょう。今回はFizzBuzzを題材として使用します。作成したコードは、GitHubにアップしているので、併せて参照してみてください。

 

設計

まず最初に行うのは「設計」です。作成するプログラムの仕様を元に、ざっと設計して必要と思われるタスクをToDoリストとして作成します。最初にこの工程を行うことで、事前の目標付けとともに、仕様であいまいな部分やどのようにテストを行うのかといった不安要素をなるべくなくします。

 

最初のテスト作成

ToDoリストを作成したら、その中から一つ選び最初の「失敗する」テストを作成します。今回は「正の整数の場合、その数値を文字列で返す」を選び、このことをテストするために「引数が1の場合、”1″を返す」ことを確認するテストコードを書きます。

この時、自分が「最初のユーザー」となり、使いやすいAPI設計を考えることが重要です。それには、型名やメソッド名、引数の名前や型、戻り値の型など、様々な観点があります。

今回はまずテスト対象プロジェクトFizzBuzzとテストプロジェクトFizzBuzz.Testを作成し、FizzBuzz.TestプロジェクトにFizzBuzzTest.csを追加してFizzBuzzTestクラスを作成しました。なお、テスティングフレームワークにはMSTestを使い、Chaining AssertionをNuGet経由でインストールしています。

PM> Install-Package ChainingAssertion


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FizzBuzz.Test
{
[TestClass]
public class FizzBuzzTest
{
[TestMethod]
public void 引数が1の場合文字列で1を返す()
{
// arrange
var fizzBuzzer = new FizzBuzzer();
// act
string result = fizzBuzzer.Say(number: 1);
// assert
result.Is("1");
}
}
}

view raw

FizzBuzzTest.cs

hosted with ❤ by GitHub

(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/af2891d63f95b2ea60c88e643b335d0ca14159bd/FizzBuzz.Test/FizzBuzzTest.cs)

.NET開発で最初のテストコードを書く時のポイントは以下の通りです。

  • 戻り値の型を明示する
  • 引数の型を明示する
  • 名前付き引数で引数名を明示する

こうしておくことで、次のフェーズを行う際、VSの機能を使ってコードを自動生成できます。

またこの他、テストコードは適宜日本語で書くことで、その意図がわかりやすくなることもあります。特にC#はメソッド名に使える文字が限られているため、日本語の表現力に頼るのは理にかなっています。

 

最初のテスト失敗

テストコードを作成したら、テストを実行して失敗することを確認します。ここではテストの役割のうち「正しく失敗する」ことを確認することが重要です。

なお、「最初の失敗するテスト」の失敗にはコンパイルエラーも含めてよいです。この段階ではFizzBuzzerクラスは無いのでコンパイルエラーになるはずです。

 

仮実装(Fake It)

テストが正常に失敗することを確認したら、いよいよテスト対象コードの実装に入ります。まず最初に行うのは、「仮実装(Fake It)」です。仮実装とは「テストが成功する最速の実装」のことで、リテラル定数を使ってテストが成功するように書いてしまいます。最初に作成したテストコードであれば、戻り値として”1″を返せば、テストが成功するはずです。

一見意味がないようにも見えますが、この手順には失敗しようのない状態で「テストが正しく成功する」ことで、テストの妥当性を担保するという目的があります。もし仮実装でもテストが失敗するようなら、その後本当の実装にしてもテストが成功するわけはないのですから。

VSで仮実装を行う際、まずはテストコードからテスト対象クラスを生成します。コンパイルエラーとなっているFizzBuzzerクラスの箇所でCtrl+.キーを押し、「新しい型の生成…」を選択します。

generate-class

そして、表示された「型の生成」ダイアログにて、「種類」にclass、「場所」にFizzBuzzプロジェクト、そして「ファイル名」に”FizzBuzzer.cs”を指定して「OK」ボタンをクリックします。

generate-class-02

すると指定した通り、FizzBuzzerクラスがFizzBuzzプロジェクトのFizzBuzzer.csファイルに作成されます。


namespace FizzBuzz
{
public class FizzBuzzer
{
public FizzBuzzer()
{
}
}
}

view raw

FizzBuzzer.cs

hosted with ❤ by GitHub

次に、FizzBuzzTest.csに戻り、コンパイルエラーとなっているSayメソッドの生成を行います。Ctrl+.を押し、「メソッド ‘FizzBuzzer.Say’ を生成します」を選択します。

generate-method

すると、未実装のメソッドSayが生成されます。


using System;
namespace FizzBuzz
{
public class FizzBuzzer
{
public FizzBuzzer()
{
}
public string Say(int number)
{
throw new NotImplementedException();
}
}
}

view raw

FizzBuzzer.cs

hosted with ❤ by GitHub

テストクラスで引数名、型、戻り値の型を明示していたことで、それに合わせたシグネチャでメソッドが生成されます。

最後に、Sayメソッドが1を返すように仮実装すれば終了です。


using System;
namespace FizzBuzz
{
public class FizzBuzzer
{
public FizzBuzzer()
{
}
public string Say(int number)
{
return "1";
}
}
}

view raw

FizzBuzzer.cs

hosted with ❤ by GitHub

(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/b89706c18621ab0302e3fe524539281e0a4cdd39/FizzBuzz/FizzBuzzer.cs)

この時、FizzBuzzTest.csファイルのSayメソッド呼び出しか所でAlt+F12を押すことで、「定義をここに表示」すると便利です。

peek-definition.jpg

 

最初のテストの成功

 

テストを実行し、成功することを確認します。Ctrl+R, Aキーを押すことで、すべてのテストを実行できます。

success-first-test.jpg

無事テストが成功しました。

 

リファクタリング

テストが成功したので、今度はリファクタリングです。テストが成功する状態を保ったまま、コードを整理します。言い換えれば、挙動を変えずに構造を改善するということです。

なお、テストコードを整えるのもリファクタリングの一つです。ここでは、テストコード内で明示した型や引数名を取り除いてみましょう。


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FizzBuzz.Test
{
[TestClass]
public class FizzBuzzTest
{
[TestMethod]
public void 引数が1の場合文字列で1を返す()
{
var fizzBuzzer = new FizzBuzzer();
fizzBuzzer.Say(1).Is("1");
}
}
}

view raw

FizzBuzzTest.cs

hosted with ❤ by GitHub

(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/42483eb7eaa217d80440a026572ffd211bb3ddab/FizzBuzz.Test/FizzBuzzTest.cs)

コードを変更したら、テストを実行して成功することを確認しておきましょう。

 

次の失敗するテストを追加する

先に進むため、「小さな一歩」となるような新たなテストを追加します。この時、先ほど仮実装したメソッドにて、同じ仕様を満たすもう一つのテストコードを追加することで、実装1点に対してテスト2点の「三角」で進めることを「三角測量」と呼びます。今回の例でいえば、「正の整数の場合、その整数を文字列にして返す」という仕様に対して、最初に作った1を”1″にして返すテストに加え、2を”2″にして返すかどうかを確認するテストを追加することで、三角測量を行います。

 


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FizzBuzz.Test
{
[TestClass]
public class FizzBuzzTest
{
[TestMethod]
public void 引数が1の場合文字列で1を返す()
{
var fizzBuzzer = new FizzBuzzer();
fizzBuzzer.Say(1).Is("1");
}
[TestMethod]
public void 引数が2の場合文字列で2を返す()
{
var fizzBuzzer = new FizzBuzzer();
fizzBuzzer.Say(2).Is("2");
}
}
}

view raw

FizzBuzzTest.cs

hosted with ❤ by GitHub

(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/f21f34237754464babe6e6746a7f52df94295f4b/FizzBuzz.Test/FizzBuzzTest.cs)

 

すべてのテストを実行

テストを追加したらすべてのテストを再実行し、追加したテストだけが失敗することを確認します。もしこの時複数のテストが失敗するようであれば、それは良くない設計であることを示す臭いです。「良くない」とは仕様でなく内部実装に対してテストしている、責務分割が不十分で一つの変更があちこちに影響するような作りになっている可能性が高いのです。

failure-second-test

無事2つ目のテストだけが失敗しました。

 

テストが成功するまで修正

あとは、失敗した2つ目のテストについて、テストが成功するまで実装を行います。このとき、仮実装で使用した定数、リテラルを、引数や変数を使うように直します。サンプルでは、Sayメソッドの引数numberを文字列にして返すようにします。


using System;
namespace FizzBuzz
{
public class FizzBuzzer
{
public FizzBuzzer()
{
}
public string Say(int number)
{
return $"{number}";
}
}
}

view raw

FizzBuzzer.cs

hosted with ❤ by GitHub

(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/f21f34237754464babe6e6746a7f52df94295f4b/FizzBuzz/FizzBuzzer.cs)

 

修正後にテストを実行し、2つのテスト両方が成功することを確認します。

success-second-test

 

再びリファクタリング

2つのテストで共通で使っているfizzBuzz変数をフィールドにしてSetUpメソッドで初期化したり、整数が整数の文字列を返すテストは一つあれば十分なので、一方を消したりします。


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FizzBuzz.Test
{
[TestClass]
public class FizzBuzzTest
{
private FizzBuzzer fizzBuzzer;
[TestInitialize]
public void SetUp()
{
fizzBuzzer = new FizzBuzzer();
}
[TestMethod]
public void 引数が正の数の場合文字列でその数を返す()
{
fizzBuzzer.Say(1).Is("1");
}
}
}

view raw

FizzBuzzTest.cs

hosted with ❤ by GitHub

(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/f3ffd123918d51a20a2e6cb8b4892097a9adccbe/FizzBuzz.Test/FizzBuzzTest.cs)

テストを実行し、成功することを確認します。

 

ToDoリストを更新する

ここまでで事前に作成したToDoリストの最初の仕様の実装が終わったので、完了したタスクを消してしまいます。

 

次のテストを追加する

今度は別の仕様について実装していこうと思うので、次のテストを作成し、追加します。3の倍数のテストを追加してみます。


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace FizzBuzz.Test
{
[TestClass]
public class FizzBuzzTest
{
private FizzBuzzer fizzBuzzer;
[TestInitialize]
public void SetUp()
{
fizzBuzzer = new FizzBuzzer();
}
[TestMethod]
public void 引数が正の数の場合文字列でその数を返す()
{
fizzBuzzer.Say(1).Is("1");
}
[TestMethod]
public void 引数が3の倍数の場合Fizzを返す()
{
fizzBuzzer.Say(3).Is("Fizz");
}
}
}

view raw

FizzBuzzTest.cs

hosted with ❤ by GitHub

(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/f4b7c5a74d3ccada1787a583b96ab66d613705e4/FizzBuzz.Test/FizzBuzzTest.cs)

 

明白な実装

実装が自明である場合、仮実装をスキップしてしまっても構いません。これを「明白な実装」と呼び、イメージとしては開発スピードアップのため、ギアをあげるイメージです。今回は引数を3で割った余が0の時に”Fizz”を返すよう実装します。


using System;
namespace FizzBuzz
{
public class FizzBuzzer
{
public FizzBuzzer()
{
}
public string Say(int number)
{
if (number % 3 == 0)
{
return "Fizz";
}
return $"{number}";
}
}
}

view raw

FizzBuzzer.cs

hosted with ❤ by GitHub

(https://github.com/masaru-b-cl/ngtnet2-fizzbuzz/blob/da6c8c0288634b67789fcfa17965bf4ff761910c/FizzBuzz/FizzBuzzer.cs)

 

以上のような手順を繰り返して、ToDoリストの項目を全部消したら、実装が完了したということになります。

 

TDDの指針

最後に、TDDを行う上でのいくつかの指針を紹介します。

 

いつTDDを行うか

まず、「いつ」TDDを行えばよいのかというと、それは「不安」を感じたとき、ということになります。例えば、ライブラリの使い方に自信がなかったり、第三者の実装が信用できない時など、開発に関連する不安があれば、それをテストで保護してあげるのです。

ただし、今作成しているのが「使い捨て」のコードであれば、TDDを行わない方が良いこともあります。テストコードを作成しても、そのテストが数回しか実行されないようであれば、テストはただのコストでしかありません。

その他、GUIのコードについてはTDDを行いません。GUIの自動化テストを作成するには非常に手間がかかり高コストな作業です。しかし、GUIは一番ユーザーに近いところであることから、変更が頻繁に起こります。そうすると、せっかく作ったテストもすぐに変更しないといけなくなってしまいます。

また、トライ&エラーであれこれ試すような時もTDDは向いていません。ただし、あれこれ試した結果、確定した実装に対してテストを書いても問題ありません。「テストファースト」にはこだわらなくても大丈夫です。

 

どんなテストが良いテスト?

一般的に「良い」とされるテストの特徴に「F.I.R.S.T」と呼ばれるものがあります。

  • Fast(高速)
    テストの実行がすぐ終わること
  • Independent(独立している)
    他のテストへの影響がなく、任意のテストメソッド単体でも実行可能であること
  • Repeatable(繰り返し実行可能)
    何度実行しても同じ結果となること
  • Self-Validationg(手動での検証がない)
    テストコードのみでテストが完結していること。
  • Timely(テストファースト)
    最初にテストを書いていること

これら5つの特徴には優先度があり、特に重要なのはIとR、続いてF、S、Tです。少なくともIとRだけは考慮したテストを書きましょう。

 

どのようにテストを行うか

ToDoリストを元にどのようなテストを行えばよいのかについては、各種のテスト技法を参考としましょう。基本的なテスト技法として、同値クラス、境界値分析くらいは考慮したテストを書きましょう。

 

既存コードがある場合

「テストがないコードはレガシーコードだ!」のキャッチコピーでおなじみの「レガシーコード改善ガイド」を参考に、小さく初めて少しずつ範囲を広げていきましょう。

 

最後に

最後に大事なことですが、TDDはあくまで「センス」ではなく「スキル」です。訓練次第で誰でも習得画家のなものなのです。たまに「テストが書けるようになったらTDDをやってみよう」みたいなことを聞くことがありますが、「できるようになったら始める」というのは永遠に始めることはできません。まずは始めてみて、うまくいかなければ直せばよいのですから。

また、本来はチームを巻き込んだ形TDDができるのが理想ですが、まずはあなた一人でも始めることができます。やってみて、良いものであれば他の人に勧めて、徐々にTDD人口を増やしていきましょう。

そして、TDDによって安心を得て、「健康になりましょう」

 

参考資料

 

Xamarin向けC#+NUnitのスケルトンを作りました #tddbc

TDDBC for C# with NUnit on Xamarin Studio

MSTest用のスケルトンはすでに作ってありましたが、NUnitのスケルトンはまだ作ってなかったので作りました。対象はXamarin Studioです。

MonoDevelopでもたぶん動くんだとは思いますが、環境がないので未検証です。

XamarinでTDDBCに参加される奇特な方は、是非ご利用ください。

テストケースの分類法あれこれ in C# with MSTest #TddAdventJp #TDD by @masaru_b_cl

はじめに

本エントリは「TDD Advent Calendar 2013」の参加エントリです。

8日目は@biacさんの「TDD 最初の一歩 (C#編) #tddadventjp: TDD.NET」でした。

 

テストケースの分類

TDD限らず自動化テストを行っていると、どんどん増えていくテストケースをいかに整理、管理していくかが課題の一つとなってきます。整理できていないと、テストコードの可読性低下、不要なテストが残ることによるテストの実行速度低下、思いがけないレッド(=テスト失敗)などにつながります。

本エントリではC#およびMSTestを使い、「xxがxxの場合、xxがxxならばxxとなる」を表すテストケースの分類方法をまとめてみます。

 

テストケース名での分類

一番シンプルなのが、テストケース名で分類する方法です。

 


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TddAdventJp2013.Test
{
[TestClass]
public class テストケース名
{
[TestMethod]
public void Aがa1の場合・Bがb1ならばc1となる()
{
}
[TestMethod]
public void Aがa1の場合・Bがb2ならばc2となる()
{
}
[TestMethod]
public void Aがa2の場合・Bがb1ならばc3となる()
{
}
}
}

 

メリット

  • なんといってもシンプル

デメリット

  • 視認性がいまいち
    image
  • 「、」がテストケース名に使えないので「・」で代用しないといけない
  • テストケース名がいわゆる半角数字で始められないなど、C#の言語仕様に制限される

 

カテゴリーを使った分類

カテゴリーをつけるための属性を使用する方法です。

 


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TddAdventJp2013.Test
{
[TestClass]
public class カテゴリー
{
[TestCategory("Aがa1の場合")]
[TestMethod]
public void Bがb1ならばc1となる()
{
}
[TestCategory("Aがa1の場合")]
[TestMethod]
public void Bがb2ならばc2となる()
{
}
[TestCategory("Aがa2の場合")]
[TestMethod]
public void Bがb1ならばc3となる()
{
}
}
}

 

メリット

  • テストケース名がシンプルになる
  • テストエクスプローラーで、カテゴリーごとに表示できる
    image
  • ファイルを横断してテストケースを分類できる

デメリット

  • 同じカテゴリーを何度も指定しないといけない
  • カテゴリーごとに表示しても階層は2階層止まり

 

内部クラスを使った分類

テストケースを内部クラスとして定義する方法です。

 


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TddAdventJp2013.Test
{
public class 内部クラス
{
[TestClass]
public class Aがa1の場合
{
[TestMethod]
public void Bがb1ならばc1となる()
{
}
[TestMethod]
public void Bがb2ならばc2となる()
{
}
}
[TestClass]
public class Aがa2の場合
{
[TestMethod]
public void Bがb1ならばc3となる()
{
}
}
}
}

 

メリット

  • テストケース名がシンプルになる
  • 「~の場合」といった内容を一度だけ書けばよい
  • テストエクスプローラーで、内部クラスごとに表示できる
    image
  • 複数階層もいける
  • 別の分類で同じテストケース名が使える

デメリット

  • 少々面倒
  • 内部クラス名、テストケース名がいわゆる半角数字で始められないなど、C#の言語仕様に制限される

 

番外:partialクラスの活用

C#はpatialクラスを使うことで、一つのクラスを複数ファイルに分割することができます。これを利用することで、同一テストクラスでも意味のあるまとまりごとにテストケースを分けて記載することが可能になります。

image

 


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TddAdventJp2013.Test
{
[TestClass]
public partial class partialクラス
{
}
}

 


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TddAdventJp2013.Test
{
public partial class partialクラス
{
[TestCategory("Aがa1の場合")]
[TestMethod]
public void Bがb1ならばc1となる()
{
}
[TestCategory("Aがa1の場合")]
[TestMethod]
public void Bがb2ならばc2となる()
{
}
}
}

 


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TddAdventJp2013.Test
{
public partial class partialクラス
{
[TestCategory("Aがa2の場合")]
[TestMethod]
public void Bがb1ならばc3となる()
{
}
}
}

 

最後に

テストケースを整理するために上にあげた方法は排他的なものではないので、それぞれ組み合わせることももちろん可能です。他にももっと良い分類方法などあれば、ぜひ教えてください。

なお、今回のサンプルコードは、GitHubにあげてあります。

https://github.com/masaru-b-cl/TDDAdventJp2013

 

次は

10日目はまだ空いているようです。そこのあなた!ぜひ書いてみてはいかがでしょう!

参加登録は「TDD Advent Calendar 2013の参加状況確認・参加登録 – Qiita [キータ]」から!

[2013/12/11追記]
10日目がアップされました! > @Lewuatheさん++

Agile Samurai Basecamp in Tokyo – The first cry of Atom

Quick Test SwitcherをVS2013に対応させました

Quick Test SwitcherをVS2012に対応させました

に引き続き、VS上でテストコードとプロダクトコードを一発で切り替えたり、テストを簡単に実行したりするVS拡張「Quick Test Switcher」をVS2013にも対応させました。なお、このパッケージもちろんVS2012にも対応します。

GitHubのDownload機能は使えなくなっちゃったので、SkyDriveに置いておきます。

QuickTestSwitcher – SkyDrive

コードは例によってGitHubです。

masaru-b-cl/QuickTestSwitcher

VS2013でも快適なTDDライフを!

#tddbc 長岡 1.0の演習をC#でやってみた – @masaru_b_cl

Javaの奇妙なバージョン – C#

 

ポイント

  • テスティングフレームワークはMSTest
  • サポートライブラリにChaining Assertion
  • テストケースにはカテゴリーを付け、partialクラスを用いて複数ファイルに分割
  • Versionという名前はSystem.Versionクラスで予約済みなので、JdkVersionに変更
  • JdkVersionはイミュータブルなValueObjectとして、structとして定義

 

というわけで、遅ればせながら演習をやってみています。

 

チャレンジングなこととしては、テストケースにカテゴリを付け、カテゴリ毎にpatialクラスに分割して記述しています。

image

image

 

テスト実行結果はこんな感じ。VS2012 Update 1の追加機能である、カテゴリごとのテスト結果表示を行っています。

image

 

分割したファイルをグループ化するには、*.csprojを直接編集して、対象ファイルCompile要素の中に、DependUpon要素として親となるファイルを指定するだけです。


<Compile Include="JdkVersionTest.cs" />
<Compile Include="JdkVersionTest.IsValid.cs">
<DependentUpon>JdkVersionTest.cs</DependentUpon>
</Compile>

view raw

grouping.csproj

hosted with ❤ by GitHub

 

VS2012 Pro以上ならば、VSCommands拡張を使って、GUIで簡単にグループ化できます。

image

 

まとめ

VSCommandsおすすめ!

#tddbc 長岡 1.0を開催しました – @masaru_b_cl

TDD Boot Camp 長岡 1.0 – TDDBC

TDD Boot Camp(TDDBC) – TDDBC長岡1.0

TDD Boot Camp(TDDBC) – TDDBC長岡1.0/演習

TDD Boot Camp(TDDBC) – TDDBC長岡1.0/KPT

 

5/18(土)に「TDDBC 長岡 1.0」を開催しました。

お集まりいただいた参加者の皆様を始めとして、講師として駆けつけてくださった@t_wada、TAとしてお手伝いいただいた@kenchan@setoazusa@ktz_alias@megascus@a_suenami、並びにスタッフとしてご協力いただいた@civic@dictav@ishiduca@yu_hori@hiro55bs、本当にありがとうございました。

 

TDDBCとしてのふりかえりは、TDDBC Wikiを見てもらうとして、このエントリでは「イベント主催者」としての感想を述べていきたいと思います。

 

  • スタッフの協力もありつつがなく終えることができて非常に良かった
    • イベント主催は、「コンダクター」としての立ち振る舞いが求められると感じた
    • 自分でガンガンやるよりは、タスクの明確化、適度なサイズへの分割、振り分けがメインだった
      • 他の人に委譲可能なタスクを見出すのは難しい
    • 当たり前だが、動かないと先に進まない
      • 動けば付随して何かしらタスクが発生する
      • TDD黄金の回転の、動かない→動くの象限への移動と同じく「気付き」がある
      • イベント発案したら、とにかく動くの大事
      • 必要なタスクが分かれば、人に任せることもできる
  • みんな生き生きとTDD、ペアプロを楽しんでもらえたようで何より
  • 緑川が思いのほか好評で面食らった
  • 地味にうれしかったのが、@t_wadaのサインをもらった人が、非常にうれしそうだったこと

    (きのこ本Tシャツを着て、きのこ本にサインを書く@t_wada)
    • 距離、時間の関係でそういう機会がなかった人に、その機会を提供できた
      • これは事前に想定していなかった
      • イベントを開いた甲斐があるというもの
  • スタッフはNDSで築いたネットワークをフル活用
    • 非開発者含め
    • 助かった反面、参加者として参加して欲しかったというのもある

 

色々とありましたが、まぁ成功と言っていいのではないかと思います。

この調子で、TDDBC 新潟、上越などが開かれるととっても嬉しいですね。もちろん長岡も継続開催の方向で!

 

でも、次は主催は他の人に任せたいなー。キーノートくらいやりますので、誰か燗酒がおいしい季節に企画してくださいw

TDD Boot Camp 長岡 1.0 参加者募集を開始しました #tddbc

TDD Boot Camp 長岡 1.0 – TDDBC

日時
2013/05/18 (土) 10:00 – 17:30

申込期限
2013/05/18 (土) 10:00

参加費

会場払い2,000円

開催場所
まちなかキャンパス長岡 5F交流ルーム
〒940-0062 新潟県長岡市大手通2-6 フェニックス大手イースト4F

参加者
0 / 20人

というわけで、遅くなりましたが参加者募集を開始しました。ふるってご参加ください。

また、TAなどのスタッフも引き続き募集しています。何かしらお手伝いしていただける方は、ご一報願います。

#tddbc Tyokyo 2013-03に参加してきました

3/16(土)に開催されたTDD Boot Camp Tokyo 2013-03 – TDDBCに参加してきましたので、レポートです。

 

ランチミーティング

今回のTDDBCは今年開催予定の福岡、長岡の主催者のサポートという意味合いがありました。そこで、イベント開始前にランチミーティングという形で、TDDBC開催に向けての相談をしてきました。

出席者は今回の主催者である@katzchangをはじめ、@t_wada、横浜の@grimerose、福岡を開催予定の@Spring_MT、そして私といった感じ。内容についてはまたTDDBC-MLTDDBC Wikiで共有しますので、そちらをご覧下さい。

 

キーノート

そして参加者が集まったところで、TDDBC開始です。すっかりおなじみとなったt_wadaのキーノートセッションです。

t_wadaのキーノートはスライドは何度も読んでいましたが、実際に観るのは初めてでした。もちろんスライドには乗っていないことが実際には多く、これはやはりセッションを観ないとわからないことが多いなと感じました。

メモから起こしてみます。

  • TDDBCのメインは、実はTDDではなく「ペアプロ」
    • ペアプロは楽しい、そして疲れるw
  • コードレビューもポイント
    • 普段の仕事では同じ問題を複数人で同時に解くことはまずない。
    • 他の人、言語のコードと比較することができる。
  • ふりかえりもポイント
    • KPT(Keep、Probrem、Try)でふりかえる
  • ソフトウェア開発の三本柱
    • バージョン管理(一番大事!)
    • テスティング
    • 自動化
  • バージョン管理
    • 人間は忘れる
      • いかに記憶を専用ツールに任せるか
    • メンテナンスと新機能の並行開発
      • VCSなしでは困難
        • セーブなしでRPGをやるようなもの
    • Before DVCS時代
      • CVSの貧弱なマージ機能を補完するため、人間が工夫していた
        • 手でブランチ図を描いて、マージポイントなどを管理していた
        • ただ、開発が進むと管理しきれなくなる
  • テスティング(Test-“ing”)
    • 名詞ではなく進行形
      • 書き”ながら”開発を進める
  • 自動化
    • 人間は
      • 飽きる
      • 疲れる
      • 繰り返しが苦手
      • 創造性がある
    • 機械は人間の逆
      • 飽きない
      • 疲れない
      • 繰り返しが得意
      • 指示したことしかできない
    • 機械と人間でコラボレーション
      • お互いの得意なところで、不得意なところを補完する
      • たとえば
        • コミット:人間
        • ビルド:機械
        • レポート:機械
        • 判断:人間
    • 人間→機械より、機械→人間の通知の割合を大きく
      • 人間は非同期に作業するのが得意
      • Jenkinsなどを活用し、機械の割合を増やす
  • 三本柱以外のメタファー
    • 三種の神器
      • レベルを上げて物理で殴れば、なくても何とかなりそう感あり
      • メタファーとしてはやはりどうかと
    • 三脚椅子
      • 今のところ一番しっくり
      • 先日論破された
        • 「足が一本もなければ安定するじゃん」
        • もっとも
  • テスト
    • コンテキストを合わせる
    • だれが何のために行うか
      • 開発者(TDDはこれ)
      • 顧客(ユーザー)
      • 品証(QA)
  • TDDとは
    • 原典(TDD入門)の書き出しがすべて
      • 「動作するきれいなコードを書き続ける」
  • 2つの道
    • きれいだけど動かないコードから動くコードへ
    • 汚いけど動くコードからきれいなコードへ
  • 動かないコード→動くコードに移るときに「気付き」がある
    • TDDは汚いコードでもまず動くようにする
    • この段階を小さく早く繰り返すことで、「気付き」のタイミングが多い
  • Red→Green→Refactoring
    • 黄金の回転
      • JoJo第7部 Steel Ball Runより
  • 汚いコード→きれいなコード
    • 実現するには「強い意志の力」が必要
    • 「汚さ」に敏感になる
    • 「リーダブルコード」がその助けとなる
    • 汚いコードはいずれ自分が嫌いになる
  • TDDのこころ
    • 一つずつ、少しずつ、段を小さく
      • 問題をBreakDownする
    • 複数を相手にしない、一人ずつ対処する
      • 1対1の連続にする
        • 思ったこと:ただ、疲れはする
      • TODOを先にリストアップ
        • やりやすいところから片付ける
    • 素早く回す
      • 回転が遅い、ということは何か問題がある証拠
    • 自分が最初のユーザー
      • 作る→使ってもらう→フィードバックでは遅いし漏れる
      • 作りやすい<->使いやすいのバランスを見極める
        • トレードオフになるものも多い
        • TDDが導く
    • 不安をテストに
    • 祈るのではだめ
      • テスト、VCS、自動化に守られた状態なら、祈る必要はない
    • 命綱を編む
      • 必要になってから編んでも、もう遅い
      • 日々の習慣に組み込むのがTDD
        • 編んだ命綱を自動化で回す
  • TDDの最大のメリット
    • 心理的なもの
  • TDD=バグ0ではない
    • バグをテストで保護して対処
    • バグのたびに強くなる
  • TDDのテストは目的でなく手段
  • TDDの目的は「健康」
    • コードの健康
    • チームの健康
  • TDD導入事例
    • IBM、MSの論文
      • バグは6割から最大9割削減
      • 実装時間は3から3割増
    • エリクソンの論文(アンケート結果の分析)
      • デバッグ時間の減少
      • コードの品質(外部/内部)の上昇
      • 実装時間は増えるが、トータルコストはむしろ減る
        • デバッグ時間の減少が大きい
  • デバッグ時間現象の意味
    • デバッグ→想定外の作業
    • デバッグを減らす→想定外の時間が減る
    • 計画した時間とのブレが少なくなる

 

TDDのペアプロデモ

キーノートセッション後は、@grimrose、@yattomによるペアプロデモです。FizzBuzzを題材にこんな感じでTDDを進めていました。

  1. TODOリストの作成
    インターフェースの検討もここで行っていました。たとえば、引数、戻り値の型、など。また、例外パターンの考慮も行い、「具体的な例外の型」については後回しにすることも決めていました。
    2以降でも途中で出てきた問題、課題は都度TODOリストに追加して忘れないようにする。
  2. 最初のテスト
    数字を入れたら数字を返すテストを作成し、コンパイルが通るようにしてテストが失敗することを確認するまで行いました。
  3. 仮実装
    テストを通す最短のコードを書くことを「仮実装」といい、「テストコードのテスト」の代わりとなるため、省かずに行ったほうがよい。
  4. 三角測量
    最初のテストに加えてもう一つテストケースを追加し、仮実装から本来あるべき実相を導きます。
  5. リファクタリング
    テストが通ったままの状態を維持しつつ、コードを整理します。
  6. 次のテスト
    Buzzを返すケースのテストなどを書いていきます。
  7. 明白な実装
    Buzzを返すケースができていればFizzを返すケースの実装も明白なので、三角測量はせずに一気に加速して実装してしまう。ただし、最小限のテストは書く。

個人的には、テストコードのリファクタリングのところも見たかったなぁ。

また、デモの途中で次のような質問が出ました。

Q:TDDはどのタイミングでコミットすればよいのか?

A:基本はGreenのタイミングで。ただ、GitなどのDVCSならば、Red、Green、Refactoringのそれぞれでコミットすることもある。ただ、この時もPushはGreenのタイミングで。

 

ペアプロ実習

デモが終わったところで部屋を移動し、ペアプロ実習タイムです。ペアの割合はPHP、Ruby、Javaの3強+他言語(C#含む)といった感じでした。

私は@aetos382と組み、VS2012、C#、MSTest with Chaining Assertionで臨みました。

 

お題

少し前にはやった「LTSV」ライブラリを少し改変したもので、@sue445さんがsue445/tddbc_tokyo_20130316 · GitHubにまとめてくれました。オリジナルのLTSVライブラリとの違いは、setterが変更前の値を返すこと、setした順番で中身をダンプすること、あたりです。

 

ペアプロの様子

まずTODOを2人で洗い出し、例外パターンなどの抜けがないかクロスチェックしました。ちょっとここで全部をやろうとしてしまったことは失敗だったかなと思っています。

そして実装開始です。我々はSet→Get→Dumpと進めていくことにしました。途中Setの値置き換えのテストを、Getを作成する前に行おうとしたところ、Getがないとテストできず、道を誤ったので一度戻り、今度はGetを実装する、といった進め方になりました。

また、最初はDictionary<string, string>をデータストアとしていたのですが、setした順序を保持するといった要件を実現するのに、うまくいかず途中でList<Tuple<string, string>>に切り替えることになってしまいました。

 

結果

LINQを活用したり、Chaining-Assertionを活用したりと、C#らしいコードになったこともあり、最後のコードレビューで採り上げられてしまいました。

 

クロージングセッション

最後にt_wadaによるクロージングセッションで、TDDの応用について話がありました。一言でまとめると、レガシーコード改善ガイドなどの本から他の本、他の本とたどり、スキルを高めていってほしい、といった内容でした。

 

まとめ

私の初めてのTDDBC参加は、こんな感じでした。今回の打合せ結果、イベント参加結果をもとに、5/18(土)に予定しているTDDBC長岡1.0に活かしていくつもりです。

あと、「お題」はまた改めてやり直してGitHubにあげてみようと思います。若干不本意な実装で終わってしまったので。

TDDしないケースについてのUncle Bobの考え

なごや方面のテストエンジニアの@kyon_mmさんが、こんなことを呟いていました。

で、紹介されていたのが、以下のエントリ。

The Pragmatics of TDD – The Clean Coder Blog

(※2019/1/16 エントリのURLが変わっていたので差し替え)

読んでみると、ボブおじさん(Uncle Bob)ことRobert C. MartinがTDDしないケースについて言及していて素晴らしいエントリでした。

ので、勝手訳してみます。(添削していただいた@liliputさんに感謝!)

Original blog entry is The Pragmatics of TDD

#The Pragmatics of TDD

#TDDの実例

So my last blog: The Startup Trap raised quite a ruckus. Amidst the various shouts of agreement and support, there was also a group who vehemently disagreed. I’m not going to summarize all their disagreements here, since I’ve already used up my quota of curse words for the month. But one of those disagreements struck me as something I should address.

前回のblog「スタートアップの罠」は物議を醸した。様々な同意や補助といった叫び声のど真ん中に、激しく異議を唱えるグループがいた。その意見をまとめることはしないけど、思いつく限りの汚い言葉を全部使いきっちゃったよ。でも、言及する価値があると思われる、とても面白い意見がひとつあった。

It’s the old argument of pragmatism vs. dogmatism. In essence, the complaint was that I was being too dogmatic. That TDD might be great in some cases, but in others it might have too high a cost. So you have to be pragmatic and choose wisely.

「実用主義」対「教条主義」のよくある議論だよ。まとめると「私が教条的すぎる」ということだそうだ。TDDはあらゆるケースで素晴らしいプロセスに違いないけど、高コストであるに違いない。だから、あんたは実用主義的になり賢明な選択をしなきゃならない。

At first this sounds perfectly reasonable. After all, pragmatism is a good thing, right? I mean, if you knew that TDD was going to make you miss your deadline; and that you could make the deadline by dropping it; you’d drop it, right?

一見するとこれは完全に理に適っているように聞こえる。結局のところ「実用主義は素晴らしい」、そうだろう?TDDでは締め切りを守れず、TDDをやめたら締め切りを守れるなら、TDDをやめる。違うかい?

Right. No question. And, in fact, there are times when that’s exactly the right course of action. The purpose of this blog is to lay out those times when I think TDD is too expensive.

そのとおり。疑問の余地はない。事実として、その一連の行動が正しいときはある。このblogの目的はTDDを使っても割に合わない場合を明確にすることにある。

But before I do I want to make a point. TDD is a discipline for programmers like double-entry bookkeeping is for accountants or sterile procedure is for surgeons. Are there times when accountants don’t use double-entry bookkeeping? Are there times when surgeons don’t use sterile procedure?

しかしその前に、言いたいことを言わせてもらおう。TDDは会計士にとっての複式簿記、外科医にとっての滅菌消毒と同じように、プログラマーにとっての”嗜み”だ。会計士が複式簿記を使わない時があるかって?外科医が滅菌消毒しない時があるかって?

The answer is yes in both cases. I rather doubt that accountants use double-entry bookkeeping when they balance their personal checkbooks, or when checking the total on a restaurant bill. I could be proven wrong about the former, after all, I used double-entry bookkeeping for years when balancing my checkbook. But I eventually grew to realize that the effort wasn’t worth the risk. As for the latter, I think we’d all agree that double-entry bookkeeping is overkill for a restaurant bill.

答えは両方「イエス」だ。会計士が個人的な小切手の勘定をしたり、レストランの支払いを集計するときに複式簿記を使っているとは思えない。前者については、経験上複式簿記を使うのは間違いだと分かった。数年間、自分で個人的な小切手を勘定するときに複式簿記を使ってみたんだけど、これにはそれだけのリスクを冒す価値が無いことに気付いたんだ。後者については、レストランの支払い集計に複式簿記を用いるのは、やりすぎだってことで同意が取れていると思っている。

As for surgeons and sterile procedure: I had a lipoma removed from my arm several years ago. My wife, an RN, observed the procedure. It was done under local in the doctors office. As the doctor was preparing I heard her question the doctor about the fact that he wasn’t using sterile procedure. He replied that for an operation of this size “clean procedure” was adequate. She and I accepted that statement; and the doctor completed the operation.

外科医と滅菌消毒については、何年か前に腕にできた腫瘍を取ってもらったことがあるんだ。僕の妻(看護師なんだけど)はその処置を見てたんだ。それは家の近くの診療所でやったんだよ。医師が処置する前に、彼女の「滅菌消毒しないの?」という質問が聞こえてきた。彼の答えは「このくらいの処置なら「洗浄処理」しとけばいいんだよ」だった。僕と彼女はそのことはに納得して、医師は処置を終えた。

A couple of days later, the incision became inflamed and painful. One of the sutures had become infected, and they had to reopen the incision and clean it out. I don’t know if this was because of “clean procedure” but from now on I will insist that the doctors who work on me use sterile procedure and not “clean procedure”.

二日後に、傷口に燃えるような痛みが現れた。縫い目の一部が炎症を起こしたんだ。で、彼らはもう一度傷口を開いて洗浄した。洗浄処理の理由は分からないけど、今からでも「洗浄処理」じゃなくて滅菌処理を医師にしてもらいたいよ。

Still, the point is valid. There are times when TDD is too costly, and a lower discipline should be used instead. I hope my stories have convinced you that those times are rare corner cases, and that the pragmatism meme should not be used to thwart your disciplines just because they seem inconvenient.

それでも、論点はあっている。TDDが高コストなら、代わりとなる低コストな原則を用いるべきだ。これらは重箱の隅をつつくようなまれなケースで、不便そうだからという理由で(TDDという)原則を曲げるために「実用主義というミーム」が使われるべきではない、ということを納得してくれたらいいなぁ。

The Pragmatics

So, when do I not practice TDD?

実際の例

では、TDDをしないのはどんなときか?

  • I don’t write tests for getters and setters. Doing so is usually foolish. Those getters and setters will be indirectly tested by the tests of the other methods; so there’s no point in testing them directly.
  • getterとsetterについてはテストを書かない。そんなことをするのはあほくさい。getterやsetterは他のメソッドのテストで間接的にテストされるだろうから(直接的にテストすることはTDDのポイントじゃないよ)。
  • I don’t write tests for member variables. They too will be tested indirectly.
  • メンバー変数にはテストを書かない。これも間接的にテストされるだろうから。
  • I don’t write tests for one line functions or functions that are obviously trivial. Again, they’ll be tested indirectly.
  • 明らかに些末なワンライナー関数についてはテストを書かない。もう一度言うけど、そういうのは間接的にテストされるだろうから。
  • I don’t write tests for GUIs. GUIs require fiddling. You have to nudge them into place by changing a font size here, an RGB value there, an XY position here, a field width there. Doing that test-first is silly and wasteful.
    • However, I make sure that any significant processing in the GUI code is removed into modules that are testable. I don’t allow significant code to go untested. So my GUI code is little more than glue and wires that pull data into place on the screen. (See articles on MVVM or Model View Presenter)
  • GUIについてはテストを書かない。GUIは「こねくりまわす」必要があるからね。フォントサイズを変えたり、色を変えたり、ポジションを変えたりするのに、いちいちちょこちょこ触らないといけない。テストファーストするなんてばからしいことこの上ないよ。
    • ただ、GUIコードを取り除いてテスト可能なモジュールに突っ込むのは重要なプロセスだと思ってる。重要なコードにテストを書かないなんて許されない。だから、僕のGUIコードはデータを引っ張ってきて画面に出すように紐づけてあるよ(MVVMやMVPの記事を見てくれ)
  • In general I don’t write tests for any code that I have to “fiddle” into place by trial and error. But I separate that “fiddling” code from code that I am surer of and can write tests for.
    • I have, upon occasion, fiddled code into place and then written tests after the fact.
    • I have also, upon occasion, deleted the code once “fiddled” and re-written it test-first.
    • Which of these you choose is a judgement call.
  • 一般的には、しっくりくるまでトライアンドエラーで”あれこれする(fiddle)”コードにはテストは書かない。でも”いじくりまわす(fiddling)”コードは確信をもってテストを書けるコードからは分けるようにしている。
    • ときどきは、しっくりくるまでいじくりまわした後にテストを書いている。
    • またときどきは、一度こねくり回し他コードを消してから、テストファーストで書き直している。
    • どっちを選ぶかは、みんなに任せよう。
  • A few months ago I wrote an entire 100 line program without any tests. (GASP!)
    • The program was a one-shot. It was used once and then discarded it. (It was for a special effect in one of my videos).
    • The program was all about the screen. In essence it was a pure GUI app. So I had to fiddle the whole thing into place.
    • I wrote it in Clojure, and so I had the REPL! I could run the growing program from the REPL, and I could see the results of every line of code I wrote instantly. It wasn’t TDD, it was EDD (Eye Driven Development).
  • 数か月前、全部で100行のプログラムをテストなしで書いた。(うぉっ!)
    • そのプログラムは一点物だった。一度しか使われずに捨てられたんだ。(僕のビデオに特殊なエフェクトをかけるためのモノだったんだ。)
    • そのプログラムは全部画面についてのモノだった。要するに純粋なGUIアプリだった。だから僕はしっくりくるまでコードをこねくり回す必要があった。
    • それはClojureで書かれ、REPL(Read Eval Print Loop、対話型評価環境)があった!REPLでプログラムを成長させながら走らせることができて、書いたコードの結果全てを、その場で見ることができた。TDDじゃないね、EDD(Eye Driven Development、目視駆動開発)だ。
  • I usually don’t write tests for frameworks, databases, web-servers, or other third-party software that is supposed to work. I mock these things out, and test my code, not theirs.
    • Of course I sometimes do test the third-party code if:
      • I think it’s broken.
      • The results are fast and predictable enough that a Mock would be overkill.
  • 普段フレームワーク、データベース、Webサーバーや、他の動作が保障されたサードパーティ製ソフトには、テストを書かない。こいつらはモックにして僕のコードのテストをする、そいつら(フレームワーク他自体)じゃなくね。
    • もちろん、たまにはサードパーティ製コードのテストを書くよ。例えば
      • こいつは壊れてると思うときとか
      • モックを使うより早く結果が分かって十分に結果が予想できるときはね。

It’s not all Dogma.

教条じゃないよ

This list isn’t complete. I’m sure I’ll think of some other times that I don’t write tests; but the spirit of this list ought to be evident. Pragmatics do come into play when doing TDD; it’s not all dogma.

このリストは完全じゃない。よく考えれば、他にもいろいろテストを書かないケースを思いつくだろうことはわかっている。でもこのリストの魂は明確にする義務がある。上の実例はTDDをするときに生きてくる。断じて教条じゃないんだ。

However, I respect the dogma; there is a reason for it. Pragmatics can sometimes override; but: I will not write any significant production code without making every effort to use TDD.

だけれども、教条もリスペクトしているし、そうするに足る理由がある。実例はたまに別の解釈をされることもあるけど、重要な製品コードはTDDを使う努力をせずには、書くことはないだろう。(訳註:TDDをはなから使わないという選択は無い、ということが「教条」ということかな?)

なお、他にも@Posauneさんが先に訳していましたので、一緒に紹介しておきます。

https://gist.github.com/posaunehm/5109379