web-dev-qa-db-ja.com

現在のスレッドがメインスレッドであることを表明する単体テスト

問題は、現在のスレッドがメインスレッドであることを表明する単体テストを記述することには意味があるのでしょうか。長所短所?

最近、サービスのコールバックのために現在のスレッドをアサートする単体テストを見ました。いいアイデアだとは思いませんが、もっと統合テストだと思います。私の意見では、ユニットテストはメソッドを分離してアサートする必要があり、サービスの利用者の性質については知らないはずです。

たとえば、iOSでは、このサービスのコンシューマーは、デフォルトでメインスレッドでコードを実行するための制約を持つUIであることが意図されています。

8

私のお気に入りの単体テストの原則を紹介しましょう。

次の場合、テストは単体テストではありません。

  1. データベースと通信します
  2. ネットワークを介して通信します
  3. それはファイルシステムに触れます
  4. 他の単体テストと同時に実行することはできません
  5. 実行するには、環境に特別なこと(構成ファイルの編集など)を行う必要があります。

ユニットテストルールのセット-Michael Feathers

ユニットテストで「メインスレッド」であると主張されている場合、4番のファウルを実行しないのは難しいことです。

これは厳しいように見えるかもしれませんが、単体テストはさまざまな種類のテストの1つにすぎないことを覚えておいてください。

enter image description here

これらの原則に違反することは自由です。ただし、テストをユニットテストと呼ばないでください。実際の単体テストでそれを入れて、それらを遅くしないでください。

11
candied_orange

スレッドがメインスレッドかどうかをテストする単体テストを作成する必要がありますか?依存しますが、おそらくは良い考えではありません。

ユニットテストのポイントがメソッドの適切な機能を検証することを目的としている場合、この側面のテストはほぼ確実にnotこの側面のテストです。あなたがあなたが言ったように、メソッドの適切な機能はどのスレッドがそれを実行しているかに基づいて変化する可能性が低く、どのスレッドが使用されるかを決定する責任があるので、それは概念的には統合テストに近いですcallerであり、メソッド自体ではありません。

ただし、メソッドが新しいスレッドを生成する場合は、メソッド自体にdependが十分に当てはまる可能性があるため、これが依存すると言う理由です。おそらくこれはあなたのケースではありません。

私のアドバイスは、ユニットテストのこのチェックoutを残して、この側面の適切な統合テストに移植することです。

5
Neil

サービスの消費者の性質について知らないはずです。

消費者がサービスを利用するとき、どの機能がどのように提供されるかについて明示的または黙示的な「契約」があります。コールバックが発生するスレッドの制限は、そのコントラクトの一部です。

「メインスレッド」で実行されるある種の「イベントエンジン」が存在するコンテキストでコードが実行され、他のスレッドがイベントエンジンのスケジュールコードがメインスレッドで実行されるように要求する方法があると思います。

ユニットテストの最も純粋なビューでは、すべての依存関係を絶対に模倣します。現実の世界ではそれを行う人はほとんどいません。テスト中のコードは、少なくとも基本的なプラットフォーム機能の実際のバージョンを使用できます。

したがって、本当の質問IMOは、イベントエンジンとランタイムスレッドのサポートを、単体テストが依存できるかどうかにかかわらず許可される「基本的なプラットフォーム機能」の一部と見なすことですか。その場合、コールバックが「メインスレッド」で発生するかどうかを確認することは完全に理にかなっています。イベントエンジンとスレッドシステムをモックする場合は、モックを設計して、コールバックがメインスレッドで発生したかどうかを判断できるようにします。イベントエンジンをモックしているが、ランタイムスレッディングサポートはモックしていない場合は、モックイベントエンジンが実行されているスレッドでコールバックが発生するかどうかをテストする必要があります。

2
Peter Green

それが魅力的なテストになる理由がわかります。しかし、実際には何をテストするのでしょうか?私たちは機能を持っていると言います

showMessage(string m)
{
    new DialogueBox(m).Show(); //will fail if not called from UI thread
}

これをテストして、非UIスレッドで実行し、エラーがスローされることを証明できます。ただし、実際にそのテストが何らかの値を持つためには、非UIスレッドで実行したときに関数が実行する特定の動作が関数に必要です。

showMessage(string m)
{
    try {
        new DialogueBox(m).Show(); //will fail if not called from UI thread
    }
    catch {
        Logger.Log(m); //alternative display method
    }
}

現在、テストする必要がありますが、この種の代替手段が実際の生活に役立つかどうかは不明です

1
Ewan

コールバックが、UIスレッドやメインスレッド以外で起動してもスレッドセーフでないことが文書化されている場合、コードモジュールの単体テストでは、このコールバックの呼び出しを確実にするために完全に妥当と思われます。安全を保証するためにOSまたはアプリケーションフレームワークが文書化されているスレッドの外部で作業することにより、スレッドセーフではない処理を行っていません。

1
hotpaw2

アレンジ
行為
主張する

単体テストは、それが特定のスレッド上にあることを主張する必要がありますか?いいえ、その場合はユニットをテストしていないので、ユニットで行われている構成については何も言われていないため、統合テストでさえありません...

テストがオンになっているスレッドをアサートすることは、テストサーバーの名前をアサートすること、またはテストメソッドの名前をアサートすることに似ています。

さて、特定のスレッドでテストを実行するようにArrangeするのは理にかなっているかもしれませんが、スレッドに基づいて特定の結果が返されることを確認したい場合のみです。

1
jmoreno

メインスレッドでのみ正しく実行することが記載されている関数があり、その関数がバックグラウンドスレッドで呼び出された場合、それは関数の障害ではありません。障害は呼び出し側の障害であるため、単体テストは無意味です。

メインスレッドで関数が正しく実行されるようにコントラクトが変更され、バックグラウンドスレッドでの実行時にエラーが報告される場合は、そのためのユニットテストを記述できます。バックグラウンドスレッドで、ドキュメントどおりに動作することを確認します。これで、単体テストはできましたが、メインスレッドで実行されることを確認する単体テストはありません。

私はあなたの関数の最初の行がメインスレッドで実行されることをassertにすることを強くお勧めします。

1
gnasher729

単体テストは、適切な使用法ではなく、上記の関数の正しい実装をテストする場合にのみ役立ちます。ユニットテストでは、コードベースの他のスレッドから別のスレッドから関数が呼び出されないことはできないためです。 t前提条件に違反するパラメーターを指定して関数を呼び出すことはできないと言います(技術的には、テストしようとしているのは、基本的には使用法の前提条件の違反です。これは、テストを行うことができないため、効果的にテストすることはできません。 tコードベースの他の場所がそのような関数をどのように使用するかを制限します;ただし、前提条件の違反が適切なエラー/例外になるかどうかをテストできます)。

これは、他の人が指摘しているように、関連する関数自体の実装における私にとっての主張のケースです。または、さらに洗練されているのは、関数がスレッドセーフであることを確認することです(ただし、一部のAPIを操作する場合、これは必ずしも実用的ではありません)。

また、余談ですが、すべてのケースで「メインスレッド」!=「UIスレッド」です。多くのGUI APIはスレッドセーフではありません(そして、スレッドセーフGUIキットを作成するのは難しいです)が、それは、アプリケーションのエントリポイントがあるスレッドと同じスレッドからそれらを呼び出す必要があるという意味ではありません。これは、関連するUI関数内にアサーションを実装して「メインスレッド」から「UIスレッド」を区別する場合にも役立ちます。たとえば、アプリケーションのメインエントリポイント(ではなく、少なくとも、実装が本当に関連するものにのみ適用している仮定/使用制限の量を減らします。

スレッドセーフは、実際には以前の私のチームの「落とし穴」でしたが、私たちの特定のケースでは、最も安全な「マイクロ最適化」というラベルを付けました。手書きのアセンブリ。単体テストではかなり包括的なコードカバレッジがあり、かなり洗練された統合テストがありましたが、テストを回避したソフトウェアでデッドロックと競合状態に遭遇しました。そしてそれは、開発者が独自に発生する関数呼び出しのチェーンで発生する可能性のあるすべての副作用に気づかずに無計画にマルチスレッド化されたコードであり、投げるだけで後からそのようなバグを修正できるというかなり単純な考えがあるためです左右にロックし、おそらくテストカバレッジから誤った自信を得る*。

マルチスレッディングを信用していない旧式のタイプとは逆の方向に歪んでいて、それを採用することに後れをとっていて、正しさはパフォーマンスを打ち負かして、今あるすべてのコアをめったに使用しないようにしました。純粋な関数、不変のデザイン、永続的なデータ構造のように、レース状態やデッドロックを心配することなく、ハードウェアを完全に活用することができました。 2010年頃までずっと、私はマルチスレッドを情熱を持って嫌っていたが、スレッドセーフの理由から取るに足らない領域でのいくつかの並列ループを除いて、情熱を持って嫌いであり、以前のチームでマルチスレッディングに悩まされた製品。

私にとって、最初にマルチスレッディングを行い、後でバグを修正する方法は、最初はマルチスレッディングを嫌いになるほどのマルチスレッディングにとっては恐ろしい戦略です。設計が確実なスレッドセーフであり、それらの実装が同様の保証のある関数(例:純粋な関数)のみを使用することを確認するか、マルチスレッドを回避します。それは少し独断的に出くわすかもしれませんが、それはテストを逃れる後知恵で再現するのが難しい問題を発見する(またはより悪いことに、発見しない)よりも優れています。ロケットエンジンを最適化しても、宇宙への旅の途中で予期せず突然爆発しやすくなる場合は、意味がありません。

スレッドセーフではないコードを使用せざるを得ない場合は、単体/統合テストで解決する問題としてはあまり考えません。理想はアクセスを制限するです。 GUIコードがビジネスロジックから分離されている場合、それを作成するスレッド/オブジェクト以外からのそのような呼び出しへのアクセスを制限する設計を実施できる場合があります*。これは、私にとって理想的な遠いモードです。他のスレッドがそれらの関数を呼び出せないようにするのではなく、それらを呼び出せないようにすることです。

  • はい、私は通常、コンパイラーがあなたを保護することができないところであなたが強制するどんなデザイン制限の周りにも常に方法があることを理解しています。私は実際に話しているだけです。 「GUIスレッド」オブジェクトなどを抽象化できる場合、それはGUIオブジェクト/関数にパラメーターを渡す唯一のオブジェクトであり、そのオブジェクトが他のスレッドにアクセスできないように制限できる可能性があります。もちろん、それを迂回して深く掘り下げ、そのようなフープを回避して上記のGUI関数/オブジェクトを他のスレッドに渡して呼び出すことができるかもしれませんが、少なくともそこには障壁があり、それを実行する誰にでも「ばか「そして、少なくとも、設計が明らかに制限しようとしていたものの抜け穴を明確に迂回して探すために、間違っているわけではありません。 :-Dこれは実際に私にとって非常に実用的なリトマステストです。実際に誤用しやすいものを設計するための馬鹿であるというリスクなしに、設計を誤用することで誰かを「馬鹿」と自信を持って呼ぶことができるようなものです。
1
Dragon Energy