web-dev-qa-db-ja.com

組み込みデバイスでTDDを実行するにはどうすればよいですか?

私はプログラミングに不慣れで、AVRで低レベルのCおよびASMを使用して作業したこともありますが、大規模な組み込みCプロジェクトに頭を悩ませることはできません。

RubyのTDD/BDDの哲学に縮退しているため、このようなコードを作成してテストする方法を理解できません。それが悪いコードだと言っているのではなく、これがどのように機能するのか理解していないだけです。

いくつかの低レベルのプログラミングにもっと取り組みたかったのですが、慣れ親しんだ考え方とはまったく異なるように見えるので、これにどのように取り組むかまったくわかりません。ポインタの計算やメモリの割り当ての仕組みを理解するのに問題はありませんが、Rubyと比較して複雑なC/C++コードがどのように見えるかを見ると、信じられないほど難しいようです。

私はすでにArduinoボードを注文しているので、低レベルのCに詳しく入り、実際に適切に行う方法を理解したいのですが、高水準言語のルールが適用されないようです。

組み込みデバイスでTDDを実行したり、ドライバーやカスタムブートローダーなどを開発したりすることもできますか?

17
Jakub Arnold

まず、自分で記述していないコードを理解しようとすることは、自分で記述するよりも5倍難しいことを知っておく必要があります。プロダクションコードを読むことでCを学ぶことができますが、実際に学ぶよりもかなり時間がかかります。

RubyのTDD/BDDの哲学に縮退しているため、このようなコードを作成してテストする方法を理解できません。それが悪いコードだと言っているのではなく、これがどのように機能するのか理解していないだけです。

それはスキルです。あなたはそれで上手になります。ほとんどのCプログラマーは、人々がRubyをどのように使用するかを理解していませんが、それができないという意味ではありません。

組み込みデバイスでTDDを実行したり、ドライバーやカスタムブートローダーなどを開発したりすることもできますか?

まあ、このテーマに関する本があります:

enter image description hereマルハナバチがそれをできるなら、あなたもできる!

他の言語のプラクティスを適用しても通常は機能しないことに注意してください。 TDDはかなり普遍的です。

18
Pubby

ここには多種多様な答えがあります。主にさまざまな方法で問題に対処しています。

私は25年以上にわたり、低レベルの組み込みソフトウェアとファームウェアをさまざまな言語(ほとんどはC)で書いてきました(ただし、途中でAda、Occam2、PL/M、およびさまざまなアセンブラーに転用しています)。

長い間、試行錯誤を繰り返した結果、かなり迅速に結果が得られ、テストラッパーとハーネスを作成するのが非常に簡単な方法に落ち着きました(ここで値を追加します!)。

メソッドは次のようになります。

  1. 使用する主要な周辺機器ごとに、ドライバーまたはハードウェアアブストラクションコードユニットを記述します。また、プロセッサを初期化してすべてを設定するためのコードも作成します(これにより、環境がフレンドリーになります)。通常、小さな組み込みプロセッサでは、AVRがその例です-このようなユニットが10〜20台あり、すべて小さい場合があります。これらは、初期化、スケーリングされていないメモリバッファーへのA/D変換、ビット単位の出力、プッシュボタン入力(サンプリングされたばかりのデバウンスなし)、パルス幅変調ドライバー、UART /単純なシリアルドライバー、割り込みを使用するためのユニットです。小さなI/Oバッファ。さらにいくつかあるかもしれません-例えばI2CまたはSPIドライバ)。

  2. 次に、ハードウェアアブストラクション(HAL)/ドライバーユニットごとに、テストプログラムを作成します。これは、シリアルポート(UART)とプロセッサの初期化に依存しているため、最初のテストプログラムはこれらの2つのユニットのみを使用し、基本的な入出力をいくつか行います。これにより、プロセッサを起動できること、および基本的なデバッグサポートのシリアルI/Oが機能していることをテストできます。それが機能するようになったら(そしてそれだけで)、他のHALテストプログラムを開発し、既知の優れたUARTおよびINITユニットの上に構築します。ビット単位の入力を読み取り、シリアルデバッグターミナルにニース形式(16進数、10進数など)で表示するためのテストプログラムがあるかもしれません。次に、EEPROMまたはEPROMテストプログラムなどの大きくて複雑なものに移動できます。実行するテストを選択して実行し、結果を確認できるように、これらのメニュー駆動型です。スクリプトを作成することはできませんが、通常は必要ありません-メニュー駆動型で十分です。

  3. すべてのHALを実行したら、定期的なタイマーティックを取得する方法を見つけます。これは通常、4〜20ミリ秒の間のレートです。これは規則的でなければならず、割り込みで生成されます。カウンターのロールオーバー/オーバーフローは通常、これを行う方法です。次に、割り込みハンドラはバイトサイズの「セマフォ」をインクリメントします。この時点で、必要に応じて電源管理をいじることもできます。セマフォの考え方は、その値が0より大きい場合、「メインループ」を実行する必要があるということです。

  4. EXECUTIVEはメインループを実行します。それはほぼそのセマフォが0以外になるのを待ちます(私はこの詳細を抽象化します)。この時点で、これらのティック(ティックレートを知っているco)をカウントするためにカウンターをいじってみることができます。そのため、現在のエグゼクティブティックが1秒、1分、およびその他の一般的なインターバルであるかどうかを示すフラグを設定できます。使いたいかもしれません。エグゼクティブは、セマフォが> 0であることを認識すると、すべての「アプリケーション」プロセスの「更新」関数を1回通過します。

  5. アプリケーションプロセスは事実上互いに並んで配置され、「更新」ティックによって定期的に実行されます。これは、エグゼクティブによって呼び出される関数です。これは実質的に貧弱な人のマルチタスク処理であり、非常にシンプルな自家製のRTOSは、すべてのアプリケーションが入り、小さな作業を行い、終了することに依存します。アプリケーションは独自の状態変数を維持する必要があります公平性を強制するプリエンプティブなオペレーティングシステムがないため、長時間実行されている計算を実行できません。アプリケーションの実行時間は、(累積的に)主ティック期間よりも短くする必要があります。

上記のアプローチは簡単に拡張できるので、非同期に実行される通信スタックのようなものを追加して、通信メッセージをアプリケーションに配信できます(「rx_message_handler」である新しい関数をそれぞれに追加し、図のようなメッセージディスパッチャーを記述しますどのアプリケーションにディスパッチするか)。

このアプローチは、名前を付けたいほとんどすべての通信システムで機能します。多くのプロプライエタリシステム、オープンスタンダードコミュニケーションシステムで機能し、TCP/IPスタックでも機能します。

また、明確に定義されたインターフェースを備えたモジュール式部品​​で構築されるという利点もあります。いつでもピースを出し入れして、別のピースに置き換えることができます。途中の各ポイントで、テストハーネスまたはハンドラーを追加できます。これらは、既知の適切な下層パーツ(下のもの)に基づいて構築されます。設計の約30%から50%は、通常はかなり簡単に追加できる特別に作成された単体テストを追加することでメリットを得られることがわかりました。

私はこれをさらに一歩進め(これを行った他の誰かからニックネームを付けました)、HALレイヤーをPCの同等のものに置き換えます。したがって、たとえば、PCでC/C++やwinformsなどを使用し、コードを注意深く書くことで、各インターフェイス(たとえば、EEPROM = PCメモリに読み込まれたディスクファイル)をエミュレートして、PCで組み込みアプリケーション全体を実行できます。使いやすいデバッグ環境を使用できるため、時間と労力を大幅に節約できます。通常、本当に大きなプロジェクトだけが、この労力を正当化できます。

上記の説明は、組み込みプラットフォームでの作業方法に固有のものではありません。同様のことを行う多くの商業組織に出会いました。その実行方法は通常、実装では大きく異なりますが、原則はほとんど同じです。

上記が少し趣を与えてくれることを願っています...このアプローチは、数kBで動作する小さな組み込みシステムで、強力なバッテリー管理から、永続的に電源が供給される100K以上のソースラインのモンスターまで機能します。 Windows CEなどの大きなOSで「組み込み」を実行する場合、上記のすべてはまったく重要ではありません。とにかく、それは本当の組み込みプログラミングではありません。

16
quickly_now

私がやったことは、デバイスに依存するコードをデバイスに依存しないコードから分離し、デバイスに依存しないコードをテストすることです。優れたモジュール性と規律により、mostly十分にテストされたコードベースが完成します。

3
Paul Nathan

選択した例のように、複数のプラットフォームの段階的な開発と最適化の長い歴史を持つコードは、通常、読みにくくなっています。

Cの重要な点は、実際には、広範なAPIの豊富さとハードウェアパフォーマンス(およびその欠如)にまたがってプラットフォームをスパンできることです。 MacVimは、今日の一般的なスマートフォンよりもメモリとプロセッサのパフォーマンスが1000分の1を下回るマシンで応答性よく動作しました。あなたのRubyコード?)は、選択した成熟したCの例よりもシンプルに見える理由の1つです。

2
hotpaw2

私は過去9年間のほとんどをCプログラマーとして過ごしてきたという逆の立場にあり、最近いくつかのRuby on Railsフロントエンドに取り組んでいます。

私がCで取り組んでいるものは、ほとんどが自動倉庫を制御するための中規模のカスタムシステムです(通常、数十万ポンドから最大数百万ポンド)。機能の例は、カスタムのインメモリデータベースであり、短い応答時間の要件と倉庫ワークフローのより高レベルの管理を備えた機械に接続します。

まず第一に、TDDは一切行いません。単体テストを導入するために何度か試してみましたが、Cでは、少なくともカスタムソフトウェアを開発するときは、その価値よりも問題が多くなります。しかしCでは、RubyよりTDDの方がはるかに少ないと言えます。これは主に、Cがコンパイルされているためであり、警告なしでコンパイルされた場合、Railsでrspecが自動生成した足場テストとかなり類似した量のテストをすでに行っています。 Ruby単体テストなしでは実行できません。

しかし、私が言えることは、Cは一部の人が作るほど難しくないということです。 C標準ライブラリの多くは、理解できない関数名の混乱であり、多くのCプログラムがこの規則に従っています。私はそうしていないことを嬉しく思います。実際、標準ライブラリ機能のラッパーがたくさんあります(strncpyの代わりにST_Copy、regcomp/regexecの代わりにST_PatternMatch、iconv_open/iconv/iconv_closeの代わりにCHARSET_Convertなど)。社内のCコードは、私が読んだ他のほとんどのコードよりも読みやすくなっています。

しかし、他の高水準言語のルールが適用されないように思われる場合、私は同意しません。優れたCコードの多くは、オブジェクト指向を「感じ」ます。リソースへのハンドルを初期化し、引数としてハンドルを渡していくつかの関数を呼び出し、最終的にリソースを解放するパターンをよく目にします。実際、オブジェクト指向プログラミングの設計原則は、主に人々が手続き型言語で行っていた良いことからきています。

Cが本当に複雑になるのは、基本的に非常に低レベルなデバイスドライバーやOSカーネルなどを実行するときです。より高いレベルのシステムを作成する場合、Cのより高いレベルの機能を使用して、低いレベルの複雑さを回避することもできます。

調べてみたい非常に興味深いことの1つは、RubyのCソースコードです。 Ruby API docs(http://www.Ruby-doc.org/core-1.9.3/))では、クリックしてさまざまなメソッドのソースコードを確認できます。このコードは非常に見栄えが良くエレガントです-想像するほど複雑ではありません。

2
asc99c

できない理由はありません。問題は、他のタイプの開発にあるような「すぐに使える」ニースの単体テストフレームワークがない可能性があることです。それで大丈夫です。それは単に、テストに「自分でロール」するアプローチを取る必要があることを意味します。

たとえば、A/Dコンバーターの「偽の入力」を生成するためにインストルメンテーションをプログラムする必要がある場合や、組み込みデバイスが応答するための「偽のデータ」のストリームを生成する必要がある場合があります。

「TDD」という言葉の使用に抵抗がある場合は、「DVT」(設計検証テスト)と呼んでください。これにより、EEはアイデアをより快適にします。

2
Angelo

組み込みデバイスでTDDを実行したり、ドライバーやカスタムブートローダーなどを開発したりすることもできますか?

少し前に、ARM CPUの第1レベルのブートローダーを作成する必要がありました。実際には、このCPUを売っている人からのものがあります。そして、彼らのブートローダーが私たちのブートローダーを起動するスキームを使用しました。しかし、2つのファイルを1つではなくNOR flashにフラッシュする必要があったため、これは遅く、ブートローダーのサイズを最初のブートローダーにビルドし、ブートローダーを変更するたびに再構築する必要がありました。など。

それで、私は彼らのブートローダーの機能を私たちのものに統合することに決めました。商用コードなので、すべてが期待どおりに機能することを確認する必要がありました。そのため、 [〜#〜] qemu [〜#〜] を変更して、そのCPUのIPブロック(すべてではなく、ブートローダーにアクセスするもののみ)をエミュレートし、QEMUに「printf」すべてのコードを追加しましたPLL、UART、SRAMコントローラーなどを制御するレジスターの読み取り/書き込み。次に、このCPUをサポートするようにブートローダーをアップグレードしました。その後、ブートローダーとそのエミュレーターを提供する出力を比較したところ、いくつかのバグを見つけるのに役立ちました。一部はARMアセンブラー、一部はCで記述されました。また、その後修正されたQEMUは、JTAGと実際のARM CPUを使用してキャッチできなかった1つのバグをキャッチするのに役立ちました。

したがって、Cとアセンブラーでもテストを使用できます。

0
Evgeniy