有用性に基づいた単体テストの種類


13

価値の観点から、私は実際にユニットテストの2つのグループを見ています:

  1. 自明でないロジックをテストするテスト。それらを(実装前または実装後のいずれかで)記述すると、いくつかの問題/潜在的なバグが明らかになり、将来ロジックが変更された場合に備えて自信を持てるようになります。
  2. 非常に簡単なロジックをテストするテスト。これらのテストは、テストよりもドキュメントコード(通常はモックを使用)に似ています。これらのテストのメンテナンスワークフローは、「一部のロジックが変更され、テストが赤になりました-このテストを作成した神に感謝します」ではなく、「一部の些細なコードが変更され、テストが偽陰性になりました-利益を得ることなくテストを維持(書き換え)する必要があります」 。ほとんどの場合、これらのテストは維持する価値がありません(宗教上の理由を除く)。そして、多くのシステムでの私の経験によると、これらのテストはすべてのテストの80%に相当します。

私は他の人が値によるユニットテストの分離のトピックでどう思うか、それが私の分離にどのように対応するかを見つけようとしています。しかし、私が主に目にしているのは、フルタイムのTDDプロパガンダか、テストは役に立たず、ただ書くだけのコードプロパガンダです。途中で何かに興味があります。あなた自身の考えや記事/論文/本への言及を歓迎します。


3
既知の(特定の)バグ(元のユニットテストセットをすり抜けた)をチェックする単体テストを、回帰バグを防ぐ役割を持つ別のグループとして保持します。
コンラッドモラウスキ14

6
これらの2番目の種類のテストは、一種の「摩擦の変化」と見なすものです。それらの有用性を軽視しないでください。コードの些細なことさえ変更すると、コードベース全体に波及効果が生じる傾向があり、この種の摩擦を導入することは開発者にとって障害となり、開発者は気まぐれや個人的な好みに基づいて変更するのではなく、本当に必要なものだけを変更します。
テラスティン

3
@Telastyn-あなたのコメントについてのすべては、私にとって全く怒っているようです。誰がコードを変更することを意図的に難しくするでしょうか?開発者が適切と思われるコードを変更することを思いとどまらせるのはなぜですか-あなたは彼らを信頼していませんか?彼らは悪い開発者ですか?
ベンジャミンホジソン14

2
いずれにせよ、コードを変更すると「リップル効果」が発生する傾向がある場合は、コードに設計上の問題あります。この場合、開発者は合理的な範囲でリファクタリングを行う必要があります。脆弱なテストは、リファクタリングを積極的に妨げます(テストは失敗します;そのテストが実際には何もしないテストの80%の1つであるかどうかをわざわざ調べることができるのは誰ですか?しかし、あなたはこれを望ましい特性として見ているようです...私はそれをまったく得ていません。
ベンジャミンホジソン14

2
とにかく、OP はRailsの作成者からのこのブログ投稿が面白いと思うかもしれません。彼の主張を大幅に単純化するには、おそらくテストの80%を捨てようとする必要があります。
ベンジャミンホジソン14

回答:


14

ユニットテスト内で格差が生じるのは自然なことだと思います。それを適切に行う方法については多くの意見があり、当然他の意見はすべて本質的に間違っています。最近、DrDobbsに関する記事が非常に多く、この問題を詳しく調べており、回答の最後にリンクしています。

テストで最初に目にする問題は、テストを間違えやすいことです。私の大学のC ++クラスでは、1学期と2学期の両方で単体テストにさらされました。どちらの学期でも、プログラミング全般については何も知りませんでした。C++を介したプログラミングの基礎を学ぼうとしていました。ここで、生徒たちに「ああ、ちょっと年に一度の税計算機を書いた!今度はユニットテストを書いて、正しく動作することを確認する」と言うことを想像してください。結果は明白なはずです-それらはすべて私の試みを含めて恐ろしいものでした。

単体テストを書くのが面倒で、さらに良くなりたいと認めると、すぐに流行のテストスタイルまたはさまざまな方法論に直面します。方法論をテストすることにより、テストファーストやDrDobbsのAndrew Binstockが行うことなどのプラクティスを参照します。これは、コードと一緒にテストを記述します。どちらにも長所と短所があり、それが炎の戦争を引き起こすので、私は主観的な詳細に入ることを拒否します。どのプログラミング方法論が優れているかについて混乱していない場合は、おそらくテストのスタイルがトリックを行うでしょう。TDD、BDD、プロパティベースのテストを使用する必要がありますか?JUnitには、TDDとプロパティベースのテストの境界を曖昧にする理論と呼ばれる高度な概念があります。いつ使用しますか?

tl; drテストを間違えるのは簡単です。信じられないほどの意見があり、適切なコンテキスト内で熱心かつ専門的に使用されている限り、どのテスト方法も本質的に優れているとは思いません。私の考えでは、アサーションまたはサニティテストの拡張機能であり、これにより、開発へのフェイルファーストのアドホックアプローチが確実になりました。

主観的な意見として、より良いフレーズがないため、テストの「フェーズ」を書くことを好みます。必要に応じてモックを使用して、クラスを分離してテストする単体テストを作成します。これらは、おそらくJUnitまたは同様のもので実行されます。次に、統合テストまたは受け入れテストを作成します。これらは個別に実行され、通常は1日に数回しか実行されません。これらは重要なユースケースです。JUnitが簡単に提供できない自然言語で機能を表現するのが良いので、私は通常BDDを使用します。

最後に、リソース。これらは、異なる言語および異なるフレームワークでのユニットテストを中心とした矛盾する意見を提示します。彼らはイデオロギーと方法論の格差を提示する必要がありますが、私はあなたの意見をすでにあまり操作していない限り、あなた自身の意見を立てることができます:)

[1] Andrew BinstockによるアジャイルのCor落

[2] 前の記事の回答に対する回答

[3] ボブおじさんによるアジャイルの破損への対応

[4] Rob Myersによるアジャイルの破損への対応

[5] なぜキュウリのテストに悩まされるのですか?

[6] 間違っている

[7] ツールからの一歩

[8] 「解説付きローマ数字カタ」に関する解説

[9] 解説付きローマ数字カタ


1
私の友好的な主張の1つは、年次税計算機の機能をテストするテストを作成している場合、ユニットテストを作成していないということです。それは統合テストです。計算機はかなり単純な実行単位に分割する必要があり、ユニットテストはそれらのユニットをテストします。これらのユニットの1つが正常に機能しなくなった場合(テストが失敗し始めた場合)、基礎壁の一部をノックアウトするようなものであり、コードを修正する必要があります(一般的にテストではありません)。それか、不要になったコードのうち、破棄する必要があるものを特定しました。
クレイグ14

1
@クレイグ:正確に!これは、適切なテストの書き方がわからないという意味です。大学生として、徴税人は、SOLIDの適切な理解なしに書かれた1つの大きなクラスでした。これは他の何よりも統合テストのようなものだとあなたは絶対に正しいですが、それは私たちにとって未知の用語でした。私たちは教授による「ユニット」テストにのみさらされました。
IAE

5

両方のタイプのテストを用意し、必要に応じて使用することが重要だと思います。

あなたが言ったように、2つの極端があります、そして、私は正直にどちらにも同様に同意しません。

重要なのは、単体テストでビジネスルールと要件をカバーする必要があるということです。システムが個人の年齢を追跡しなければならないという要件がある場合は、年齢が負でない整数であることを確認するための「簡単な」テストを作成します。システムに必要なデータのドメインをテストしています。些細なことですが、システムのパラメーターを強制しているため、価値があります

同様に、より複雑なテストでは、価値をもたらす必要があります。もちろん、要件ではないがどこか象牙の塔で実施する必要があるものを検証するテストを書くことができますが、それは顧客があなたに支払っている要件を検証するテストを書くのに費やす時間です。たとえば、ネットワークではなくローカルファイルからのストリームのみである場合、コードがタイムアウトする入力ストリームを処理できることを検証するテストを記述するのはなぜですか?

私はユニットテストを固く信じており、それが理にかなっているところならどこでもTDDを使用しています。ユニットテストは確かに、コードを変更するときの品質と「フェイルファースト」動作の向上という形で価値をもたらします。ただし、覚えておくべき古い80/20ルールもあります。ある時点で、テストを作成するときに利益が減少し、さらにテストを作成することで測定可能な価値がある場合でも、より生産的な作業に移行する必要があります。


システムが個人の年齢を追跡することを保証するテストを作成することは、単体テストではありません、IMO。それは統合テストです。単体テストでは、たとえば基準日と任意の単位(日、週など)のオフセットから年齢値を計算する汎用実行単位(「手順」)をテストします。私のポイントは、コードの一部がシステムの残りの部分に奇妙な送信依存関係を持たないことです。いくつかの入力値から年齢を計算するだけです。その場合、ユニットテストは正しい動作を確認できます。オフセットが負の年齢を生成する場合、例外をスローする可能性があります。
クレイグ14

私は計算に言及していませんでした。モデルにデータが格納されている場合、データが正しいドメインに属していることを検証できます。この場合、ドメインは非負の整数のセットです。計算はコントローラーで(MVCで)行われる必要があり、この例では年齢の計算は別のテストになります。

4

私の考えは次のとおりです。すべてのテストにはコストがかかります。

  • 初期の時間と労力:
    • 何をテストし、どのようにテストするかを考える
    • テストを実装し、想定されていることをテストしていることを確認します
  • 継続的なメンテナンス
    • コードが自然に進化するにつれて、テストが実行すべきことをテストが実行していることを確認する
  • テストを実行する
    • 実行時間
    • 結果の分析

また、すべてのテストが利点を提供することを意図しています(私の経験では、ほとんどすべてのテストが利点を提供します)。

  • 仕様
  • ハイライトコーナーケース
  • 回帰を防ぐ
  • 自動検証
  • APIの使用例
  • 特定のプロパティ(時間、スペース)の定量化

そのため、多数のテストを記述した場合、それらにおそらく何らかの価値があることを確認するのは非常に簡単です。これが複雑になるのは、その値(ちなみに、事前にわからないかもしれません。コードを捨てると、回帰テストは値を失います)をコストと比較し始めるときです。

今、あなたの時間と労力は限られています。最小限の費用で最大限の利益をもたらすことを行うことを選択します。そして、私はそれを行うのは非常に難しいことだと思います。特に、持っていないか、入手するのに費用がかかるという知識が必要な場合があるからです。

そして、それがこれらの異なるアプローチ間の真の摩擦です。有益なテスト戦略をすべて特定したと思います。ただし、各戦略には一般に異なるコストと利点があります。また、各戦略のコストとメリットは、おそらくプロジェクト、ドメイン、およびチームの詳細に大きく依存します。つまり、複数のベストアンサーが存在する可能性があります。

場合によっては、テストなしでコードをポンプアウトすると、最高のメリット/コストが得られる場合があります。その他の場合は、徹底的なテストスイートの方が良い場合があります。さらに他のケースでは、設計を改善することが最善の策かもしれません。


2

であるユニットは本当に、テスト?そして、ここで実際にそのような大きな二分法がありますか?

私たちは、文字通りバッファの終わりを少し過ぎて読むとプログラムを完全にクラッシュさせたり、完全に不正確な結果を生じさせたり、最近の「HeartBleed」TLSバグによって証明されるように、おそらく安全なシステム全体を置く分野で働いています欠陥の直接的な証拠を作成せずに開きます。

これらのシステムからすべての複雑さを排除することは不可能です。しかし、私たちの仕事は、可能な限り、その複雑さを最小限に抑え、管理することです。

ユニットテストは、たとえば、予約が3つの異なるシステムに正常にポストされ、ログエントリが作成され、電子メールの確認が送信されることを確認するテストですか?

私はノーと言うつもりです。それは統合テストです。そして、それらは最も確実に自分の場所を持っていますが、彼らはまた別のトピックです。

統合テストは、「機能」全体の全体的な機能を確認するために機能します。しかし、その機能の背後にあるコードは、シンプルでテスト可能なビルディングブロック、つまり「ユニット」に分割する必要があります。

そのため、単体テストのスコープは非常に限られている必要があります。

これは、単体テストでテストされるコードのスコープが非常に限られていることを意味します。

さらに、優れたデザインの柱の1つは、複雑な問題を(可能な範囲で)小さく、単一の目的に分割し、互いに分離してテストできることを意味します。

最終的には信頼できる基礎コンポーネントで構成されたシステムが完成します。正確なことを伝えるために、シンプルで小さな限定的なスコープテストを記述したため、これらの基礎的なコードのいずれかが壊れるかどうかがわかります。

多くの場合、おそらくユニットごとに複数のテストが必要です。テスト自体は単純で、可能な範囲で唯一の動作をテストする必要があります。

自明ではない、精巧で複雑なロジックをテストする「ユニットテスト」の概念は、ちょっと矛盾したものだと思います。

そのような意図的な設計の内訳が行われた場合、テストされたコードユニットの基本機能が変更されない限り、ユニットテストは突然どのように誤検知を開始しますか?そして、それが起こった場合、プレイ中にいくつかの非自明な波及効果があると信じる方が良いでしょう。誤検知を引き起こしていると思われる壊れたテストは、実際に何らかの変更がコードベースの依存関係のより広い範囲を壊したことを警告しており、それを調べて修正する必要があります。

これらのユニットのいくつか(それらの多く)は、モックオブジェクトを使用してテストする必要があるかもしれませんが、それは、より複雑なテストや複雑なテストを記述する必要があるという意味ではありません。

予約システムの不自然な例に戻ると、コードを単体テストするたびにライブ予約データベースまたはサードパーティサービス(またはその「開発」インスタンス)にリクエストを送信することはできません。

したがって、同じインターフェイスコントラクトを提示するモックを使用します。その後、テストは、比較的小さな決定論的なコードチャンクの動作を検証できます。ボード全体に緑色が表示されると、基礎を構成するブロックが破損していないことがわかります。

ただし、個々の単体テストのロジック自体は可能な限りシンプルなままです。


1

これはもちろん私の意見ですが、過去数か月間fsharpで関数型プログラミングを学ぶ(C#のバックグラウンドから来た)ことで、いくつかのことに気付きました。

OPが述べたように、私たちが日々目にする「ユニットテスト」には通常2種類あります。メソッドのインとアウトをカバーするテスト。これは一般に最も価値がありますが、「アルゴリズム」ではなく「抽象化」についてのシステムの80%で実行するのは困難です。

もう1つのタイプは、抽象化の対話性をテストするもので、通常はモックを伴います。私の意見では、このテストはアプリケーションの設計のためにほとんど必要です。それらを省略すると、奇妙なバグやスパゲッティコードを危険にさらします。なぜなら、最初にテストを実行することを強いられない限り、人々は設計を適切に考えないからです(そして、通常はそれを台無しにします)。問題は、テスト方法論ではなく、システムの基礎となる設計です。命令型言語またはオブジェクト指向言語で構築されたほとんどのシステムは、「副作用」、つまり「これを行うが、何も言わない」に依存しています。副作用に依存する場合、ビジネス要件または操作は通常その一部であるため、副作用をテストする必要があります。

システムをより機能的な方法で設計し、副作用への依存関係の構築を避け、不変性による状態の変更/追跡を避けると、より多くのアクションを明確にテストする「インアンドアウト」テストにより重点を置くことができます。 、そしてあなたがそこに着く方法を減らします。不変性のようなものが同じ問題に対するはるかに単純な解決策の面であなたに与えるものに驚くでしょう、そして「副作用」にもはや依存しなくなったら、ほとんど追加費用なしで並列化や非同期プログラミングのようなことをすることができます。

Fsharpでコーディングを開始してから、モックフレームワークは何も必要としませんでした。IOCコンテナーへの依存関係を完全になくしました。私のテストは、ビジネスニーズと価値に基づいて行われ、命令型プログラミングで構成を実現するために通常必要とされる重い抽象化レイヤーでは行われません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.