テストがテストするコードとインラインで書かれていない理由はありますか?


91

最近、Literate Programmingについて少し読んで、考えさせられました...よく書かれたテスト、特にBDDスタイルの仕様は、散文よりもコードが何をするかを説明するのにより良い仕事をすることができ、大きな利点があります自身の精度を検証します。

テストするコードとインラインで記述されたテストを見たことはありません。これは、言語が同じソースファイルに記述されたときにアプリケーションとテストコードを簡単に分離する傾向がないため(そして誰も簡単にしなかったため)、または人々がテストコードをアプリケーションコードから分離するより原則的な理由があるからですか?


33
doctestを使用したpythonなどの一部のプログラミング言語では、これを行うことができます。
サイモンベルゴ

2
BDDスタイルの仕様は、コードを説明する散文よりも優れていると感じるかもしれませんが、それは2つの組み合わせが優れていないという意味ではありません。
ジェフ

5
ここでの引数の半分は、インラインドキュメントにも適用されます。
CodesInChaos

3
@Simon doctestは、主にテスト用に設計されていないため、深刻なテストには単純すぎます。それらは、自動的に検証できるコード例がドキュメントに含まれていることを目的としており、優れています。現在、一部の人々はユニットテストにもそれらを使用していますが、最近(過去数年間のように)、壊れやすい混乱、過度に冗長な「文書化」、および他の混乱で終わる傾向があるため、これには多くの難易度がかかりました。

7
Design by Contractでは、テストを簡単にするインライン仕様が可能です。
-Fuhrmanator

回答:


89

インラインテストで考えられる唯一の利点は、書き込むファイルの数を減らすことです。最近のIDEでは、これはそれほど大したことではありません。

ただし、インラインテストには多くの明らかな欠点があります。

  • それは懸念の分離に違反しています。これは議論の余地があるかもしれませんが、私にとって機能のテストは実装とは異なる責任です。
  • テスト/実装を区別するために新しい言語機能を導入するか、2つの間の境界があいまいになるリスクがあります。
  • ソースファイルが大きいと、作業が難しくなります。読みにくく、理解しにくくなり、ソース管理の競合に対処しなければならなくなる可能性が高くなります。
  • いわば「テスター」の帽子をかぶるのが難しくなると思います。実装の詳細を見ると、特定のテストの実装をスキップしたくなるでしょう。

9
それは面白い。私が見ることができる利点は、「コーダー」の帽子をかぶったとき、テストについて考えたいということですが、その逆は当てはまらないのが良い点です。
クリスデブルー

2
これらの線に沿って、1人がテストを作成し、もう1人が実際にコードを実装することができます(そしておそらく望ましい)。テストをインラインにすると、これはさらに難しくなります。
ジム・ナット

6
私ができればダウン投票します。どうにかこれはどのように答えですか?実装者はテストを書いていませんか?実装の詳細を見ると、テストをスキップしますか?「非常に難しい」大きなファイルでの競合?? そして、どのようにしてテストを実装の詳細と混同することができますか?
-bharal

5
@bharalまた、 "Just too hard"という言葉では、マゾヒズムは愚か者の美徳です。実際に解決しようとしている問題を除いて、すべてを簡単にしたいのです。
-deworde

3
単体テストはドキュメントと見なすことができます。それは、読みやすさを改善するために、コメントと同じ理由でユニットテストをコードに含める必要があることを示唆しています。ただし、それに関する問題は、多くの単体テストと、予想される結果を指定しないテスト実装のオーバーヘッドが多くなる傾向があることです。コード内のコメントも簡潔に保つ必要があり、より大きな説明は邪魔にならないようにします。関数の外部のコメントブロック、別のファイル、またはデザインドキュメントに移動します。単体テストは、コメントのようにテストされたコードを保持するのに十分に短い場合、IMOになることはめったにありません。
Steve314

36

私はいくつかを考えることができます:

  • 読みやすさ。「実際の」コードとテストを散在させると、実際のコードを読みにくくなります。

  • コードの肥大化。「実際の」コードとテストコードを同じファイル/クラス/より大きなコンパイル済みファイルなどにつながる可能性のあるものに混在させる。これは、レイトバインディングを持つ言語にとって特に重要です。

  • 顧客/クライアントにテストコードを見せたくない場合があります。(私はこの理由が好きではありません...しかし、もしあなたがクローズドソースプロジェクトに取り組んでいるなら、テストコードはとにかく顧客を助けそうにないでしょう。)

現在、これらの問題のそれぞれに可能な回避策があります。しかし、IMO、そもそもそこに行かない方が簡単です。


初期の頃は、Javaプログラマーがこの種のことをしていたことを観察する価値があります。たとえば、main(...)テストを容易にするためにクラスにメソッドを含める。この考えはほぼ完全に消えました。ある種のテストフレームワークを使用してテストを個別に実装することは業界の慣例です。

また、Knuthが考案したLiterate Programmingがソフトウェアエンジニアリング業界で決して流行りませんでした。


4
+1可読性の問題-特にOO設計では、テストコードは実装コードに比例して大きくなる可能性があります。
-Fuhrmanator

2
テストフレームワークを使用して指摘するための+1。実動コードと同時に優れたテストフレームワークを使用することは想像できません。
joshin4colours

1
RE:顧客/クライアントにテストコードを見せたくない場合があります。(私はこの理由が好きではありません...しかし、あなたがクローズドソースプロジェクトで作業している場合、テストコードはとにかく顧客を助けることはまずありません。) -クライアントマシン上でテストを実行することが望ましいかもしれません。テストを実行すると、問題が何であるかを迅速に識別し、クライアント
環境の

1
@sixtyfootersdude-それはかなり珍しい状況です。そして、あなたがクローズドソースを開発していると仮定すると、万が一のためにあなたのテストを標準のバイナリディストリビューションに含めたくないでしょう。(顧客に実行させたいテストを含む個別のバンドルを作成します。)
スティーブンC

1
1)3つの実際の理由を示した私の答えの最初の部分を見逃しましたか?そこには「批判的思考」が含まれていました... 2)Javaプログラマーがこれを行っていたと言った2番目の部分を見逃しましたか?そして、プログラマーがこれをやめたという明白な意味...正当な理由で?
スティーブンC

14

実際、契約による設計はこれを行うと考えることができます。問題は、ほとんどのプログラミング言語では次のようなコードを記述できないことです:(手作業で前提条件をテストするのは非常に簡単ですが、コードを記述する方法を変更せずに後条件をテストするのは非常に困難です(巨大なネガティブIMO)。

Michael Feathersがこれについてプレゼンテーションをしています。これは、コードの品質を改善できると彼が言っている多くの方法の1つです。


13

コード内のクラス間の密結合を回避しようとする多くの同じ理由から、テストとコード間の不要な結合を回避することもお勧めします。

作成:テストとコードは、異なる人によって異なる時間に作成される場合があります。

制御:テストを使用して要件を指定する場合、実際のコードよりもいつ誰がそれらを変更できるかについてのさまざまなルールの対象にする必要があります。

再利用性:テストをインラインに配置すると、別のコードでテストを使用できなくなります。

あなたが正しく仕事をするコードの塊を手に入れたが、パフォーマンス、保守性、その他の面で望まれる多くのものを残していると想像してください。そのコードを新しく改善されたコードに置き換えることにしました。同じテストセットを使用すると、新しいコードが古いコードと同じ結果を生成することを確認できます。

選択可能性:テストをコードから分離しておくと、実行するテストを選択しやすくなります。

たとえば、現在作業中のコードにのみ関連するテストの小さなスイートと、プロジェクト全体をテストする大きなスイートがあるとします。


私はあなたの理由に戸惑っています:TDDは既にテストの作成が生産コードの前に(または同時に)行われ、同じコーダーによって行われなければならないと言います!また、テストは要件に非常に似ていることを示唆しています。もちろん、TDDドグマを購読しない場合、これらの異議は当てはまりません(これは受け入れられますが、明確にする必要があります!)。また、「再利用可能な」テストとは正確には何ですか?定義により、テストはテストするコードに固有ではありませんか?
アンドレスF。13年

1
@AndresF。いいえ、テストはテストするコードに固有ではありません。それらは、テストする動作に固有です。したがって、Widgetが正しく動作していることを確認する一連のテストを備えたWidgetモジュールを持っているとしましょう。同僚がBetterWidgetを思い付きます。BetterWidgetは、Widgetと同じことを3倍高速にすることを目的としています。Literate Programmingがドキュメントをソースコードに埋め込むのと同じ方法でWidgetのテストがWidgetのソースコードに埋め込まれている場合、それらのテストをBetterWidgetにあまりうまく適用して、Widgetと同じ動作を確認することはできません。
カレブ

@AndresF。TDDに従わないことを指定する必要はありません。宇宙のデフォルトではありません。再利用ポイントについて。システムをテストするとき、内部ではなく入力と出力に注意します。その後、まったく同じように動作するが、異なる方法で実装される新しいシステムを作成する必要がある場合、古いシステムと新しいシステムの両方で実行できるテストがあると便利です。これは私に何度も起こりました。古いシステムがまだ稼働している間に新しいシステムで作業したり、それらを並べて実行したりする必要がある場合があります。Facebookが「React Fiber」をテストする方法と、Reactテストを使用してパリティを取得する方法を見てください。
user1852503

10

ここに私が考えることができるいくつかの追加の理由があります:

  • 別のライブラリにテストがあると、そのライブラリのみをテストフレームワークにリンクし、本番コードにリンクするのが簡単になります(これは一部のプリプロセッサでは回避できますが、テストを記述するのが簡単なソリューションの場合にそのようなものをビルドする理由別の場所)

  • 関数、クラス、ライブラリのテストは、通常、「ユーザー」の観点(その関数/クラス/ライブラリのユーザー)から作成されます。このような「コードを使用する」ことは通常、別のファイルまたはライブラリに記述され、その状況を模倣する場合、テストはより明確または「より現実的」になる場合があります。


5

テストがインラインの場合、顧客に製品を出荷するときにテストに必要なコードを削除する必要があります。そのため、テストを保存するための余分な場所は、必要なコードと顧客が必要とするコード分離するだけです


9
不可能ではありません。LPと同様に、追加の前処理フェーズが必要です。Cや、jsへのコンパイル言語で簡単に実行できます。
クリス

私にそれを指摘するための+1。私はそれを表すために答えを編集しました。
mhr

また、あらゆる場合にコードサイズが重要であるという仮定もあります。場合によっては重要だからといって、すべての場合で重要になるわけではありません。プログラマーがソースコードサイズを最適化するように動かされていない環境がたくさんあります。その場合、彼らはそれほど多くのクラスを作成しません。
zumalifeguard

5

このアイデアは、オブジェクトベースまたはオブジェクト指向のデザインのコンテキスト内での「Self_Test」メソッドに相当します。Adaのようなコンパイルされたオブジェクトベースの言語を使用している場合、すべてのセルフテストコードは、プロダクションコンパイル中にコンパイラによって未使用(呼び出されない)としてマークされるため、すべて最適化されます-いずれにも表示されません結果の実行可能ファイル。

「Self_Test」メソッドを使用することは非常に良い考えであり、プログラマーが本当に品質に関心があるなら、彼らは皆それをやっているでしょう。ただし、重要な問題の1つは、「Self_Test」メソッドは実装の詳細にアクセスできず、代わりにオブジェクトの仕様内で公開されている他のすべてのメソッドのみに依存しなければならないという点で、厳格な訓練が必要なことです。明らかに、自己テストが失敗した場合、実装を変更する必要があります。セルフテストは、オブジェクトのメソッドの公開されたすべてのプロパティを厳密にテストする必要がありますが、特定の実装の詳細に決して依存しないでください。

オブジェクトベース言語とオブジェクト指向言語は、テスト対象オブジェクトの外部のメソッドに関してその種の規律を頻繁に提供します(オブジェクトの仕様を強制し、実装の詳細へのアクセスを防ぎ、そのような試みが検出された場合はコンパイルエラーを発生させます) )。ただし、オブジェクト自体の内部メソッドには、すべての実装の詳細への完全なアクセス権が与えられます。そのため、セルフテストメソッドは固有の状況にあります。その性質上、内部メソッドである必要があります(セルフテストは明らかにテスト対象のオブジェクトのメソッドです)が、外部メソッドのすべてのコンパイラー規則を受け取る必要があります(オブジェクトの実装の詳細から独立している必要があります)。プログラミング言語がオブジェクトを統制する機能を提供している場合はほとんどありません」■外部メソッドであるかのように内部メソッド。したがって、これはプログラミング言語の重要な設計上の問題です。

適切なプログラミング言語のサポートがない場合、最適な方法は、コンパニオンオブジェクトを作成することです。つまり、コーディングするすべてのオブジェクト(「Big_Object」と呼びます)に対して、2番目のコンパニオンオブジェクトも作成します。その名前は、「実際の」オブジェクト(この場合は「Big_Object_Self_Test」)の名前と連結した標準接尾辞で構成されます")、およびその仕様が単一のメソッドで構成されている(" Big_Object_Self_Test.Self_Test(This_Big_Object:Big_Object)return Boolean; ")。コンパニオンオブジェクトはメインオブジェクトの仕様に依存し、コンパイラはコンパニオンオブジェクトの実装に対してその仕様のすべての規則を完全に実施します。


4

これは、リリースビルドからテストコードを削除することが不可能であるため困難であるため、インラインテストが行​​われないことを示唆する多数のコメントへの応答です。これは真実ではありません。ほぼすべてのコンパイラとアセンブラは、C、C ++、C#などのコンパイルされた言語でこれを既にサポートしています。これは、いわゆるコンパイラディレクティブで行われます。

c#の場合(c ++でも、使用しているコンパイラに応じて構文が少し異なる可能性があります)、これがその方法です。

#define DEBUG //  = true if c++ code
#define TEST /* can also be defined in the make file for c++ or project file for c# and applies to all associated .cs/.cpp files */

//somewhere in your code
#if DEBUG
// debug only code
#elif TEST
// test only code
#endif

これはコンパイラディレクティブを使用するため、フラグが設定されていない場合、ビルドされる実行可能ファイルにはコードが存在しません。これは、複数のプラットフォーム/ハードウェア向けに「1回書き込み、2回コンパイル」プログラムを作成する方法でもあります。


2

Perlコードでインラインテストを使用します。インラインコードからテストファイルを生成するモジュールTest :: Inlineがあります。

私はテストを整理するのが特に得意ではありませんが、インライン化すると保守が容易になり、維持される可能性が高くなります。

提起されたいくつかの懸念への対応:

  • インラインテストはPODセクションで記述されているため、実際のコードの一部ではありません。これらはインタプリタによって無視されるため、コードが肥大化することはありません。
  • Vim折りたたみを使用して、テストセクションを非表示にします。あなたが見る唯一のものは、のようにテストされている各メソッドの上の単一行+-- 33 lines: #test----です。テストを使用する場合は、テストを展開するだけです。
  • Test :: Inlineモジュールは、テストを通常のTAP互換ファイルに「コンパイル」するため、従来のテストと共存できます。

参考のため:


1

Erlang 2は実際にインラインテストをサポートしています。使用されていないコード内のブール式(たとえば、変数に割り当てられた、または渡された)は、自動的にテストとして扱われ、コンパイラによって評価されます。式が偽の場合、コードはコンパイルされません。


1

テストを分離するもう1つの理由は、実際の実装よりもテスト用に追加のライブラリや異なるライブラリを使用することが多いことです。テストと実装を混在させると、実装でのテストライブラリの偶発的な使用をコンパイラがキャッチできなくなります。

また、テストは、テストする実装部分よりもコード行が多い傾向があるため、すべてのテスト間で実装を見つけるのに苦労します。:-)


0

これは真実ではありません。生産コードが特に生産ルーチンが純粋である場合、生産コードと一緒にユニットテストを配置することをお勧めします。

たとえば.NETで開発している場合、テストコードを製品アセンブリに配置し、Scalpelを使用してそれらを削除してから出荷することができます。

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