科学研究コードの単体テストを書くことは価値がありますか?


89

自動化された一連の回帰テストを含む、完全なプログラムを検証するテスト(収束テストなど)を使用することの価値を強く確信しています。プログラミングの本を読んだ後、ユニットテスト(つまり、単一の関数の正確性を検証し、問題を解決するためにコード全体を実行するテストではない)を作成する必要があるというしつこい気持ちになりました。。ただし、単体テストは常に科学的コードに適合しているとは限ら、結果的に人為的または時間の無駄に感じます。

研究コードの単体テストを作成する必要がありますか?


2
これはちょっとした未解決の質問ですね。
キューバイト

2
すべての「ルール」と同様に、批判的思考の用量が常に適用されます。特定のルーチンが単体テストの明らかな方法を持っているかどうかを自問してください。そうでない場合は、その時点で単体テストが意味をなさないか、コードの設計が不十分でした。理想的には、1つのルーチンが他のルーチンから可能な限り1つのタスクを実行しますが、それは時々トレードオフする必要があります。
ラガーベア

stackoverflowに関する質問についても、同様の方法でいくつかの良い議論があります。
naught101

回答:


85

長年、私は自分のコードの単体テストを書くのに十分な時間がなかったという誤解を受けていました。私がテストを書いたとき、それらは肥大化した、重いものであり、それが必要だとわかったときだけユニットテストを書くべきだと思うように促しました。

それから私はテスト駆動開発を使い始め、それが完全な啓示であることがわかりました。ユニットテストを書かない時間はないと確信しています

私の経験では、テストを念頭に置いて開発することで、よりクリーンなインターフェース、より焦点を絞ったクラスとモジュール、そして一般的にテスト可能なコードがよりソリッドになります。

ユニットテストがないレガシーコードを操作し、手動で何かをテストする必要があるたびに、「このコードにすでにユニットテストがある場合、これは非常に高速になる」と考え続けます。結合テストの高いコードに単体テスト機能を追加しようとするたびに、「分離された方法で記述されていれば、これは非常に簡単になる」と考え続けます。

私がサポートする2つの実験ステーションの比較と対照。1つはしばらく前から存在し、多くのレガシーコードがありますが、もう1つは比較的新しいものです。

古いラボに機能を追加する場合、多くの場合、ラボに降りて、必要な機能の意味と、他の機能に影響を与えずにその機能を追加する方法を理解するために多くの時間を費やします。コードは単にオフラインテストを許可するように設定されていないため、ほとんどすべてをオンラインで開発する必要があります。オフラインで開発しようとした場合、合理的な数よりも多くのモックオブジェクトが作成されます。

新しいラボでは、通常、デスクでオフラインで開発し、すぐに必要なもののみをモックアウトし、その後ラボで短時間を費やして、拾わない残りの問題を解決することで、機能を追加できます-ライン。

わかりやすくするために、@ naught101が尋ねたので...

私はいくつかのアドホックデータ分析を使用して、実験制御およびデータ収集ソフトウェアに取り組んでいる傾向があるため、TDDと改訂制御の組み合わせは、基礎となる実験ハードウェアの変更と、長期にわたるデータ収集要件の変更の両方を文書化するのに役立ちます。

ただし、探索コードを開発する状況であっても、仮定を成文化することで大きな利点が得られ、それらの仮定が時間とともにどのように変化するかを見ることができます。


7
マーク、ここでどのようなコードについて話しているのですか?再利用可能なモデル?この理論的根拠は、探索データ分析コードのようなものに実際には当てはまらないことがわかります。あなたは本当に多くのことをジャンプする必要があり、多くの場合、他の場所でコードを再利用することを期待しません。
naught101 14

35

科学的なコードは、通常は問題の数学的構造のため、私が取り組んだビジネスコードよりも頻繁にインターロック機能の星座を持つ傾向があります。そのため、個々の機能の単体テストは非常に効果的ではないと思います。ただし、効果的な単体テストのクラスがあり、特定の機能を対象とするという点でプログラム全体のテストとはまったく異なると思います。

これらの種類のテストの意味を簡単に定義します。回帰テストでは、コードに変更が加えられたときに、既存の動作の変更(何らかの方法で検証済み)を探します。単体テストではコードを実行し、仕様に基づいて目的の出力が得られることを確認します。出力が有効であると判断しなければならなかったため、元の回帰テスト単体テストであったため、それらはそれほど違いはありません。

数値単体テストの私のお気に入りの例は、有限要素実装の収束率のテストです。確かに単純ではありませんが、PDEの既知の解決策を取り、メッシュサイズを小さくすることでいくつかの問題を実行し、エラーノルムを曲線に適合させます(は収束速度)。Pythonを使用してPETScのポアソン問題に対してこれを行います。回帰のように違いを探しているのではなく、特定の要素に指定された特にレート探しています。C h r r rhChrrr

PyLithに由来するユニットテストのもう2つの例は、ポイント位置(合成結果を生成しやすい単一の関数)と、メッシュ内のゼロボリューム凝集セルの作成です。コード内の機能。

保存と一貫性のテストを含む、この種のテストは多数あります。操作は回帰とそれほど違いはありません(テストを実行して標準に対して出力をチェックします)が、標準出力は以前の実行ではなく仕様から取得されます。


4
ウィキペディアによると、「コンポーネントテストとも呼ばれる単体テストは、通常は機能レベルで、コードの特定のセクションの機能を検証するテストを指します」と述べています。有限要素コードでの収束テストは、多くの機能を伴うため、明らかに単体テストにはなりません。
デビッドケッチャソン

それが私が投稿の一番上で、私がユニットテストの広い視野を持っていることを明らかにした理由であり、「通常」はまさにそれを意味します。
マットネプリー

私の質問は、単体テストのより広く受け入れられている定義という意味で意味がありました。質問でこれを完全に明示しました。
デビッドケッチャソン

私は答えを明確にしました
マットネプリー

後者の例は、私が意図したものに関連しています。
デビッドケッチャソン

28

コードコンプリート、第2版のテスト駆動開発について読んで以来、ユニットテストフレームワークを使用してきました開発戦略の一環として、作成したさまざまなテストは診断的であるため、デバッグに費やす時間を削減することで生産性が劇的に向上しました。副次的な利点として、私は自分の科学的結果にはるかに自信を持ち、自分の結果を守るために何度もユニットテストを使用しました。単体テストでエラーが発生した場合、通常はかなり迅速にその理由を突き止めることができます。アプリケーションがクラッシュし、すべてのユニットテストがパスした場合、コードカバレッジ分析を実行して、コードのどの部分が実行されていないかを確認し、デバッガーでコードをステップ実行してエラーの原因を特定します。次に、新しいテストを作成して、バグが修正されたままであることを確認します。

私が書いたテストの多くは、純粋な単体テストではありません。厳密に定義すると、単体テストは1つの機能の機能を実行することになっています。モックデータを使用して1つの関数を簡単にテストできる場合、それを行います。また、特定の関数の機能を実行するテストを作成するために必要なデータを簡単にモックできないため、統合テストでその関数を他の関数と一緒にテストします。統合テスト複数の関数の動作を一度にテストします。Mattが指摘しているように、科学コードは多くの場合、インターロック関数のコンステレーションですが、多くの場合、特定の関数が順番に呼び出され、中間テストで出力をテストするユニットテストを作成できます。たとえば、実稼働コードが5つの関数を順番に呼び出す場合、5つのテストを作成します。最初のテストでは、最初の関数のみが呼び出されます(したがって、単体テストです)。次に、2番目のテストは最初と2番目の関数を呼び出し、3番目のテストは最初の3つの関数を呼び出します。コード内のすべての機能について単体テストを作成できたとしても、プログラムのさまざまなモジュラー部分を組み合わせるとバグが発生する可能性があるため、とにかく統合テストを作成します。最後に、私が必要だと思うすべての単体テストと統合テストを書いた後、私は 結果を再現可能にしたいので、ケーススタディを単体テストでラップし、回帰テストに使用します。再現性がなく、異なる結果が得られる場合、その理由を知りたいです。回帰テストの失敗は実際の問題ではないかもしれませんが、新しい結果が少なくとも古い結果と同じくらい信頼できるかどうかを判断することを私に強制します。

また、単体テストに加えて、静的コード分析、メモリデバッガー、コンパイラの警告フラグを使用してコンパイルして、単純なエラーや未使用のコードをキャッチすることもできます。



統合テストを十分に検討しますか、それとも個別の単体テストを作成する必要があると思いますか?
siamii

可能な場合はどこでも、個別の単体テストを作成します。デバッグが簡単になり、分離されたコードが強制されます(これが目的です)。
ジェフオックスベリー

19

私の経験では、科学研究コードの複雑さが増すにつれて、プログラミングにおいて非常にモジュール式のアプローチをとる必要があります。これは、大規模で古いベースのコード(f77誰か?)にとっては痛いことですが、前進する必要があります。モジュールがコードの特定の側面を中心に構築されると(CFDアプリケーションの場合、境界条件または熱力学を考える)、ユニットテストは、新しい実装を検証し、問題とさらなるソフトウェア開発を分離するために非常に貴重です。

これらのユニットテストは、コード検証の 1レベル下(波動方程式の解析解を回復できますか?)およびコード検証の 2レベル下(乱流パイプフローの正しいピークRMS値を予測できます)で、プログラミングが保証されている必要があります(引数は正しく渡されていますか、ポインターは正しいものを指しているのですか?)および「数学」(このサブルーチンは摩擦係数を計算します。数値のセットを入力して解を手動で計算すると、ルーチンは同じになりますか結果?)は正しいです。基本的に、コンパイラーが発見できるレベル、つまり基本的な構文エラーの1つ上のレベルに進みます。

アプリケーションの少なくともいくつかの重要なモジュールには絶対にお勧めします。ただし、非常に面倒で時間がかかることを理解する必要があるため、無制限の人手がない限り、100%の複雑なコードにはお勧めしません。


単体テストする(およびしない)ピースを選択するための具体的な例や基準はありますか?
デビッドケッチャソン

@DavidKetcheson私の経験は、使用するアプリケーションと言語によって制限されます。そのため、主にF90の約20万行の汎用CFDコードでは、コードのいくつかの機能を実際に分離するために、ここ1〜2年にわたって試行してきました。モジュールを作成してコード全体で使用することではこれを達成できないため、これらのモジュールを真に比較し、実際にライブラリーにする必要があります。そのため、ごく少数のUSEステートメントと、残りのコードとのすべての接続のみが、ルーチン呼び出しを通じて行われます。もちろん、ライブラリの残りの部分と同様に、ユニットテストできるルーチン。
FrenchKheldar

@DavidKetcheson答えで言ったように、境界条件と熱力学はコードの2つの側面であり、実際にそれらを分離してユニットテストを行うことは理にかなっています。より一般的な方法では、小さなものから始めて、きれいにそれをしようとします。理想的には、これは2人の仕事です。インターフェイスを説明するルーチンとドキュメントを書く人もいれば、理想的にはソースコードを見ずにインターフェイスの説明だけで行う単体テストを書く人もいます。そうすることで、ルーチンの意図がテストされますが、これは簡単に整理できるものではないことがわかります。
FrenchKheldar

1
単体テストに加えて、他の種類のソフトウェアテスト(統合、システム)を含めないのはなぜですか?時間とコストに加えて、これは最も完全な技術的解決策ではないでしょうか?私の参照は1(Sec 3.4.2)と2(5ページ)です。つまり、従来のソフトウェアテストレベル3(「テストレベル」)でソースコードをテストすべきではありませんか?
-ximiki

14

科学コードの単体テストは、さまざまな理由で役立ちます。

特に3つは次のとおりです。

  • 単体テストは、他の人がコードの制約を理解するのに役立ちます。基本的に、単体テストはドキュメントの形式です。

  • 単体テストでは、コードの単一ユニットが正しい結果を返していることを確認し、詳細が変更されてもプログラムの動作が変わらないことを確認します。

  • 単体テストを使用すると、研究コードを簡単にモジュール化できます。これは、コードの並列化やGPGPUマシンでの実行に関心がある場合など、新しいプラットフォームをターゲットにしようとする場合に特に重要です。

何よりも、単体テストは、コードを使用して作成している研究結果が有効で検証可能であるという自信を与えます。

質問で回帰テストについて言及していることに注意してください。多くの場合、リグレッションテストは、ユニットテストや統合テストの自動化された定期的な実行によって達成されます(統合されたコードの一部をテストします。信頼されている以前のプログラムの結果)。大規模で複雑なコンポーネントのレベルですでに統合テストまたは単体テストを使用しているようです。

私が言うことは、研究コードがますます複雑になり、他の人のコードとライブラリに依存するようになると、エラーが発生したときにどこでエラーが発生するかを理解することが重要だということです。単体テストを使用すると、エラーをより簡単に特定できます。

科学的コンピューティングのベストプラクティスで共著した論文のセクション7「間違いの計画」の説明、証拠、および参考文献が役立つことがあります。また、防衛的プログラミングの補足概念も紹介しています。


9

私のdeal.IIのクラスでは、私は正常に動作(と私は意図的「と述べストレスに進んでいないのテストを持っていないソフトウェアを教えるではない」、正しく動作しない」ことが正しく動作しないが)。

もちろん、私はマントラを守っています-これが取引です。IIはすべてのコミットで2,500テストを実行するようになりました;-)

もっと真剣に言うと、Mattはすでに2つのクラスのテストを適切に定義していると思います。私たちは低レベルのものの単体テストを書いており、それは自然に高レベルのものの回帰テストに進んでいます。私たちのテストをどちらか一方に分ける明確な境界線を引くことができるとは思いません、確かに多くの人が出力を見て、それが大部分が合理的であると判断した行を踏みます(ユニットテスト?)精度の最後の部分を調べずに(回帰テスト?)。


ソフトウェアテストの従来のレベルに対して、なぜこの階層(低位の単位、高位の回帰)を提案するのですか?
-ximiki

@ximiki-私はそうするつもりはありません。テストは、リンクにリストされているすべてのカテゴリを含むスペクトル上に存在すると言っています。
ヴォルフガングバンガース

8

はいといいえ。確かに、変換ルーチン、文字列マッピング、基本的な物理学、数学など、生活を楽にするために使用する基本的なツールセットの基本的なルーチンのユニットテストを行います。実際には、ユニットとしてではなく機能テストとしてテストすることをお勧めします。また、レベルと用途が大きく変化するクラスやエンティティ(最適化の目的など)、または何らかの理由で内部の詳細が変更されるクラスとエンティティを、ユニットテストとストレスをかけます。最も典型的な例は、ディスクからマップされた巨大なマトリックスをラップするクラスです。


7

絶対に!

何、それはあなたにとって十分ではありませんか?

科学プログラミングでは、他のどの種類よりも、物理システムを一致させることに基づいて開発しています。テスト以外で行ったことがあるかどうか、どのようにしてわかりますか?コーディングを開始する前に、コードの使用方法を決定し、いくつかの例を実行してみてください。可能性のあるエッジケースをキャッチしてみてください。モジュール方式で実行します。たとえば、ニューラルネットワークの場合、単一のニューロンのテストセットと完全なニューラルネットワークのテストセットを作成できます。そうすれば、コードの記述を開始するときに、ネットワークでの作業を開始する前にニューロンが機能することを確認できます。このような段階で作業するということは、問題が発生した場合、テストするコードの最新の「段階」しかなく、以前の段階はすでにテスト済みであることを意味します。

さらに、テストを取得した後、別の言語でコードを書き換える必要がある場合(たとえば、CUDAに変換する場合)、または更新するだけの場合でも、既にテストケースがあり、それらを使用して作成できますプログラムの両方のバージョンが同じように機能することを確認してください。


+1:「このような段階で作業するということは、問題が発生した場合、テストするコードの最新の「段階」しかなく、以前の段階はすでにテスト済みであることを意味します。」
-ximiki

5

はい。

コードは単体テストなしで記述されるという考えは、嫌悪感です。コードが正しいことを証明してから、証明が正しい= Pであることを証明しない限り。


3
...そして、あなたはその証明が正しいという証明を証明します...そして今、それは深いウサギの穴です。
JM

2
亀はずっとダイクストラを誇りに思っています!
aterrel

2
一般的なケースを解決して、証明が正しいことを証明してください!亀のトーラス!
アイシン

5

私はこの質問に独断的にではなく、実用的にアプローチします。「関数Xで何が問題になる可能性があるか」という質問を自問してください。コードにいくつかの典型的なバグを導入すると、出力がどうなるかを想像してください:誤ったプリファクター、間違ったインデックス、...そして、その種のバグを検出する可能性が高い単体テストを書きます。与えられた関数について、関数自体のコードを繰り返さずにそのようなテストを書く方法がない場合は、しないでください-しかし、次のより高いレベルのテストについて考えてください。

科学コードの単体テスト(または実際には任意のテスト)のはるかに重要な問題は、浮動小数点演算の不確実性にどのように対処するかです。私の知る限り、まだ一般的な解決策はありません。


単体テストを使用して手動でテストするか自動でテストするかに関係なく、浮動小数点表現でまったく同じ問題が発生します。ACCU過負荷雑誌に掲載されているRichard Harrisの優れた一連の記事を強くお勧めします。
マークブース

「与えられた関数について、関数自体のコードを繰り返さずにそのようなテストを書く方法がないなら、しないでください」。詳しく説明してもらえますか?例は私にとってこれを明確にするでしょう。
-ximiki

5

Tangurenaには申し訳ありません。このあたりで、マントラは「テストされていないコードは壊れたコード」であり、それはボスから来たものです。単体テストを行うすべての正当な理由を繰り返すのではなく、いくつかの詳細を追加したいだけです。

  • メモリ使用量をテストする必要があります。メモリを割り当てるすべての関数は、そのメモリにデータを保存および取得する関数が正しいことを実行していることを確認してテストする必要があります。これはGPUの世界ではさらに重要です。
  • 前に簡単に触れましたが、エッジケースのテストは非常に重要です。これらのテストは、計算の結果をテストするのと同じ方法で考えてください。入力パラメーターまたはデータが許容範囲外にある場合、コードがエッジで動作し、正常に失敗することを確認します(ただし、シミュレーションでそれを定義します)。この種のテストの作成に関与する思考は、作業を鋭くするのに役立ち、ユニットテストを作成したがプロセスが役に立たない人をめったに見つけない理由の1つである可能性があります。
  • テストフレームワークを使用します(いいリンクを提供してくれたGeoffが述べたように)。BOOSTテストフレームワークをCMakeのCTestシステムと組み合わせて使用​​しましたが、単体テスト(および検証テストと回帰テスト)をすばやく作成する簡単な方法として推奨できます。

+1:「入力パラメーターまたはデータが許容範囲外になった場合、コードがエッジで動作し、正常に失敗することを確認します(ただし、シミュレーションでそれを定義します)。」
-ximiki

5

素粒子物理学の論文分析コードの3番目のバージョンを含む、いくつかの小規模(つまり、単一のプログラマー)コードで効果を上げるために、単体テストを使用しました。

最初の2つのバージョンは、独自の重みと相互接続の多重化によって崩壊しました。

他の研究者は、モジュール間の相互作用は科学的コーディングが破られる場所であることが多く、それについて正しいと書いています。しかし、診断するのが非常に簡単であるものを使用すると、各モジュールがあることを決定的に示すことができたときに問題にされ、行うことに意味されるものをやって。


3

化学ソルバー(複雑な地質領域用)の開発中に使用したわずかに異なるアプローチは、コピーアンドペーストスニペットによるユニットテストと呼ばれるものでした。

大規模な化学システムモデラーに組み込まれた元のコードのテストハーネスを構築することは、時間枠内では実現できませんでした。

しかし、さまざまな表現の単体テストとして、化学式の(ブーストスピリット)パーサーがどのように機能するかを示すスニペットのますます複雑なセットを作成することができました。

最後の最も複雑な単体テストは、システムで必要なコードに非常に近く、そのコードをモック可能に変更する必要はありませんでした。このようにして、ユニットテストされたコード全体にコピーすることができました。

これを単なる学習演習と真の回帰スイート以上のものにしているのは、2つの要因です。ユニットテストはメインソースに保持され、そのアプリケーションの他のテストの一部として実行されます。スピリットは2年後に変更されます)-コピーアンドペーストされたコードは実際のアプリケーションで最小限の変更しか行われていないため、ユニットテストを参照するコメントを追加して、同期を保つことができます。


2

より大きなコードベースの場合、高レベルのもののテスト(必ずしも単体テストではありません)が役立ちます。一部のより単純なアルゴリズムの単体テストは、ヘルパー関数がのsin代わりに使用しているため、コードが意味のないことを確認するのにも役立ちますcos

しかし、全体的な研究コードについては、テストを記述して維持することは非常に困難です。アルゴリズムは意味のある中間結果がないと大きくなる傾向があり、明らかなテストが行​​われ、結果が出るまでに実行に時間がかかることがよくあります。もちろん、良い結果が得られた参照の実行に対してテストすることもできますが、これは単体テストの意味では良いテストではありません。

多くの場合、結果は真の解の近似値です。単純な関数がイプシロンまで正確であればテストできますが、結果のメッシュが正しいかどうかを確認するのは非常に困難です。

そのような場合、自動化されたテストのコストと利益の比率が高すぎることがよくあります。より良いものをお勧めします:テストプログラムを作成します。たとえば、エッジサイズのヒストグラムやメッシュの角度、最大および最小の三角形の面積、それらの比率など、結果に関するデータを作成するために中規模のPythonスクリプトを作成しました。

私は、通常動作時の入力と出力メッシュを評価するためにそれを使用することができ、両方私はアルゴリズムを変更した後、健全性チェックを持っているためにそれを使用します。アルゴリズムを変更しても、新しい結果がより良いかどうかは必ずしもわかりません。なぜなら、どの近似が最良であるかという絶対的な尺度がないことが多いからです。しかし、そのようなメトリックを生成することにより、「新しいバリアントは最終的には角度比は向上しますが、収束率は低下します」などの優れた要因をいくつか知ることができます。

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