web-dev-qa-db-ja.com

匿名の名前空間はコードをテスト不可能にします

典型的なC++コードは次のとおりです。

foo.hpp

_#pragma once

class Foo {
public:
  void f();
  void g();
  ...
};
_

foo.cpp

_#include "foo.hpp"

namespace {
    const int kUpperX = 111;
    const int kAlternativeX = 222;

    bool match(int x) {
      return x < kUpperX || x == kAlternativeX;
    }
} // namespace

void Foo::f() {
  ...
  if (match(x)) return;
  ...
_

それはまともな慣用的なC++コードのように見えます-クラス、ヘルパー関数matchのメソッドによって使用されるヘルパー関数Foo、そのヘルパー関数のいくつかの定数。

そして、テストを書きたいです。
matchの個別の単体テストを書くことは完全に論理的です。
しかし、それは匿名の名前空間に存在します。
もちろん、Foo::f()を呼び出すテストを作成できます。ただし、Fooが重くて複雑な場合、このテストは適切ではありません。このようなテストは、受験者を他の無関係な要因から隔離しません。

したがって、matchおよびその他すべてを匿名の名前空間から移動する必要があります。

質問:関数と定数を匿名の名前空間に配置すると、テストで使用できなくなるのはなぜですか。

12
Abyx

プライベートな実装の詳細を単体テストする場合は、名前のない名前空間に対して、プライベート(または保護された)クラスメンバーと同じ種類の回避を行います。

休憩とパーティー。

クラスの場合はfriendを悪用しますが、名前のない名前空間の場合は#include- mechanismを悪用します。これにより、コードを変更する必要はありません。
これで、テストコード(またはすべてを公開するための何かだけ)が同じTUにあるため、問題はありません。

警告:実装の詳細をテストする場合、それらが変更されるとテストは失敗します。とにかくリークする実装の詳細のみをテストするか、テストが異常に短命であることを受け入れるようにしてください。

7
Deduplicator

この例の関数は非常に複雑に見えますが、単体テストの目的では、関数をヘッダーに移動する方がよい場合があります。

テストで使用できなくなる場合、匿名の名前空間に関数と定数を置く意味は何ですか?

それらを他の世界から隔離するため。そして、匿名名前空間に入れることができるのは関数と定数だけではありません-それは型のためにもあります。

ただし、単体テストが非常に複雑になる場合は、間違っています。そのような場合、関数はそこに属していません。次に、リファクタリングを少し行って、テストを簡単にします。

したがって、匿名の名前空間では、非常に単純な関数のみを使用する必要があり、その変換単位で使用される定数と型(typedefを含む)が使用される場合があります。

6
BЈовић

それは非常に自明ではないので、一致のために個別の単体テストを書くことは完全に論理的です。

あなたがmatchに示したコードは、トリッキーなEdgeケースのないかなり簡単な1ライナーですか、それとも単純化された例のようですか?とにかく、私はそれが簡略化されていると思います...

質問:関数と定数を匿名の名前空間に配置すると、テストで使用できなくなるのはなぜですか?

この質問は、Deduplicatorが#include策略。しかし、ここでの表現は、すべての内部実装の詳細をすべてテストすることが、ある種の普遍的な最終目標であるように思えます。

単体テストの目的も、機能の細かい内部マイクロユニットをすべてテストすることであるとは限りません。同じ質問がCの静的ファイルスコープ関数にも当てはまります。開発者がC++でpimplsを使用する理由を尋ねることで、質問の答えを難しくすることもできますbothfriendshipおよび#includeホワイトボックスへのトリック、実装時間の改善のための実装詳細の容易なテスト可能性のトレードオフ。

一種の実用的な観点からは、全体的に聞こえるかもしれませんが、matchは、それが作動する原因となる一部のEdgeケースでは正しく実装されない場合があります。ただし、Fooにアクセスできる唯一の外部クラスmatchが、それらのEdgeケースに遭遇するような方法で使用できない場合、Foomatchには、Fooが変更されない限り発生しないこれらのEdgeケースがあり、その時点でFooのテストは失敗し、すぐにわかります。

すべての内部実装の詳細(たとえば、ミッションクリティカルなソフトウェアなど)をテストすることに熱心なマインドセットは、侵入してパーティに参加したいと思うかもしれませんが、多くの人々は、それが最良のアイデアであるとは必ずしも考えていません。想像できる最ももろいテスト。 YMMV。しかし、私はこの質問の表現に取り組みたかったので、この種の非常に細かい内部の詳細レベルのテスト容易性が最終目標であるように思えます。最も厳密なユニットテストの考え方でさえ、ここで少しリラックスできるかもしれません。すべてのクラスの内部のX線撮影を避けます。

では、なぜC++の匿名の名前空間で関数を定義するのか、またはCの内部リンケージを使用して、外部の世界から隠されたファイルスコープの静的関数として関数を定義するのでしょうか。そしてそれは主にそれです:外の世界からそれらを隠すため。これには、コンパイル時間の短縮から複雑さの軽減(他の場所でアクセスできないものは他の場所で問題を引き起こすことはありません)など、多くの効果があります。おそらく、プライベート/内部実装の詳細のテスト可能性は、ビルド時間を短縮し、不要な複雑さを外部の世界から隠すなど、人々がそれをやり直すときに一番気になることではありません。

5
user204677