TDD:私の人生を変えた開発方法論

午前7時15分 当社の技術サポートには仕事が殺到しています。 グッドモーニングアメリカは私たちのことを話しましたが、私たちのサイトを初めて訪れた多くの人がエラーに遭遇しました。



本当に急いでいます。 現時点では、リソースの訪問者を新しいユーザーに変える機会を失う前に、フィックスパックを展開します。 開発者の1人が何かを準備しました。 彼はこれが問題に対処するのに役立つと考えています。 まだ本番環境に移行していないプログラムの更新バージョンへのリンクを会社のチャットに配置し、全員にテストを依頼します。 うまくいく!



私たちの英雄的なエンジニアはスクリプトを実行してシステムを展開し、数分後にアップデートが戦います。 突然、テクニカルサポートの呼び出し回数が2倍になりました。 私たちの緊急の修正が何かを壊し、開発者はgitのせいをつかみ、この時点でエンジニアはシステムを以前の状態にロールバックしました。



画像



本日私たちが翻訳した資料の著者は、TDDのおかげでこれをすべて回避できたと考えています。



TDDを使用する理由



私はこのような状況に長い間ありませんでした。 そして、開発者が間違いを犯しなくなったわけではありません。 実際、長年にわたり、TDD方法論は私が率い、影響を与えたすべてのチームに適用されてきました。 もちろんエラーは依然として発生しますが、それ以降、ソフトウェアの更新頻度と更新中に解決する必要のあるタスクの数が指数関数的に増加したにもかかわらず、プロジェクトを「ノックダウン」できる問題の生産への浸透はほぼゼロに減少しました最初に何か話したことがあったとき。



誰かがTDDに連絡する必要がある理由を私に尋ねたとき、私は彼にこの話をします。 TDDに切り替えた最も重要な理由の1つは、この方法によりコードでのテストカバレッジが改善され、実稼働でのエラー40〜80%減少することです。 これは私がTDDで最も気に入っていることです。 これにより、開発者の肩から多くの問題が発生します。



さらに、TDDを使用すると、開発者はコードを変更する恐れをなくすことができます。



私が参加しているプロジェクトでは、自動ユニットテストと機能テストのセットがほぼ毎日コードの生産を妨げているため、これらのプロジェクトの作業が深刻に混乱する可能性があります。 たとえば、先週行われたライブラリの自動更新を10個見ており、TDDを使用せずにリリースする前に、それらが何かを台無しにするのではないかと心配しています。



これらの更新はすべてコードに自動的に統合されており、すでに実稼働で使用されています。 私はそれらのどれも手動でチェックしませんでしたし、それらがプロジェクトに悪影響を与える可能性があることをまったく心配しませんでした。 同時に、この例を与えるために、私は長く考える必要はありませんでした。 GitHubを開いて、最近の合併を調べて、私が話していることを確認しました。 以前は手動で解決されていたタスク(または、さらに悪いことに、無視された問題)は、自動化されたバックグラウンドプロセスです。 テストでコードを十分にカバーせずに同様のことを試みることはできますが、これを行うことはお勧めしません。



TDDとは何ですか?



TDDはテスト駆動開発の略です。 この方法論を適用して実装されるプロセスは非常に簡単です。









テストはエラーを検出し、テストは正常に完了し、リファクタリングが実行されます



TDDを使用するための基本原則は次のとおりです。



  1. いくつかの機能の実装コードを作成する前に、この将来の実装コードが機能するかどうかを確認できるテストを作成します。 次のステップに進む前に、テストが開始され、エラーがスローされると確信しました。 これにより、テストが偽陽性結果を生成しないことを確認できます。これは、テスト自体の一種のテストです。
  2. 機会の実装を作成し、テストに合格することを確認します。
  3. 必要に応じて、コードのリファクタリングを実行します。 リファクタリングは、システムが正常に動作しているか正しく動作していないかを開発者に示すことができるテストの存在下で、開発者の行動に自信を植え付けます。


TDDは、プログラムの開発に必要な時間をどのように節約できますか?



一見すると、テストの作成はプロジェクトコードの量の大幅な増加を意味し、開発者は多くの余分な時間がかかるように思われるかもしれません。 私の場合、最初はすべてがそれであり、原則として、テスト可能なコードを作成する方法と、すでに作成されたコードにテストを追加する方法を理解しようとしました。



TDDは特定の学習曲線によって特徴付けられ、初心者がこの曲線に沿って登る間、開発に必要な時間は15-35%増加する可能性があります。 多くの場合、これがまさに発生します。 しかし、TDDの使用を開始してから約2年後に、驚くべきことが起こり始めます。 つまり、たとえば、ユニットテストの予備的な記述から、TDDを使用しなかったときよりも早くプログラミングを始めました。



数年前、ビデオクリップのフラグメントを操作する機能をクライアントシステムに実装しました。 つまり、ユーザーが記録フラグメントの開始と終了を示し、それへのリンクを取得できるようにして、クリップ全体ではなく、クリップ内の特定の場所を参照できるようにすることがポイントでした。



私は働きませんでした プレイヤーはフラグメントの終わりに到達し、それをプレイし続けましたが、なぜそうなのか分かりませんでした。



私は、問題がイベントリスナーを不適切に接続していると考えました。 私のコードは次のようになりました:



video.addEventListener('timeupdate', () => {  if (video.currentTime >= clip.stopTime) {    video.pause();  } });
      
      





問題を見つけるプロセスは次のようになりました。変更、コンパイル、再起動、クリック、待機...このアクションのシーケンスは何度も繰り返されました。



プロジェクトに導入された各変更を確認するために費やすのに1分近くかかり、問題を解決するための信じられないほど多くのオプションを経験しました(ほとんどが2〜3回)。



timeupdate



キーワードを間違えたのでしょうか? APIを正しく使用する機能を理解しましたか? video.pause()



呼び出しはvideo.pause()



ますか? コードを変更し、 console.log()



追加し、ブラウザーに戻って、[



]ボタンをクリックし、選択したフラグメントの最後の位置をクリックしてから、クリップが完全に再生されるまで辛抱強く待ちました。 if



コンストラクト内にログインしても何も起こりませんでした。 問題の可能性についての手がかりのように見えました。 入力時に間違いを犯さないように、APIドキュメントからtimeupdate



という単語をコピーしました。 ページをもう一度更新し、もう一度クリックして、もう一度待ちます。 そして再び、プログラムは正しく動作することを拒否します。



最後に、 if



ブロックの外側にconsole.log()



配置しました。 「それは役に立たない」と私は思った。 結局、 if



は非常に単純だったので、間違ったつづり方はまったくわかりませんでした。 ただし、この場合のロギングは機能しました。 コーヒーを飲み込んだ。 「あれは一体何だ!?」

マーフィーのデバッグ法。 あなたがテストしたことのないプログラムの場所は、エラーを含むことはできないと固く信じていたので、完全に使い果たされた後にエラーを見つける場所になります。彼らは考え得るすべてをすでに試していること。


何が起こっているのかを理解するために、プログラムにブレークポイントを設定しました。 clip.stopTime



の意味をclip.stopTime



。 驚いたことに、それはundefined



でした。 なんで? 私は再びコードを見ました。 ユーザーがフラグメントの終了時間を選択すると、プログラムはフラグメントの終了マーカーを正しい場所に配置しますが、値clip.stopTime



設定しません。 「私は信じられないほどのばかです」と私は考えました、「私は人生の終わりまでコンピューターに入れてはいけません。」



私はこれと数年後のことを忘れませんでした。 そしてすべて-私が経験した感覚のおかげで、まだ間違いを見つけています。 あなたはおそらく私が話していることを知っています。 これがすべて起こった。 そして、おそらく、誰もがこのミームで自分自身を認識することができるでしょう。









これは私がプログラムするときの見方です



今日このプログラムを書いたら、次のように作業を開始します。



 describe('clipReducer/setClipStopTime', async assert => { const stopTime = 5; const clipState = {   startTime: 2,   stopTime: Infinity }; assert({   given: 'clip stop time',   should: 'set clip stop time in state',   actual: clipReducer(clipState, setClipStopTime(stopTime)),   expected: { ...clipState, stopTime } }); });
      
      





この行よりもはるかに多くのコードがあると感じています:



 clip.stopTime = video.currentTime
      
      





しかし、それが全体のポイントです。 このコードは仕様として機能します。 これは、ドキュメントであり、コードがこのドキュメントで要求されているとおりに機能することの証明です。 また、このドキュメントが存在するため、フラグメントの終了時間にマーカーを使用する順序を変更する場合、これらの変更中にクリップの終了時間で正しい操作に違反したかどうかを心配する必要はありません。



ちなみに、 ここでは 、先ほど見たものと同じ、単体テストを作成するのに役立つ資料です。



ポイントは、このコードを入力するのにかかる時間ではありません。 ポイントは、何か問題が発生した場合にデバッグに要する時間です。 コードが正しくない場合、テストは優れたエラーレポートを提供します。 問題がイベントハンドラーにないことはすぐにわかります。 状態の変更が実装されるのはclipReducer()



またはsetClipStopTime()



であることがわかります。 テストのおかげで、コードが実行する機能、実際に表示される内容、およびコードに期待される内容がわかります。 さらに重要なことは、同僚が同じ知識を持ち、コードを書いてから6か月後に新しい機能を導入することです。



新しいプロジェクトを開始するとき、最初に行うことの1つとして、特定のファイルが変更されるたびにユニットテストを自動的に実行するオブザーバースクリプトを設定します。 私はよく2つのモニターを使用してプログラムします。 それらの1つでは、開発者のコ​​ンソールが開いており、そのようなスクリプトの結果が表示され、もう1つでは、コードを記述した環境のインターフェースが表示されます。 コードを変更すると、通常3秒以内に、変更が機能しているかどうかがわかります。



私にとって、TDDは単なる保険ではありません。 これは、コードのステータスに関する情報をリアルタイムで絶えず迅速に受信する機能です。 合格したテストの形での即時報酬、または何か間違ったことをした場合のエラーの即時報告。



TDD方法論は、より良いコードの書き方をどのように教えてくれましたか?



認めるのは恥ずかしいことですが、私は1つの告白をしたいと思います。TDDと単体テストを学ぶ前に、アプリケーションを作成する方法がわかりませんでした。 自分がどのように雇われたかは想像できませんが、何百人もの開発者にインタビューした後、多くのプログラマーが同じような状況にあると自信を持って言えます。 TDD方法論は、ソフトウェアコンポーネント(モジュール、関数、オブジェクト、ユーザーインターフェイスコンポーネントなどを意味する)の効果的な分解と合成について知っているほぼすべてを教えてくれました。



これは、ユニットテストにより、プログラマがコンポーネントを互いに、およびI / Oサブシステムから分離してテストする必要があるためです。 モジュールにいくつかの入力データが提供されている場合、それは特定の既知の出力データを提供する必要があります。 そうでない場合、テストは失敗します。 存在する場合、テストは成功します。 ここでのポイントは、モジュールがアプリケーションの他の部分とは独立して機能することです。 状態のロジックをテストしている場合は、画面に何も表示したり、データベースに何かを保存したりせずにこれを行うことができます。 ユーザーインターフェイスの構成をテストする場合は、ブラウザーにページを読み込んだり、ネットワークリソースにアクセスしたりしなくても、テストできます。



とりわけ、TDD方法論は、ユーザーインターフェイスコンポーネントを開発する際にミニマリズムを追求すれば、人生がずっと楽になることを教えてくれました。 さらに、ビジネスロジックと副作用をユーザーインターフェイスから分離する必要があります。 実用的な観点から、これは、 ReactやAngularなどのコンポーネントベースのUIフレームワークを使用する場合、画面に何かを表示するプレゼンテーションコンポーネントと、互いに接続されていないコンテナコンポーネントを作成する価値があるかもしれないことを意味します混在しています。



特定のプロパティを受け取るプレゼンテーションコンポーネントは、常に同じ結果を生成します。 このようなコンポーネントは、単体テストを使用して簡単に検証できます。 これにより、コンポーネントがプロパティで正しく機能するかどうか、およびインターフェイスの形成で使用される特定の条件付きロジックが正しいかどうかを確認できます。 たとえば、リストを構成するコンポーネントは、リストが空の場合、リストに新しい要素を追加するための招待状以外を表示しないようにすることができます。



私はTDDをマスターするずっと前に責任を共有する原則を知っていましたが、異なるエンティティ間で責任を共有する方法を知りませんでした。



単体テストにより、mokasを使用して何かをテストできるようになりました。それから、mokingはコードに何か問題があるかもしれないという兆候であることがわかりました。 それは私を驚かせ、ソフトウェア構成に対する私のアプローチを完全に変えました。



すべてのソフトウェア開発は、大きな問題を簡単に解決できる多くの小さな問題に分割し、アプリケーションを形成するこれらの問題の解決策を作成するプロセスです。 単体テストのためのタキシングは、構成の原子単位が実際には原子ではないことを示しています。 テストによってコードカバレッジに影響を与えずにmokを削除する方法を研究することで、エンティティの強力な接続性の無数の隠れた理由を特定する方法を学ぶことができました。



これにより、開発者として専門的に成長することができました。 これにより、拡張、保守、スケーリングが簡単な、はるかに単純なコードを作成する方法を学びました。 これは、コード自体の複雑さ、およびクラウドインフラストラクチャなどの大規模な分散システムでの作業の組織に適用されます。



TDDはチームの時間をどのように節約しますか?



TDDは、そもそもテストによるコードカバレッジの改善につながると既に述べました。 この理由は、この将来のコードの正しい動作をチェックするテストを作成するまで、何らかの機能を実装するためのコードの作成を開始しないからです。 最初にテストを書きます。 次に、エラーで終了できるようにします。 次に、機会を実装するためのコードを記述します。 コードをテストし、エラーメッセージを受け取り、テストの正しい合格を達成し、リファクタリングを実行してこのプロセスを繰り返します。



このプロセスにより、「フェンス」を作成できます。これにより、ごくわずかなエラーのみが「ジャンプ」できます。 このエラー保護は、開発チーム全体に驚くべき効果をもたらします。 マージチームの恐怖を軽減します。



テストでの高いレベルのコードカバレッジにより、チームはコードベースの小さな変更でも手動で制御したいという欲求を取り除くことができます。 コードの変更は、ワークフローの自然な部分になります。



コードを変更することへの恐怖を取り除くことは、特定のマシンの不鮮明さに似ています。 これを行わないと、機械は潤滑されて再起動されるまで、最終的に停止します。



この恐れがなければ、プログラムに取り組むプロセスは以前よりずっと穏やかになります。 プルリクエストは最後まで遅延されません。 CI / CDシステムはテストを実行し、テストが失敗すると、プロジェクトコードに変更を加えるプロセスを停止します。 同時に、エラーメッセージとそれらが発生した正確な場所に関する情報は、気付かないことは非常に困難です。



これが全体のポイントです。



親愛なる読者! プロジェクトで作業するときにTDDを使用しますか?






All Articles