77

C で記述された大規模なマルチプラットフォーム アプリケーションがあります (C++ の量は少ないですが増加しています)。これは、大規模な C/C++ アプリケーションに期待される多くの機能を備えて、何年にもわたって進化してきました。

  • #ifdef地獄
  • テスト可能なコードの分離を困難にする大きなファイル
  • 複雑すぎて簡単にテストできない関数

このコードは組み込みデバイスを対象としているため、実際の対象で実行するには多くのオーバーヘッドがかかります。そのため、ローカル システム上で、短いサイクルでより多くの開発とテストを行いたいと考えています。ただし、「システムの .c ファイルにコピー/貼り付け、バグを修正し、コピー/貼り付けを戻す」という従来の戦略は避けたいと考えています。開発者がわざわざそれを行うのであれば、後で同じテストを再作成し、自動化された方法で実行できるようにしたいと考えています。

ここに問題があります。コードをリファクタリングしてよりモジュール化するには、コードをよりテストしやすくする必要があります。しかし、自動化された単体テストを導入するには、よりモジュール化する必要があります。

1 つの問題は、ファイルが非常に大きいため、適切な単体テストを作成するためにスタブ化する必要がある同じファイル内の関数を呼び出す関数がファイル内にある可能性があることです。私たちのコードがよりモジュール化されるにつれて、これはそれほど問題ではないように思えますが、それはまだ先の話です。

私たちが考えたことの 1 つは、「テスト可能であることがわかっている」ソース コードにコメントをタグ付けすることでした。次に、テスト可能なコードのソース ファイルをスキャンするスクリプトを作成し、それを別のファイルにコンパイルして、単体テストにリンクします。欠陥を修正し、機能を追加するにつれて、ゆっくりと単体テストを導入することができます。

ただし、このスキームを (必要なすべてのスタブ関数と共に) 維持するのが面倒になり、開発者が単体テストの維持をやめるという懸念があります。したがって、別のアプローチは、すべてのコードのスタブを自動的に生成するツールを使用し、それとファイルをリンクすることです。(これを実行できる唯一のツールは、高価な商用製品です) しかし、このアプローチでは、外部呼び出しのみをスタブ化できるため、開始する前にすべてのコードをよりモジュール化する必要があるようです。

個人的には、開発者には外部の依存関係について考えてもらい、独自のスタブをインテリジェントに作成してもらいたいと考えています。しかし、10,000 行にも及ぶ恐ろしく大きくなりすぎたファイルのすべての依存関係をスタブ化するには、これは非常に困難な作業になる可能性があります。すべての外部依存関係のスタブを維持する必要があることを開発者に納得させるのは難しいかもしれませんが、それは正しい方法ですか? (私が聞いたもう 1 つの議論は、サブシステムのメンテナーがサブシステムのスタブを維持する必要があるというものです。しかし、開発者に独自のスタブを作成することを「強制」することが、より良い単体テストにつながるのではないでしょうか?)

もちろん、これ#ifdefsは問題に別の次元全体を追加します。

いくつかの C/C++ ベースの単体テスト フレームワークを見てきましたが、適切に見える多くのオプションがあります。しかし、「単体テストのないコードの毛玉」から「単体テスト可能なコード」への移行を容易にするものは見つかりませんでした。

それで、これを経験した他の人への私の質問は次のとおりです。

  • 良い出発点は何ですか?私たちは正しい方向に進んでいますか、それとも明らかな何かが欠けていますか?
  • 移行に役立つツールは何ですか? (現在の予算はほぼ「ゼロ」なので、できれば無料/オープン ソース)

ビルド環境は Linux/UNIX ベースであるため、Windows 専用のツールは使用できないことに注意してください。

4

13 に答える 13

52

「単体テストのないコードのヘアボール」から「単体テスト可能なコード」への移行を容易にするものは何も見つかりませんでした。

何年にもわたって蓄積された技術的負債を修正するための大変な努力だけで、なんと悲しいことでしょう。奇跡的な解決策はありません。

簡単な移行はありません。あなたは大きくて複雑で深刻な問題を抱えています。

あなたは小さなステップでそれを解決することができるだけです。それぞれの小さなステップには、次のものが含まれます。

  1. 絶対に不可欠な個別のコードを選択してください。(がらくたで端をかじらないでください。)重要で、(どういうわけか)残りから切り出すことができるコンポーネントを選んでください。単一の関数が理想的ですが、それは関数の絡み合ったクラスターである場合もあれば、関数のファイル全体である場合もあります。テスト可能なコンポーネントに完全ではないものから始めてもかまいません。

  2. それが何をすべきかを理解してください。インターフェースがどうあるべきかを理解してください。これを行うには、ターゲットピースを実際に離散化するために、初期リファクタリングを行う必要がある場合があります。

  3. 「全体的な」統合テストを作成します。これは、今のところ、見つかったコードの個別の部分を多かれ少なかれテストします。重要なものを変更しようとする前に、これを通過させてください。

  4. コードを、現在のヘアボールよりも理にかなっている、きちんとしたテスト可能なユニットにリファクタリングします。全体的な統合テストとの下位互換性を(今のところ)維持する必要があります。

  5. 新しいユニットの単体テストを作成します。

  6. すべて合格したら、古いAPIを廃止し、変更によって破損するものを修正します。必要に応じて、元の統合テストをやり直します。古いAPIをテストします。新しいAPIをテストします。

繰り返します。

于 2009-04-14T17:19:27.337 に答える
25

Michael Feathers がこれに関するバイブルを書きました。

于 2009-04-14T17:24:40.133 に答える
9

レガシーコードとテストの導入に関する私の小さな経験は、「​​特性テスト」を作成することです。既知の入力を使用してテストの作成を開始してから、出力を取得します。これらのテストは、実際に何をしているのかわからないが、機能していることはわかっているメソッド/クラスに役立ちます。

ただし、単体テスト(特性テストでさえ)を作成することがほぼ不可能な場合があります。その場合、私は受け入れテスト(この場合はFitness )を通じて問題を攻撃します。

1つの機能をテストし、それをフィットネスでチェックするために必要なクラス全体を作成します。「特性テスト」に似ていますが、1レベル上です。

于 2009-04-14T17:35:08.423 に答える
7

ジョージが言ったように、レガシー コードを効果的に使用することは、この種のことのバイブルです。

ただし、チームの他のメンバーが同意する唯一の方法は、テストを機能させ続けることの個人的な利点を彼らが理解する場合です。

これを実現するには、できるだけ使いやすいテスト フレームワークが必要です。あなたのテストを例として取り上げて、他の開発者が独自のテストを書くように計画してください。単体テストの経験がない場合は、フレームワークの学習に時間を費やすことを期待しないでください。おそらく、単体テストを作成すると開発が遅くなると見なされるため、フレームワークを知らないことがテストをスキップする言い訳になります。

クルーズ コントロール、luntbuild、cdash などを使用して、継続的インテグレーションに時間を割いてください。コードが毎晩自動的にコンパイルされ、テストが実行される場合、開発者は、単体テストが qa の前にバグをキャッチすると、利点を理解し始めます。

奨励すべきことの 1 つは、コードの所有権を共有することです。開発者がコードを変更して他の人のテストを壊した場合、その人がテストを修正することを期待すべきではなく、テストが機能しない理由を調査し、自分で修正する必要があります。私の経験では、これは達成するのが最も難しいことの 1 つです。

ほとんどの開発者は何らかの形式の単体テストを作成しますが、ビルドにチェックインしたり統合したりしない、使い捨ての小さなコードを作成することもあります。これらをビルドに簡単に統合できるようにすると、開発者は賛同し始めます。

私のアプローチは、新しいものにテストを追加することであり、コードが変更されると、既存のコードを分離しすぎない限り、必要な数または詳細なテストを追加できない場合があります。

私が単体テストを主張する唯一の場所は、プラットフォーム固有のコードです。#ifdefs がプラットフォーム固有の高レベル関数/クラスに置き換えられている場合、これらはすべてのプラットフォームで同じテストでテストする必要があります。これにより、新しいプラットフォームを追加する時間を大幅に節約できます。

boost::test を使用してテストを構築します。単純な自己登録関数により、テストを簡単に記述できます。

これらは CTest (CMake の一部) にラップされており、単体テスト実行可能ファイルのグループを一度に実行し、簡単なレポートを生成します。

毎晩のビルドは ant と luntbuild で自動化されています (ant は c++、.net、および Java ビルドを接着します)。

すぐに、自動化された展開と機能テストをビルドに追加したいと考えています。

于 2009-04-16T00:14:03.917 に答える
5

私たちはまさにこれを実行している最中です。3年前、私は単体テストがほとんどなく、コードレビューがほとんどなく、かなりアドホックなビルドプロセスがないプロジェクトで開発チームに参加しました。

コードベースは、一連のCOMコンポーネント(ATL / MFC)、クロスプラットフォームのC ++ Oracleデータカートリッジ、およびいくつかのJavaコンポーネントで構成されており、すべてクロスプラットフォームのC++コアライブラリを使用しています。一部のコードは10年近く前のものです。

最初のステップは、いくつかの単体テストを追加することでした。残念ながら、動作は非常にデータ駆動型であるため、データベースからのテストデータを使用する単体テストフレームワーク(当初はCppUnit、現在はJUnitおよびNUnitを使用する他のモジュールに拡張)を生成するための初期の取り組みがありました。初期テストのほとんどは、実際には単体テストではなく、最外層を実行する機能テストでした。テストハーネスを実装するには、おそらくある程度の労力を費やす必要があります(予算を立てる必要があるかもしれません)。

単体テストを追加するコストをできるだけ低くすると、非常に役立つと思います。テストフレームワークにより、既存の機能のバグを修正するときにテストを比較的簡単に追加できます。新しいコードには適切な単体テストを含めることができます。コードの新しい領域をリファクタリングして実装すると、コードのはるかに小さな領域をテストする適切な単体テストを追加できます。

昨年、CruiseControlとの継続的インテグレーションを追加し、ビルドプロセスを自動化しました。これにより、テストを最新の状態に保ち、合格するためのインセンティブが大幅に高まります。これは、初期の大きな問題でした。したがって、開発プロセスの一部として、定期的な(少なくとも毎晩の)単体テストの実行を含めることをお勧めします。

私たちは最近、コードレビュープロセスの改善に焦点を合わせましたが、これはかなりまれで効果がありませんでした。その目的は、コードレビューの開始と実行をはるかに安価にして、開発者がより頻繁にコードレビューを実行できるようにすることです。また、プロセス改善の一環として、プロジェクト計画に含まれるコードレビューと単体テストの時間を、個々の開発者がそれらについてもっと考えなければならないように、はるかに低いレベルで取得しようとしていますが、以前は一定の割合しかありませんでしたスケジュールで迷子になるのがはるかに簡単だった彼らに捧げられた時間の。

于 2009-04-16T19:19:41.010 に答える
4

私は、完全にユニット テストされたコード ベースを使用するグリーン フィールド プロジェクトと、長年にわたって成長してきた大規模な C++ アプリケーションと、多くの異なる開発者が関与するプロジェクトの両方に取り組んできました。

正直なところ、レガシー コード ベースをユニット テストとテスト ファースト開発が多くの価値を追加できる状態にしようとは思いません。

レガシ コード ベースが特定のサイズと複雑さに達すると、単体テストのカバレッジが多くの利点を提供するポイントに到達すると、完全な書き直しと同等のタスクになります。

主な問題は、テスト容易性のためにリファクタリングを開始するとすぐに、バグが発生し始めることです。そして、高いテスト カバレッジを取得して初めて、これらすべての新しいバグが発見され、修正されることを期待できます。

つまり、非常にゆっくりと慎重に作業を進めると、十分にユニット テストされたコード ベースの利点を今から何年も経たないと得られないということです。(おそらく、合併などが起こって以来、決してありません。) それまでの間、ソフトウェアのエンドユーザーにとって明らかな価値のない新しいバグを導入している可能性があります。

または、すべてのコードの高いテスト カバレッジに到達するまで、迅速に作業を進めてもコード ベースが不安定になります。(したがって、最終的に 2 つのブランチが作成されます。1 つは本番環境用で、もう 1 つは単体テスト済みバージョン用です。)

もちろん、これは一部のプロジェクトでは規模の問題であり、書き換えには数週間しかかからず、確かに価値があります。

于 2009-04-14T18:24:56.277 に答える
3

考慮すべき1つのアプローチは、統合テストの開発に使用できるシステム全体のシミュレーションフレームワークを最初に配置することです。統合テストから始めるのは直感に反するように思えるかもしれませんが、説明する環境で真の単体テストを実行する際の問題は非常に手ごわいものです。おそらく、ソフトウェアでランタイム全体をシミュレートするだけではありません...

このアプローチは、リストされた問題を単純にバイパスしますが、多くの異なる問題が発生します。ただし、実際には、堅牢な統合テストフレームワークを使用すると、ユニットを分離しなくても、ユニットレベルで機能を実行するテストを開発できることがわかりました。

PS:PythonまたはTclで構築された、コマンド駆動型のシミュレーションフレームワークを作成することを検討してください。これにより、テストのスクリプトを非常に簡単に作成できます...

于 2009-04-14T17:18:32.630 に答える
3

G'day、

まず、明らかなポイントを確認することから始めます。たとえば、ヘッダーファイルでdecを使用します。

次に、コードがどのようにレイアウトされているかを確認します。それは論理的ですか?たぶん、大きなファイルを小さなファイルに分割し始めます。

たぶん、JonLakosの優れた本「Large-ScaleC++ Software Design」(Amazonのサニタイズされたリンク)のコピーを入手して、それがどのようにレイアウトされるべきかについてのアイデアを得てください。

コードベース自体、つまりファイルレイアウトのようなコードレイアウトをもう少し信頼し始め、ヘッダーファイルでdecを使用するなど、悪臭を取り除くと、次の機能を選択できるようになります。ユニットテストの作成を開始するために使用します。

良いプラットフォームを選んでください。私はCUnitとCPPUnitが好きで、そこから行きます。

しかし、それは長くて遅い旅になるでしょう。

HTH

乾杯、

于 2009-04-14T17:18:39.680 に答える
2

最初にモジュール化する方がはるかに簡単です。依存関係がたくさんあるものを実際に単体テストすることはできません。いつリファクタリングするかは難しい計算です。あなたは本当にコストとリスクと利益を比較検討する必要があります。このコードは広範囲に再利用されるものですか?または、このコードは実際には変更されません。あなたがそれを使い続けることを計画しているなら、あなたはおそらくリファクタリングしたいと思うでしょう。

しかし、リファクタリングしたいようです。あなたは最も単純なユーティリティを分解することから始めて、それらの上に構築する必要があります。膨大な数のことを実行するCモジュールがあります。たとえば、文字列を常に特定の方法でフォーマットするコードがそこにあるかもしれません。たぶん、これはスタンドアロンのユーティリティモジュールとして引き出すことができます。新しい文字列フォーマットモジュールがあり、コードが読みやすくなっています。そのすでに改善。あなたはキャッチ22の状況にあると主張しています。あなたは本当にそうではありません。物事を移動するだけで、コードが読みやすく、保守しやすくなります。

これで、この分割されたモジュールの単体テストを作成できます。あなたはそれをいくつかの方法で行うことができます。コードだけを含み、PCのメインルーチンで一連のケースを実行する別のアプリを作成するか、すべてのテストケースを実行し、合格すると「1」を返す「UnitTest」という静的関数を定義することができます。これは、ターゲットで実行できます。

このアプローチでは100%達成できないかもしれませんが、それは始まりであり、テスト可能なユーティリティに簡単に分解できる他のものを見ることができるかもしれません。

于 2009-04-14T17:18:20.960 に答える
2

基本的に、2 つの別々の問題があると思います。

  1. リファクタリングする大規模なコード ベース
  2. チームで作業する

モジュール化、リファクタリング、単体テストの挿入などは難しい作業であり、ツールがその作業の大部分を引き継ぐことができるとは思えません。珍しいスキルです。一部のプログラマーはそれを非常にうまく行うことができます。ほとんどはそれを嫌います。

このようなタスクをチームで行うのは面倒です。「開発者を「強制」する」ことが機能するかどうか、私は強く疑っています。Iains の考えは非常に良いですが、リファクタリング、Modualrize、単体テストの導入など、ソースを「クリーンアップ」できるプログラマーを 1 人か 2 人見つけることを検討します。バグ、aehm 機能。そういう仕事が好きな人だけがその仕事で成功します。

于 2009-04-16T08:27:10.020 に答える
2

テストの使用を簡単にします。

「自動的に実行」を配置することから始めます。開発者 (自分自身を含む) にテストを作成してもらいたい場合は、簡単に実行できるようにして、結果を確認してください。

3 行のテストを作成し、最新のビルドに対して実行し、結果を確認するのは 1 回のクリックで完了し、開発者をコーヒー マシンに送る必要はありません。

これは、最新のビルドが必要であることを意味し、人々がコードで作業する方法などのポリシーを変更する必要があるかもしれません。そのようなプロセスが組み込みデバイスの PITA になる可能性があることはわかっています。それについてアドバイスすることはできません。しかし、テストを実行するのが難しい場合、誰もテストを書きません。

テストできるものをテストする

ここで一般的な単体テストの哲学に反することは承知していますが、それが私がしていることです。テストしやすいものに対してテストを記述します。私はモッキングを気にせず、テスト可能にするためにリファクタリングもしません。また、UI が関係している場合、単体テストは行いません。しかし、ますます多くのライブラリ ルーチンが 1 つを持っています。

簡単なテストで何が見つかる傾向にあるのか、私は非常に驚いています。ぶら下がっている果物を選ぶことは、決して無駄ではありません。

別の見方をすれば、もしそれが成功した製品でなければ、その巨大な毛玉の混乱を維持するつもりはないでしょう. 現在の品質管理は、交換が必要な完全な失敗ではありません。むしろ、簡単に実行できる単体テストを使用してください。

(ただし、それを完了する必要があります。ビルド プロセスに関する「すべてを修正する」ことにとらわれないでください。)

コードベースを改善する方法を教える

その歴史を持つすべてのコード ベースは改善を求めています。それは確かです。ただし、すべてをリファクタリングすることはありません。

同じ機能を持つ 2 つのコードを見ると、ほとんどの人は、特定の側面 (パフォーマンス、読みやすさ、保守性、テスト容易性など) でどちらが「優れている」かについて同意することができます。難しい部分は次の 3 つです。

  • さまざまな側面のバランスをとる方法
  • このコードで十分であることに同意する方法
  • 何も壊さずに悪いコードを十分に良いコードに変える方法。

最初のポイントはおそらく最も難しく、技術的な問題と同じくらい社交的な問題です。しかし、他の点は学ぶことができます。このアプローチを取る正式なコースは知りませんが、おそらく社内で何かを組織することができます.


于 2009-04-16T09:09:55.610 に答える
2

そのすべてに哲学的な側面があります。

テスト済みで、完全に機能する、整頓されたコードが本当に必要ですか? それはあなたの目的ですか?あなたはそれから何か利益を得ますか?.

はい、最初はこれはまったくばかげているように聞こえます。しかし正直なところ、あなたが単なる従業員ではなく、システムの実際の所有者でない限り、バグは仕事の増加を意味し、仕事の増加はお金の増加を意味します。毛玉に取り組んでいる間、あなたは完全に幸せになることができます.

推測にすぎませんが、この巨大な戦いに挑むリスクは、コードを整理することで得られる見返りよりもはるかに高いでしょう。これを乗り越える社会的スキルが欠けていると、トラブルメーカーと見なされるだけです. 私はこれらの人たちを見てきました、そして私もその一人でした。しかし、もちろん、これをやり遂げればかなりクールです。私は感動するでしょう。

しかし、あなたがいじめられて今、散らかったシステムを動かし続けるために余計な時間を費やさなければならないと感じているなら、コードがきちんと整頓されていれば、それは変わると本当に思いますか?. いいえ.. コードがきれいに整頓されれば、人々は最初の締め切りに再び完全に破棄するための自由な時間を得ることができます。

最終的に、コードではなく、良い職場を作るのは経営陣です。

于 2013-01-22T20:33:09.963 に答える