例から始めましょう。
たとえば、DBスキーマに大きく依存するexport
というメソッドがあるとします。そして、「大きく依存する」ということは、特定のテーブルに新しい列を追加すると、頻繁に(非常に頻繁に)対応するexport
メソッドが変更されることを知っています(通常、新しいフィールドをエクスポートデータにも追加する必要があります) )。
プログラマーはexport
メソッドを変更するのを忘れることがよくあります。私の目標は、プログラマーに明示的にを強制することです。彼がexport
メソッドを見るのを忘れたか、エクスポートデータにフィールドを追加したくないかを判断します。そして、私はこの問題の設計ソリューションを探しています。
私には2つのアイデアがありますが、どちらにも欠点があります。
すべてのデータが明示的に読み取られるようにするスマートラッパーを作成できます。
このようなもの:
def export():
checker = AllReadChecker.new(table_row)
name = checker.get('name')
surname = checker.get('surname')
checker.ignore('age') # explicitly ignore the "age" field
result = [name, surname] # or whatever
checker.check_now() # check all is read
return result
したがって、checker
は、table_row
には、読み取られなかった別のフィールドが含まれています。しかし、これはすべて重く見え、(おそらく)パフォーマンスに影響します。
最後のテーブルスキーマを記憶し、テーブルが変更されるたびに失敗する単体テストを作成できます。その場合、プログラマーは「export
メソッドをチェックアウトすることを忘れないでください」のようなものを目にするでしょう。警告プログラマを非表示にするには、export
とmanually(別の問題)をチェックして、新しいフィールドを追加してテストを修正します(またはそうしません—問題です)。
他にもアイデアはいくつかありますが、実装するのが面倒すぎたり、理解するのが難しすぎます(プロジェクトがパズルになりたくありません)。
上記の問題は、私がときどき遭遇するより広範な種類の問題の一例にすぎません。一部のコードやインフラストラクチャをバインドしたいので、そのうちの1つを変更するとすぐにプログラマに別のコードをチェックするように警告します。通常、一般的なロジックの抽出や信頼性の高い単体テストの作成など、いくつかの単純なツールがありますが、私はもっと複雑なケースのためのツールを探しています。
単体テストのアイデアは正しい方向に進んでいますが、実装は間違っています。
export
がスキーマに関連していて、スキーマが変更されている場合、2つのケースが考えられます。
スキーマのわずかな変更による影響を受けなかったため、export
は依然として完全に機能しますが、
または壊れます。
どちらの場合も、この起こり得る回帰を追跡することがビルドの目標です。一連のテスト(統合テスト、システムテスト、機能テストなど)は、export
プロシージャがcurrentスキーマで機能することを確認します。前回のコミット以降変更されたかどうか。これらのテストに合格した場合は、すばらしいです。それらが失敗した場合、これは開発者が何かを見落とした可能性があることを示す兆候であり、どこを見ればよいかを明確に示しています。
なぜ実装が間違っているのですか?まあ、いくつかの理由で。
単体テストとは関係ありません...
...そして、実際には、それもテストではありません。
最悪の部分は、「テスト」を修正するには「テスト」を実際に変更する必要があるということです。つまり、export
とはまったく関係のない操作を実行します。
代わりに、export
プロシージャの実際のテストを行うことにより、開発者がexport
を修正することを確認します。
より一般的には、1つのクラスの変更が常にまたは通常は完全に異なるクラスの変更を必要とする状況に遭遇した場合、これは設計が間違っており、単一責任の原則に違反していることを示す良い兆候です。
クラスについて具体的に説明しますが、それは多かれ少なかれ他のエンティティにも適用されます。たとえば、データベーススキーマの変更は、たとえば多くのORMで使用されるコードジェネレーターを通じてコードに自動的に反映されるか、少なくとも簡単にローカライズする必要があります。Description
列をProduct
テーブルと私はORMまたはコードジェネレーターを使用していません。少なくともsingleをData.Product
DALのクラス。すべてのコードベースを検索して、たとえばプレゼンテーションレイヤーでProduct
クラスのいくつかのオカレンスを見つける必要はありません。
変更を1つの場所に合理的に制限できない場合(単に機能しない場合や、大量の開発が必要な場合など)、回帰。クラスA
を変更し、コードベースのどこかでクラスB
が機能しなくなった場合、それは回帰です。
テストを行うと、回帰のリスクが低下し、さらに重要なこととして、回帰の場所がわかります。これが知っている 1つの場所での変更がコードベースの完全に異なる部分で問題を引き起こす場合、このレベルで回帰が発生するとすぐにアラームを発生させる十分なテストがあることを確認してください。
すべての場合において、コメントのみに依存しないようにしてください。何かのようなもの:
// If you change the following line, make sure you also change the corresponding
// `measure` value in `Scaffolding.Builder`.
動作しません。開発者はほとんどの場合それを読まないだけでなく、関係する行から削除または遠く離れて終了することが多く、理解できなくなります。
あなたの変更が十分に指定されていないように思えます。郵便番号のない場所に住んでいて、住所テーブルに郵便番号の列がないとします。次に、郵便番号が導入されるか、郵便番号がある場所に住んでいる顧客との取引を開始し、この列をテーブルに追加する必要があります。
ワークアイテムが「郵便番号列をアドレステーブルに追加する」とだけ言っている場合、はい、エクスポートが壊れるか、少なくとも郵便番号をエクスポートしません。しかし、郵便番号を入力するために使用される入力画面はどうですか?すべての顧客とその住所をリストするレポート?この列を追加するときに変更する必要があるものはたくさんあります。これらのことを覚えておくという仕事は重要です。ランダムなコードアーティファクトを頼りにして、開発者を覚えておいてもらいましょう。
意味のある列(つまり、キャッシュされた合計または非正規化されたルックアップ、またはエクスポートまたはレポートまたは入力画面に属さないその他の値だけではない)を追加する決定が下された場合、作成された作業項目にはすべての変更が含まれている必要があります必要-列の追加、作成スクリプトの更新、テストの更新、エクスポートの更新、レポート、入力画面など。これらはすべて同じ人物に割り当てられる(またはピックアップされる)とは限りませんが、すべて完了する必要があります。
開発者は、いくつかの大きな変更を実装する一環として、列自体を追加することを選択する場合があります。たとえば、誰かがそれがどのように実装されているかを考えずに、入力画面とレポートに何かを追加するための作業項目を書いた可能性があります。これが頻繁に発生する場合は、work-item-adderが実装の詳細を知る必要があるか(すべての適切な作業項目を追加できるようにする必要があるか)、または開発者が作業項目-加算器は時々物事を省きます。後者の場合は、「スキーマを変更するだけでなく、他に何が影響するか考えて停止する」という文化が必要です。
多くの開発者がいて、これが2回以上発生した場合は、チームリーダーまたは他の上級者がスキーマの変更について警告を受けるチェックインアラートを設定します。その人は、スキーマ変更の結果に対処するために関連する作業項目を探し、作業項目が欠落している場合、それらを追加するだけでなく、計画から除外した人を教育することができます。
ほとんどの場合、エクスポートを作成するときに、対応するインポートも作成します。エクスポートされるデータ構造を完全に取り込む他のテストがあるので、次に、完全に取り込まれたオリジナルとエクスポートされてインポートされたコピーを比較する往復単体テストを作成できます。それらが同じ場合、エクスポート/インポートは完了です。それらが同じでない場合、単体テストは失敗し、エクスポートメカニズムを更新する必要があることがわかります。