C++アプリケーションの単体テストを作成したいと思います。
クラスのプライベートメンバーをテストするための正しいフォームは何ですか?プライベートメンバーをテストしたり、派生クラスを使用したり、その他のトリックを使用したりするフレンドクラスを作成しますか?
テストAPIはどの手法を使用しますか?
C++アプリケーションの単体テストを作成したいと思います。
クラスのプライベートメンバーをテストするための正しいフォームは何ですか?プライベートメンバーをテストしたり、派生クラスを使用したり、その他のトリックを使用したりするフレンドクラスを作成しますか?
テストAPIはどの手法を使用しますか?
通常、質問のコメントで説明されているように、パブリックインターフェイスのみをテストします。
ただし、プライベートメソッドまたは保護されたメソッドをテストすると役立つ場合があります。たとえば、実装には、ユーザーからは見えず、非公開メンバーにアクセスすることでより正確にテストできる、重要な複雑さが含まれている場合があります。多くの場合、その複雑さを取り除く方法を理解するか、関連する部分を公開する方法を理解する方が良いですが、常にではありません。
非公開メンバーへの単体テストアクセスを許可する1つの方法は、フレンド構造を使用することです。
この質問に答えることは、他の多くのトピックに触れます。CleanCode、TDDなどの宗教性に加えて:
プライベートメンバーにアクセスするには、いくつかの方法があります。いずれにせよ、テストされたコードを却下する必要があります!これは、C ++の解析の両方のレベル(プリプロセッサと言語自体)で可能です。
すべてを公開する
プリプロセッサを使用することで、カプセル化を破ることができます。
#define private public
#define protected public
#define class struct
欠点は、配信されたコードのクラスがテストと同じではないことです。9.2.13章のC++標準は次のように述べています。
アクセス制御が異なる非静的データメンバーの割り当て順序は指定されていません。
これは、コンパイラーがテストのメンバー変数と仮想関数を並べ替える権利を持っていることを意味します。バッファオーバーフローが発生しなくてもクラスに害を及ぼさないように苦労するかもしれませんが、それは、提供するのと同じコードをテストしないことを意味します。つまり、コードによって初期化され、にprivate
定義されていない状態でコンパイルされたオブジェクトのメンバーにアクセスpublic
すると、メンバーのオフセットが異なる可能性があります。
友達
このメソッドは、テストクラスまたはテスト関数とフレンドリングするために、テストされたクラスを変更する必要があります。gtest(FRIEND_TEST(..);
)のようないくつかのテストフレームワークには、私的なものにアクセスするこの方法をサポートする特別な機能があります。
class X
{
private:
friend class Test_X;
};
テストのためだけにクラスを開き、世界を開くことはありませんが、配信されるコードを変更する必要があります。私の意見では、これは悪いことです。なぜなら、テストによってテストされたコードが変更されることは決してないからです。さらに不利な点として、配信されたコードの他のクラスに、テストクラスのように名前を付けることで、クラスに侵入する可能性があります(これは、C ++標準のODRルールにも悪影響を及ぼします)。
保護され、テストのためにクラスから派生した私的なものを宣言する
非常にエレガントな方法ではなく、非常に煩わしい方法ですが、次のようにも機能します。
class X
{
protected:
int myPrivate;
};
class Test_X: public X
{
// Now you can access the myPrivate member.
};
マクロを使用するその他の方法
動作しますが、最初の方法と同様に、標準の適合性に関して同じ欠点があります。例えば:
class X
{
#ifndef UNITTEST
private:
#endif
};
最後の両方の方法は、最初の2つの方法に勝る利点はありませんが、テストされたコードに対してより煩わしいため、最初の2つの方法に代わるものではないと思います。最初の方法は非常に危険なので、友好的なアプローチを使用することができます。
決してテストしない私的なものの議論に関するいくつかの言葉。単体テストの利点の1つは、コードの設計を改善する必要がある非常に早い段階に到達することです。これは、単体テストの欠点の1つでもあります。オブジェクト指向は、必要以上に複雑になることがあります。特に、実際のオブジェクトと同じ方法でクラスを設計するルールに従う場合。
次に、ユニットテストのアプローチで強制的にコードを変更する必要があるため、コードを醜いものに変更する必要があります。物理プロセスを制御するために使用される複雑なフレームワークでの作業は、その一例です。多くの場合、プロセスの一部はすでに非常に複雑であるため、そこでコードを物理プロセスにマッピングする必要があります。そのプロセスの依存関係リストが非常に長くなることがあります。これは、プライベートメンバーのテストがうまくいく1つの可能性のある瞬間です。各アプローチの長所と短所をトレードオフする必要があります。
クラスは時々複雑になっています!次に、それらを分割するか、そのまま使用するかを決定する必要があります。2番目の決定がより理にかなっている場合があります。結局のところ、それは常にあなたが達成したい目標の問題です(例えば、完璧な設計、迅速な組み込み時間、低い開発コスト...)。
私の意見
プライベートメンバーにアクセスするための私の決定プロセスは次のようになります。
テストされたコードが変更されるため、友好的なアプローチは好きではありませんが、(最初のアプローチで可能な限り)提供されたものと同じではない可能性がある何かをテストするリスクは、よりクリーンなコードを正当化するものではありません。
ところで、パブリックインターフェイスのみをテストすることも流暢な問題です。私の経験では、パブリックインターフェイスはプライベート実装と同じくらい頻繁に変更されるためです。したがって、パブリックメンバーのテストを減らす利点はありません。
私自身は黄金の解決策を見つけていませんがfriend
、テストフレームワークがそのメソッドにどのように名前を付けているかを知っていれば、プライベートメンバーをテストするために使用できます。私はGoogleテストでプライベートメンバーをテストするために以下を使用します。これは非常にうまく機能しますが、これはハックであり、本番コードでは使用しないことに注意してください。
テストしたいコードのヘッダー(stylesheet.h)には、次のものがあります。
#ifndef TEST_FRIENDS
#define TEST_FRIENDS
#endif
class Stylesheet {
TEST_FRIENDS;
public:
// ...
private:
// ...
};
そして私が持っているテストでは:
#include <gtest/gtest.h>
#define TEST_FRIENDS \
friend class StylesheetTest_ParseSingleClause_Test; \
friend class StylesheetTest_ParseMultipleClauses_Test;
#include "stylesheet.h"
TEST(StylesheetTest, ParseSingleClause) {
// can use private members of class Stylesheet here.
}
プライベートメンバーにアクセスする新しいテストを追加する場合は、常にTEST_FRIENDSに新しい行を追加します。この手法の利点は、テストされていないときは効果がない#defineをいくつか追加するだけなので、テストされたコードでかなり目立たないことです。欠点は、テストでは少し冗長になることです。
なぜあなたがこれをしたいのかについて一言。もちろん、理想的には、明確に定義された責任を持つ小さなクラスがあり、クラスには簡単にテスト可能なインターフェイスがあります。ただし、実際には、それは必ずしも簡単ではありません。ライブラリを作成している場合、テストが必要かどうかではなく、ライブラリの利用者が使用できるようにするもの(パブリックAPI)によって何が決定されますかprivate
。public
変更される可能性が非常に低く、テストする必要がある不変条件を使用できますが、APIの利用者には関係ありません。次に、APIのブラックボックステストでは不十分です。また、バグに遭遇し、リグレッションを防ぐために追加のテストを作成する場合は、テストが必要になることがありますprivate
。
Sometimes, it is required to test private methods. Testing can be done by adding FRIEND_TEST to the class.
// Production code
// prod.h
#include "gtest/gtest_prod.h"
...
class ProdCode
{
private:
FRIEND_TEST(ProdTest, IsFooReturnZero);
int Foo(void* x);
};
//Test.cpp
// TestCode
...
TEST(ProdTest, IsFooReturnZero)
{
ProdCode ProdObj;
EXPECT_EQ(0, ProdObj.Foo(NULL)); //Testing private member function Foo()
}
Adding more info, since many are not aware of gtest features.
This is from gtest/gtest_prod.h
https://github.com/google/googletest/blob/master/googletest/include/gtest/gtest_prod.h
// Copyright 2006, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Google C++ Testing and Mocking Framework definitions useful in production code.
// GOOGLETEST_CM0003 DO NOT DELETE
#ifndef GTEST_INCLUDE_GTEST_GTEST_PROD_H_
#define GTEST_INCLUDE_GTEST_GTEST_PROD_H_
// When you need to test the private or protected members of a class,
// use the FRIEND_TEST macro to declare your tests as friends of the
// class. For example:
//
// class MyClass {
// private:
// void PrivateMethod();
// FRIEND_TEST(MyClassTest, PrivateMethodWorks);
// };
//
// class MyClassTest : public testing::Test {
// // ...
// };
//
// TEST_F(MyClassTest, PrivateMethodWorks) {
// // Can call MyClass::PrivateMethod() here.
// }
//
// Note: The test class must be in the same namespace as the class being tested.
// For example, putting MyClassTest in an anonymous namespace will not work.
#define FRIEND_TEST(test_case_name, test_name)\
friend class test_case_name##_##test_name##_Test
#endif // GTEST_INCLUDE_GTEST_GTEST_PROD_H_
プライベートメンバーをテストしたいという願望はデザインの匂いであり、一般的に、クラスの中に閉じ込められたクラスが外に出るのに苦労していることを示しています。クラスのすべての機能は、パブリックメソッドを介して実行可能である必要があります。公にアクセスできない機能は実際には存在しません。
プライベートメソッドが缶で言うことを実行することをテストする必要があることを理解するには、いくつかのアプローチがあります。フレンドクラスはこれらの中で最悪です。それらは、一応の脆弱性である方法で、テストをテスト対象のクラスの実装に結び付けます。依存性注入の方がやや優れています。テストがモックアップバージョンを提供できるプライベートメソッドの依存性クラス属性を作成して、パブリックインターフェイスを介したプライベートメソッドのテストを可能にします。プライベートメソッドの動作をパブリックインターフェイスとしてカプセル化するクラスを抽出してから、通常どおりに新しいクラスをテストするのが最善です。
詳細については、CleanCodeを参照してください。
プライベートメソッドのテストの適切性に関するコメントにもかかわらず、本当に必要だとしましょう...これは、たとえば、レガシーコードをより適切なものにリファクタリングする前に操作する場合によくあります。これが私が使用したパターンです:
// In testable.hpp:
#if defined UNIT_TESTING
# define ACCESSIBLE_FROM_TESTS : public
# define CONCRETE virtual
#else
# define ACCESSIBLE_FROM_TESTS
# define CONCRETE
#endif
次に、コード内で:
#include "testable.hpp"
class MyClass {
...
private ACCESSIBLE_FROM_TESTS:
int someTestablePrivateMethod(int param);
private:
// Stuff we don't want the unit tests to see...
int someNonTestablePrivateMethod();
class Impl;
boost::scoped_ptr<Impl> _impl;
}
テストフレンドを定義するよりも優れていますか?代替案よりも冗長ではないようで、ヘッダー内で何が起こっているのかが明確です。どちらのソリューションもセキュリティとは何の関係もありません。メソッドやメンバーについて本当に心配している場合は、これらを不透明な実装の中に隠して、おそらく他の保護機能を使用する必要があります。
#defineを使用したC++の簡単な解決策があります。「ClassUnderTest」のインクルードを次のようにラップするだけです。
#define protected public
#define private public
#include <ClassUnderTest.hpp>
#undef protected
#undef private
[クレジットはこの記事とRonFoxに行きます][1]
元のプロジェクトで何も変更しないように、unit-testのMakefileに-Dprivate=publicオプションを追加することをお勧めします