C言語でTDDを学ぶ?TDDでC言語を学ぶ?

Facebook上で議論になったのですが、C言語でTDDを勉強していると、「よりよい設計」になりにくいという話になりました。

例として整数の区間の最初のほうの課題を考えてみます。

課題1-1
下端点と上端点を与えて閉区間を生成しよう
区間から下端点と上端点を取得しよう

TDDで実装したコードとテスト

    // プロダクションコード
    int upper_point;
    int lower_point;
    
    // テストコード
    TEST(Range, Endpoint) {
        lower_point = 3;
        upper_point = 8;
        EXPECT_EQ(3, lower_point);
        EXPECT_EQ(8, upper_point);
    }

問題:どうしたら関数を導入できるでしょうか?

元のお題はオブジェクト指向を想定しているところがあって、「生成しよう」でオブジェクトを作り、「取得しよう」でオブジェクトから値を取り出すという形になるんですが、C言語では「値を与えて取り出す」のならば、変数があればいいよねと。とりあえずグローバルでも構わない、というのは、お題(要求仕様)に何も書いてないから。(externがないのでモジュール内に閉じていますけれど。逆に、プロダクションコードとテストコードを別モジュールにしたら、グローバルになってしまいますね。)

TDDのやり方に律儀に従うなら、新たなテストによって現在の(グローバル変数だけで生成は関数不要という)設計の限界が明らかになり、よりよい、シンプルな設計が導かれます。区間のお題の続きに「閉区間が別の閉区間と等しいか」があるので、複数の区間を扱うにはグローバル変数では困るよね……と思いきや、こうなります。

    // プロダクションコード
    int upper_point;
    int lower_point;
    
    int range_equal(int lower_point2, int upper_point2) {
        return lower_point == lower_point2 && upper_point == upper_point2;
    }

    // テストコード
    TEST(Range, Endpoint) { /* 略 */ }

    TEST(Range, Equal) {
        lower_point = 3;
        upper_point = 8;
        EXPECT_TRUE(range_equal(3, 8)); // [3,8]と[3,8]は等しいはず
        EXPECT_FALSE(range_equal(1, 9)); // [3,8]と[1,9]は等しくないはず
    }

うん、グローバルに設定したものとパラメータで渡すものを比較すればいいよね……間違ってはいない。

TDDでシンプルな解を追求する

TDDには(ひいてはXPには)「シンプルさ」という価値があります。TDDではシンプルな解をよしとします。ここには「(与えられた問題や制約をすべて充足するなかでもっとも)シンプル」という条件が隠れています。テストコードを眺めて、「比較対象を片方は変数に、片方はパラメータで渡すのは、シンプルでない」と思えば、以下のように書き直せます。

    // プロダクションコード
    int upper_point;
    int lower_point;
    
    int range_equal(int lower_point1, int upper_point1,
                    int lower_point2, int upper_point2) {
        upper_point = upper_point1;
        lower_point = lower_point1;
        return lower_point == lower_point2 && upper_point == upper_point2;
    }
    
    // テストコード
    TEST(Range, Endpoint) { /* 略 */ }
    
    TEST(Range, Equal) {
        EXPECT_TRUE(range_equal(3, 8, 3, 8)); // [3,8]と[3,8]は等しいはず
        EXPECT_FALSE(range_equal(3, 8, 1, 9)); // [3,8]と[1,9]は等しくないはず
    }

しぶといな、グローバル変数

TDDが自動的に良い設計を保証するわけではありません。「TDDを使ってよりよい設計に到達する」のと「TDDを使えばよりよい設計になる」の間には、深い深い溝があります。TDDを使うと設計品質が悪くなるという調査もあります。少なくとも、何が「良い設計」であるか、言語やライブラリやフレームワークのどんな特性を利用すれば「良い設計」になるか、知識がなければ設計として実現できません。

元々のお題にはありませんが、このようなグローバル変数を使った設計に対しては、別の課題を設定してあげるという手を思いつきました。グローバル変数を避けるべき理由として「いつの間にか値が書き換わって危険」という知識があります。range_equal()を呼ぶと書き換わってしまうので、自明でない挙動をする。そこで「比較しても値が書き換わらないようにする」という課題が考えられます。それをテストで表現すれば、TDDの流れで設計を改善できます。

    // テストコード ※失敗する
    TEST(Range, Equal_DoesNotChange) {
        lower_point = 3;
        upper_point = 8;
        range_equal(0, 5, 3, 8);
        EXPECT_EQ(3, lower_point);  // range_equal()を呼んでも変化しないはず
        EXPECT_EQ(8, upper_point);
    }

このテストは失敗するので、グリーンになるように実装を直します。目指すのはグローバル変数の除去ですが、そうすると実装を直す過程で上端・下端を生成したり取得したりというテストが、成立しなくなってしまいます。おまけに今書いたばかりのテストも、中身がなくなってしまいます(EXPECT_EQで比較するものがなくなってしまう)。

    // プロダクションコード
    int range_equal(int lower_point1, int upper_point1,
                    int lower_point2, int upper_point2) {
        return lower_point1 == lower_point2 && upper_point1 == upper_point2;
    }
    
    TEST(Range, Endpoint) {
        // なくなってしまった
    }
    
    TEST(Range, Equal) {
        EXPECT_TRUE(range_equal(3, 8, 3, 8)); // [3,8]と[3,8]は等しい
        EXPECT_FALSE(range_equal(3, 8, 1, 9)); // [3,8]と[1,9]は等しくない
    }
    
    TEST(Range, Equal_DoesNotChange) {
        // なくなってしまった
    }

なくなってしまったテストは、消してしまうのがいいでしょう。万が一あとで復活させたくなっても、一度書いたことはきっと憶えているはずです(それにバージョン管理してますよね?)

よい設計を目指すTDD

さて、話を前に戻して、「比較しても変化しないこと」という課題は、グローバル変数の除去を意図して導入しました。なぜグローバル変数を除去したかったのか?不用意にグローバル変数を用いるのは「悪い」設計で、しかしTDDのサイクルの中でグローバル変数を止める自明な方法が見つからなかったためです。ここではいくつかの議論が考えられます。Facebook上の組み込みTDD勉強会(クローズドグループ)での議論を踏まえています。みなさんありがとうございます。

  • グローバル変数の利用のような初歩的な設計上の問題は、設計判断を持ち出すまでもなく常識的に取り除く(最初から使わない)べきだ。そうしたレベルならコーディング標準で制限できるし、そういうレベルのプログラマはしっかり教育したりレビューで改善すべきだ。
  • このタイミングで無理にグローバル変数を除去する根拠はない。お題を進めて仕様を拡充していけば、自然にグローバル変数を止めたくなるときが来るはず。そのときにこそ、最適な設計判断ができるのであって、いま強引に変えるのは拙速で、YAGNIでムダだ。
  • 特にC言語ではデータ構造の自由度が(純粋なオブジェクト指向プログラミングに比べると)高く、初期に十分検討しないと後でリファクタリングするのに苦労する。TDDでもC言語を使うときは事前設計をもっと念入りにやったほうがいい。
  • 「シンプルにする」というTDDの方針だけでは、必ずしもよい設計に到達できない。十分な知識と広い視点で、より望ましい設計を考え、そちらに向かって(やや強引であっても)TDDで進んでいけばいい。

いずれの言い分も妥当ですし、環境や状況や人によって重視すべき事項は変わってきます。私自身は最後の考え方がわりと好きで、よりよい設計を仮説として(できればテストに書き)、TDDで実証するというアプローチを選ぶことが多いです。

グローバル変数の話は、これでいったん片付いたことにして、データ構造を整理することを考えてみます。上端と下端は常にペアで使うものなので、ひとまとめにしたいですね。方法はいくつか考えつきます。

  • 構造体を作る
  • 長さ2の配列を作る
  • 1個のINT32の上位16bitと下位16bitに格納する
  • など

どれが望ましいでしょうか。C言語の適用範囲を考えると。いずれも妥当な解法になり得ますが、ここでも「シンプルに」、つまり「与えられた仕様を満たす範囲でできるだけシンプルに」考えたいです。メモリや処理時間の制約がなければ、構造体が素直でしょうか。

    // プロダクションコード
    struct range {
        int lower_point;
        int upper_point;
    };
    
    int range_equal(struct range range1, struct range range2) {
        return range1.lower_point == range2.lower_point && range1.upper_point == range2.upper_point;
    }
    
    TEST(Range, Endpoint) {
        // 復活!
        struct range range1;
        range1.lower_point = 3;
        range1.upper_point = 8;
        EXPECT_EQ(3, range1.lower_point);
        EXPECT_EQ(8, range2.upper_point);
    }
    
    TEST(Range, Equal) {
        struct range range1;
        range1.lower_point = 3;
        range1.upper_point = 8;
        struct range range2;
        range2.lower_point = 3;
        range2.upper_point = 8;
        EXPECT_TRUE(range_equal(range1, range2)); // [3,8]と[3,8]は等しい
        range2.lower_point = 1;
        range2.upper_point = 9;
        EXPECT_FALSE(range_equal(range1, range2)); // [3,8]と[1,9]は等しくない
    }

構造体を導入したコードを見ると、どうもテストコードが煩雑で、これなら前のほうが良かったように見えますね。rangeを簡潔に生成する方法を導入すれば改善できそうですが、本稿では省略します。

知らない解法を使えるか

また話を少し戻して、構造体導入の判断を考えてみます。構造体を使おうと思うには、少なくとも構造体を知らないといけない。カジュアルにグローバル変数を使っちゃうプログラマーが構造体を知っているか、知っていても適切に使えるかは疑問です。

知らない解法を使うことは誰にもできません。ここでは構造体を取り上げていますが、「知ってはいるけどちゃんと使ったことのない○○○(任意の手法・技法)」と考えれば誰でも遭遇し得る状況です。こうした状況で、TDDは無力なのでしょうか。きちんと勉強した熟達プログラマしかTDDは活用できないのでしょうか。

その逆で、TDDは新しい技術を使いこなせるようになったり、未知の技術を発見する役に立ちます。それ自体がシンプルな解の探究になるためです。いまある問題に対してよりシンプルな解に到達するには、以下の4レベルを上がっていく必要があります。

  1. 動く
  2. よりシンプルに書き直す
  3. 他の書き方を複数試して、一番シンプルなものを選ぶ
  4. さらにシンプルな書き方を勉強してくる

グローバル変数の例ではレベル2を取り上げました。構造体を知っているけれどうまく使えないのであれば、レベル3にチャレンジする必要があります。先のコードでは構造体を導入したものの、かえって煩雑になってしまいました。

よりよい設計、よりシンプルな解法を見つけるには、レベル3の試行錯誤が必要になってきます。試してみる、他のアプローチで試してみる、リファクタリングでいろいろなパターンを見比べてみる。その中で一番よいと思えるものを残す。TDDの回転は一直線ではありません。行きつ戻りつしながら少しずつ上がっていくのです。

試行錯誤で最善と思える結果が出ても、まだよくなる可能性があるかもしれない。そう思えたときが、レベル4になってまったく新しい解法を探しに行くタイミングです。レベル3までをきちんとこなしていれば、問題について十分に理解できているはずです。いまある解法の効用と限界も知っているでしょう。それならば、新しい解法を見つけても適切に評価できます。中途半端な解法に飛びついたり、不適切な解法を盲目的にコピーしてしまうこともありません。

レベル3までは問題を深く理解するためのステップで、レベル4が本当の問題解決だと見ることもできるかもしれませんね。

スクラムマスターというファシリテーター

スクラムというソフトウェア開発プロセスには、スクラムマスターという役割がいます。スクラムではチームメンバーが信頼に基づいた協力関係の中で価値を創出し、ときには対立もしながらよりよい成果を模索し、チーム全体の成長とプロダクトの成功を目指します。その中でスクラムマスターは全体が円滑に進むようにし、明白なルール違反を指摘し(ルールの数はごくわずかです)、対立を解消し、チーム全体が与えられた状況で長期的に最善のパフォーマンスを出すことにコミットしています。ファシリテーターならスクラムマスターができるわけではありませんが、スクラムマスターはファシリテーターの役割を果たします。

以前に紹介したパターンによると、スクラムマスターには以下の8つの役割があります。

  • 触媒: スクラムに欠かせない大量のコラボレーションを発現させる。
  • 完成マスター: スクラムマスターはチームに完成の定義(Definition Of Done)を守らせる。
  • 牧羊犬: チームが常にプロセスを守るよう支える。監視して、ズレを補正する。
  • 鏡の剣士: スクラムマスターは鏡の剣士となり、チームの行動を投影してみせる。チームは自分自身の長所と弱点に気づく。
  • コーチ: チームに改善を促し、改善のためのアドバイスを与える。
  • チアリーダー: チームを元気づけ、ポジティブなフィードバックをする。
  • ファイヤウォール: 不必要な外部の影響からチームを保護する。
  • プロダクトオーナートレーナー: スクラムマスターはプロダクトオーナーがより上手になるのを手伝う。

この中では触媒と鏡の剣士が、スクラムマスターとファシリテーターの交叉する部分になっています。スクラムマスターはミーティング(スクラムではイベントやセレモニーと呼ばれる)のファシリテーションをします。スクラムに不慣れなチームは上手な進め方が分からないので、経験あるスクラムマスターがガイドすることになります。進行はもちろん、議論の活性化や、多様な意見を表出させたり、タイムボックスを守るのもスクラムマスターになります。

同時にスクラムマスターはチームの成長を促します。特に朝会(15分以内の毎日全員が参加するミーティング)は、進行をスクラムマスターからチームメンバーに委譲する最初のセレモニーとなることが多く、進め方を全員が把握したらスクラムマスターは後ろに下がって、チームにファシリテーションを任せます。他のミーティングも、多かれ少なかれチームが進行するように段々なっていきます。スクラムマスターはファシリテーションを手伝うこともあるし、100%チーム任せにすることもあります。

ミーティング以外でも、スクラムでは始終活発なコミュニケーションが起きています。スクラムマスターはその様子を見て、チームが進む方向を把握したり、長所短所に気づいたり、問題の芽を見つけたりします。スクラムマスターはコミュニケーションが上手くいくよう手伝いながら、そうした知識をできるだけ透明にチームに伝えて、チームが気づかずにいた点を直せるよう仕向けます。チーム内で対処しきれない問題があれば、スクラムマスターが代わって解決します。

いっぽうチームはスクラムマスターからの指摘を受けながら、スクラムの進め方やコミュニケーションの取り方に上達していきます。連携すべきステークホルダーとの関係性も、徐々に確かなものになっていきます。そうするとスクラムマスターは、今までと同じように観察していても、指摘することは減ってきます。問題(スクラムの言葉では障害物、インペディメント)の解決も、チーム内の問題から、チーム外へと重心が移っていきます。

ここでは一部しか例を挙げませんが、このように、ファシリテーターとしてのスクラムマスターは、ひとつの場をファシリテートする役割と同時に、その役割をチームが吸収できるよう成長させる役割を持っています。チームのミーティング、チームのコミュニケーション、チームの問題解決、いずれもチーム自身で対応できるのが一番効果的であると信じて、その方向に向かってゆくのもスクラムマスターの仕事です。

スクラムを導入した開発チームにおいて、スクラムマスターの最終ゴールとは、自分が不要になることだとも言われています。チームと一緒に働きながら、スクラムマスターとしての全力でチームを支援し、そうした支援のやり方やテクニック、思想までチームが吸収してくれれば、名誉ある用済みになれる。スクラムマスターとして、自分の価値や効用を提供できれば、喜んでもらえるし満足度もありますが、それにチームを縛ってはならないと思います。

私が気をつけているのは「手を出さない」「口を出さない」ことです。我慢も辛抱もいるし、非難されることもあるし、逆効果のこともあります。それでも、みんな自分で考えて発言して行動できるし、私が関与するよりもきっと良い結果を出せるはず、失敗するかもしれないけどそこから学ぶことだってできる、そう信じています。経験は場で起きるかもしれないけれど、学びと成長は場を超えていきます。ファシリテーターって、促進する人だとすると、長い時間をかけた成長を促進するのもやはりファシリテータだと思いたい。まあ、「参加者に任せれば最高の結果が出るはず」よりは「私なんか大して役に立たないし」と思うほうが、簡単だしきっと現実に近いんですけれども。

そう思えば、大して役に立たないけどちょっとは役に立つことをしながら、みんなが失敗しながら学んでいく速度を最大化する、やがては役立たずの烙印を押されるその日に向かっていくこともできるのかな。

最後になりましたが、ファシリテーター Advent Calendar 2015 - Adventarには id:DiscoveryCoach ことがおりゅうさんに誘っていただきました。場違いなエントリかもしれませんが、読んでいただいてありがとうございます。

Scrum Master as a facilitator

This entry is a part of Facilitator's Advent Calendar.

In a software development process called Scrum, there is a Scrum Master. Scrum emphasizes trust between team members and though it, they create values in collaboration, search for the best possible outcome sometimes with a conflict, and aims for team's growth and success of the product. Scrum Master's job is to remove frictions in communications, point out obvious violations from Scrum's rule (there are very few rules), and commit to the team so that they can achieve the best performance in a given situation. Being a facilitator is not enough to become a Scrum Master, Scrum Masters often play facilitator roles.

In the published ScrumMaster pattern, Scrum Master has at least 8 functions.

  • CATALYST: In order to ensure that the high amount of collaboration needed for Scrum to succeed happens.
  • The ScrumMaster keeps the team adhering to their DEFINITION OF DONE by taking on the role of DONEMASTER.
  • SHEEPDOG: Continually encourage the team follow its processes; watch for and correct deviations.
  • The ScrumMaster can become the KNIGHT OF THE MIRRORS, reflecting the team's behavior back to them so that they understand their strengths and weaknesses.
  • COACH: Challenge the team to improve, and give advice on how to improve.
  • CHEERLEADER: give the team encouragement and positive feedback.
  • FIREWALL: protect the team from unnecessary external distractions.
  • The ScrumMaster may also help the PRODUCT OWNER become more effective, assuming the role of PRODUCT OWNER TRAINER.

Among the list, Catalyst and Knight Of The Mirror is the place where Scrum Master and facilitator cross. Scrum Master facilitates meetings (in Scrum, they are called events or ceremonies.) A team new to Scrum is not familiar with Scrum meetings, so an experienced Scrum Master usually guides through them. Scrum Master is responsible not only the agenda but also energizing discussions, exposing diverse opinions, and to keep time boxes.

At the same time Scrum Master facilitates the growth of the team. For example, usually daily standups are the first meetings which team receives the responsibility to run a meeting by themselves. At first Scrum Master guide the meetings and dictate what to talk about. Once team understands why and how to run the meeting, Scrum Master stands back to let team do by themselves. Sooner or later, team will run other meetings also. Scrum Master might help facilitating, or might leave the team do it 100%.

Outside meetings, there are live communications and discussions everywhere in and around any Scrum teams. Scrum Master watch them carefully and grasps the direction teams are going, notices strong and weak points, or finds potential problems. While Scrum Master facilitates and helps the communications, it's also Scrum Master's job to let the knowledge transparent to the team so that the team understand their behavior clearly. When team have a problem which they cannot solve by themselves, Scrum Master helps.

On the other hand, the team is getting better and better at those communications with suggestions from Scrum Master. The relationship between the team and stakeholders get stronger. That means Scrum Master will have fewer and fewer things to point out, though the observation continues. Problems (or impediments) will be shifted from inner-team to outer-team.

These are only a few examples to show that Scrum Master as facilitator has two roles; one is to facilitate a Ba, the other is to let the team absorb the first role. Meetings, communications, problem solving, they are all managed most effectively when managed by the team. While it is satisfying to be pleased by providing values and effects from Scrum Master, Scrum Master should never bind the team with them, even with good will.

It is said that the ultimate goal of Scrum Master is that the Scrum Master is no longer needed. Scrum Master, working with the team, doing the best to support the team, becomes honorable useless when the team absorb Scrum Master's methods, techniques, and even ideologies. It won't happen soon, will be difficult even after enough time. Yet it must be something any Scrum Master should have in mind.

My way of doing so is, not to do, not to say. It requires patience. Sometimes get blamed. Perhaps it backfires. Still, I believe they can think and speak and act and achieve better without my interference. Even if they fail, I believe they will learn from it. A Ba can provide experience to the people. But learning and growth happen beyond a single Ba. Facilitators are said, well, they facilitate. Facilitating a long term growth is also part of the facilitator's scope, or so I think. Anyway it's easier to replace the idea of "They can attain the best when left themselves" with "I won't be much of help to them anyway!" And latter's more realistic.

With the idea in my mind, I provide not much of help, but sometimes a bit of help, so that the speed of learning from failures can be maximized. That is the way toward the uselessness, perchance.

Thank you for reading so far. Gaoryu-san, or id:DiscoveryCoach, invited me for this year's Facilitators Advent Calendar. I want to express my thanks for the invitations and the interesting chance to write about my view of facilitator.

Survey of TDD - experience and current usage

This is an interim report of a survey of TDD - experience and current usage. The survey asks only 3 questions.

  1. When you started to do TDD?
  2. How often do you use TDD now?
  3. Please tell us your reason about the answer for question 2.

Here is the summary from about 90 responses.

You can access more detailed summary here. Raw responses are also available.

Next chart shows an analysis across experience and current usage.

Orange part (seldom / occasionally) increase beyond 1 year (18%→24%→26%) as experience extends.

We picked up a few reasons from responses. (Translation from Japanese by the author.)

  • regularly but not much as before
    • changed how to use TDD. e.g. I write code non-TDD and when in doubt I try TDD.
    • I have to use many new languages and cost to examine each language's unit testing increased.
    • At first I used TDD as a method to learn and earn excellent skills. Currently I know pros and cons of TDD and only use TDD when it's good.
    • Doing TDD to the letters exhausts me. I gain maximum value with minimum effort.
  • seldom / occasionally
    • Same as "TDD is Dead" dictates. I write unit tests only for complicated models. Others are covered with end-to-end tests. For newbies I ask them to learn TDD as a basic skill.
    • I learned good designs TDD produces. Now I can do good design without TDD.
    • I write integration tests.
    • Doing everything TDD takes too much time to be on schedule.
    • Longevity of software has been shortened. No much gains in writing tests for code which rarely changes. I do TDD but not for everything.
    • I do TDD when it works and don't when not.
  • no usage now
    • Because I start implementation before deciding what I/O the class will have. I don't use xUnit until I/O are fixed.
    • Test-first costs too much because there are many spec changes after starting implementation.
    • Writing mocks are too cumbersome. It's impossible to foretell which methods in which module is called in what order.
    • People write code that just pass tests. Many bug ensues. So I stopped using TDD.


We categorized the reason for responses with more than 2 years experience AND less than "regularly" used (= regularly but not much, seldom / occasionally, none.)

reason 2-5 years > 5 years total
environment 6 (75%) 2 (25%) 8 (100%)
only when effective 4 (40%) 6 (60%) 10 (100%)
discarded 1 (50%) 1 (50%) 2 (100%)
unknown 6 (100%) 0 (0%) 6 (100%)
environment
products or working environment are unsuitable to TDD. I no longer code. etc.
only when effective
I choose to do TDD only when it's effective and I know how to choose.
discarded
I understand TDD but I found it not having significant value, so I don't do TDD
unknown
no answer, others.

TDDの経験と現状のアンケート

「TDD(テスト駆動開発)ってどのくらい使われてるんですか?」と聞かれることがあります。それはですね、俺だって知りたいわー!というわけで、「TDDの経験と現状について」というアンケートを作りました。

10/23の段階で83件の回答がありました。ありがとうございます。TDD人気ありますね。中間報告として、これまでの回答を公開したいと思います。始めた時期と現在の状況のグラフです。

回答全体のサマリはこちらで見られます(回答したときに見られるのと同じです)。なお、こちらは随時更新されるので、本エントリの内容と一致しないかもしれません。
https://docs.google.com/forms/d/1pb29VBqO-kd10ks_x9oqvkMUy5rDW4nMoDnBPVM85yc/viewanalytics
※アンケートはまだまだ受付中です。こちらからどうぞ→ http://goo.gl/forms/fbWsZmH3El

分析してみる

2年以上やっている人が50%以上、最近始めた人は意外と少ない(1年以内が10%)という印象です。現状は、「よく使っている」は「以前ほどではない」も含めると約50%です。大雑把に言えば、長く使っている、使い続けている人が相当数いると言えそうです。(ただしアンケートに回答してくれた方が、業界全体(母集団)を偏りなく代表しているという根拠はないので、「いるね」という以上のことは言えませんけれど。)

今回このようなアンケートを採っているのは、TDD本当に使ってるの?意外と辞めちゃってたりしない?と思ったためです。TDDについて(特に今年は)議論がありますが、議論は議論として、現場はどうなのか。

もうひとつ、自分のことを振り返ると、以前ほどTDDしてないなあと思います。それは、コード書いてないから……ごめんなさいごめんなさい。でも書くときでも、やっぱり以前のような100%TDDはやってない。なぜだろうか。思いついた理由は2つです。

  1. 何でもかんでもTDDではなく、効果的に使うポイントがわかるようになった。
    • 例: Webアプリはエンドツーエンドでテストするよね(Selenium Driverとか)
    • 例: 静的データ構造はTDDあんまり意味ないよね
  2. TDDは実は学習のツールだった。今は学習することが減った(コーディングについて)。
    • 例: TDDのおかげでデザインパターンが使えるようになったが、今はTDDなくても使える
    • 例: SQLAlchemyってどう使うんじゃ。TDDスタイルで実験してみよう。

他の人はどうだろう?特に2番の理由ってあり得るんだろうか?この検証が、アンケートのもう一つの狙いです。

期間と使用度合いの相関は?

TDDを始めてからの期間と、現在の使用度合いの関係を調べてみました。こちらが結果のグラフです。

注目したいのはオレンジと赤の部分です。「たまにしか使わない」「以前ほどではない」が、2〜5年のところで増えています(1年以内はデータ数が8件と少ないのでとりあえず無視)。5年以上では「現在もよく使う」が増えていますが、オレンジの「たまにしか使わない」も増えています(グラフだと見難いが、18%→24%→26%)。TDDの経験年数が長いと、「たまにしか使わない」人が増える傾向があるようです(わりと乱暴ですけど)。

さらに、理由も簡単に見てみました。「実装しなくなった」「今の仕事ではTDD困難」など環境要因を除くと、TDDを使う・使わない理由が少し見えてきます。2年以上の回答から、代表的なものをいくつか紹介します。

  • 現在もよく使っているが、以前ほどではない
    • 自明である事とそうでないものの区分けができるようになった。
    • コーディングを進めていき、不安な箇所をTDDする等TDDとのつきあいかたが変わった。
    • 言語スイッチが増え、他言語のユニットテストを調べなければ使えず、コストが高くなった。
    • 最初は勉強や技能の向上を意図して、意識的にTDDを行うようにしていたため使用機会が多かった。現在はではTDDの得意な所、不得意な所がわかるようになり、使い所のみで使うようになっている。
    • 教科書通りにやると疲れるから、最低限のテストコードで、最大限の効果を出すようになっただけ
  • 現在はたまにしか使わない
    • 概ね"TDD is Dead"に書かれている通りです。Railsで開発(略)という事情もあるかもしれませんが、以前ほどUnitTestをTestFirstで書くといったことはなくなりました。今は、(略)UnitTestを書くのは仕様が複雑なModelの処理の場合のみ(略) ただ、教育的な意味合いで、初めはしっかりUnitTestを書いてください、と指導することはあるので、スキルの土台としてのTDDは必要という認識です。
    • TDDを用いたときの設計の勘所が自然と意識できるようになったこと
    • 統合テストを重視するようになり、そちらにテストを書くというコストをかけるようになったため
    • 全てをTDDのサイクルに乗せるとスケジュール的な無理が生じる場合が有る。
    • ソフトウェアの寿命が短くなって頻繁に変更しない部分にまでテストを書くメリットがなくなったので抜き気味にやっている。
    • TDDが有効な場ではTDDで開発しますし、そうでないなら別のアプローチを使います。適材適所。
  • 現在はまったく使わない
    • どんなクラスのI/Oにすれば良いか定まらないまま実装を始めてしまうため。区切りがよくなるまで(I/Oが定まるまで)xUnitは書いていない。
    • いざ製造を始めると設計不備による仕様変更が多発するため、テストを先に作っておくと手戻りが多すぎる。
    • モックの記述が非常に面倒。モジュール中のどのメソッドが何回目に呼ばれて何を返すとか、製造終了まで分かる訳がない。
    • テストが動けばいいやというコーディングスタイルが蔓延し、バグが多発した。

(なお回答の生データのリンクを末尾に載せています。ここにない理由含め、全回答がそのまま見られます。)

また、理由をカテゴリに分類して分布を調べてみました。

理由 2〜5年前 5年以上前
環境 6 (75%) 2 (25%) 8 (100%)
適所 4 (40%) 6 (60%) 10 (100%)
棄却 1 (50%) 1 (50%) 2 (100%)
不明 6 (100%) 0 (0%) 6 (100%)

対象は、現状が「以前ほどは使わない」「たまにしか使わない」「まったく使わない」の回答です。表のパーセント表記は、横方向に加えると100%になるような計算です。分類は以下の定義で、主観で判断しました。

環境
TDDができない製品、職場環境である。実装をしなくなった。など
適所
いつ、どういうときにTDDをするか判断する(結果TDDしないこともある)。
棄却
TDDを理解した上で、現状では役に立たないと判断している。
不明
それ以外、分類できない、回答なし

以上、中間報告でした。アンケート自体はまだまだ回答を受け付けています。データが増えればまたお知らせしたいと思います。

※アンケートはまだまだ受付中です。まだの方はぜひどうぞ!→ http://goo.gl/forms/fbWsZmH3El

またTDDの現状について情報やコメントがあれば、教えてください。

詳細なアンケート結果(閲覧のみ; 随時更新)
https://docs.google.com/spreadsheets/d/1jJX102o7i46WT1TF7TsQwcvAM4EHLNqOhlpBboTAikk/pubhtml?gid=1987591198&single=true
※今回の報告は、10/23までのぶんを対象に分析しました。

プラグマティックTDDとはなにか?

by JOSEPH YODER on JANUARY 11, 2012

by Joseph Yoder & Rebecca Wirfs-Brock

僕らがプラグマティックTDDという言葉を使うわけ

プラグマティック・テスト駆動開発(TDD)は、大局的な見方を大事にしながらチームのソフトウェア開発プラクティスにテスティングをどう使えば役に立つか考えるという、テスティングのアプローチです。
開発者はユニットテストをたくさん書くべしと言ったりはしないんです。そのかわり、より高い品質にたどりつけるよう、テスティングの戦略を見つけないといけません。
テスティングを自分の状況でどう利用するのがベストか、メンバーやソフトウェアの種類に応じて、決断する必要があるんですよ。

よくあるTDDは、開発者がユニットテストを多数書くことになっていますが、個々のユニットテストに価値があるかどうかはわかりません。僕たちのオススメはこうです。最高のレバレッジをきかせられるテスティング戦略を適用せよ。たとえば、ユニットテストたくさんよりも、ユーザーレベルの受入テストを適切に定義するほうが価値が高いことが多いですね。テスティングが開発を駆動するのです(ただしそれ以外のコーディングやデザインのプラクティスを捨てていいってわけでもないですよ)。いつでもどこでも使える万能のテスティング戦略なんてのはないんです。

TDDを組織導入しない理由でよくあるのが、次のような誤解です。いわく必ずテストを最初に書かなければいけない、プロダクションコードを1行も書く前にテストを書くのだ、テストとコードは小さなインクリメントで同時に書け、ってやつですね。僕たちは思うんですが、TDDってむしろ自分のソフトウェアと自分の要求をどう確認(validate)するのがベストかじっくり考えようという話なんです。テスティングと確認(validation)がセットになって、開発プロセスを駆動するべきなんですよ(なんで僕たちはテスト駆動が大好きです)。テスティングは、ユニットテストをいっぱい書くだけの話じゃありません。

テスティングの「リズム」の違いとは何か

僕たちはテスティングは大事なもので、日々のプログラミングの中の欠かせない部分だと思っています。だけど、コードを書くときは常に小さなサイクルでテストも一緒に育てるのがいいとも限りません。先にアウトライン的なテストのシナリオだけ書いて、そのテストシナリオをコーディングのガイドとするやり方のほうがうまくいくという人もいます。そこそこ実装ができて想定シナリオが動かせるようになったら、そこでテストを実際に実装し、順次バグをつぶしていきます。テスティングなしで何百行もコードを書くというのでさえなければ、こういうやりかたもアリだと僕らは思ってます(なんにせよ、どのやり方がいいという話ではありません)。

どんなテスティングプラクティスを考えるのか?

TDD上手になるのはたいへんです。テストスイートを自動化し、リファクタリングと書き直しでテストから重複を取り除き、例外ケースをテストしと、キリがありませんね。それに受入テスト、スモークテスト、結合、パフォーマンスや負荷テストもインクリメンタルな開発に役立つものです。こうしたテスティングが手に負えないなあと感じましたか?よし、それではプラクティカルになりましょう!テスティングはテスティングのためにあらず、です。あなたが書くテストは、あなたにレバレッジを与えるのです。コードを変えたり育てたりするのにより自信が持てるようになり、システムが要求に合っているか確認することもできます。だからこそ、何をテストし何をテストしないのか、どこまでテストするのか、それを知るのが大事なのです。

それでどこから始めればいいのか?

チームにテスティングの文化を作るのが重要です。同じ手法やツールをチームに広めましょう。いろいろなやり方を試してみて、グループとして得たものを確認しましょう(ふりかえりをするのがいいです)。新しいプロジェクトを始めるところなのであれば、既存コードがあるときとはアプローチががらっと変わるかもしれませんね。圧倒されないようにしてください。フォーカスしたいエリアを決めましょう。たとえば、新しい機能のユニットテストを書くのは簡単です。ですが既存コードに、バグが多いとか変更したいとかで、テストを書きたいときもあります。こちらのほうが難しいので、先に結合テストを自動化するのにフォーカスするほうがいいかもしれません(そすればビルドとデプロイのプロセスが整理できます)。とにかく、どこに投資したいかにかかっているわけです。始めはほどほどに、テスティングプラクティスを育てつつ、テスティングの手間と利点を測定しましょう。当たり前かもしれませんが、一番大切なのは実際に始めて、テスティングを開発プロセスの欠かせない一部にしていくことです。