私は、次の構造を使用する ac api に依存しています (関数名は単なる例です)。
getRoot(FolderHandle* out)
getFirstChildFolder(FolderHandle in, FolderHandle* out)
getNextFolder(FolderH in, FolderHandle* out)
getChildFolder(FolderH in, FolderHandle* out)
getProperties(FolderH in, PropertiesH* out)
getChildFolder(FolderH in, FolderH* out)
getName(PropertiesH in, char** out)
getFile(FolderH in, FileH* out)
getNextFile(FileH in, FileH* out)
getProperties(FileH in, PropertiesH* out)
そこで、まず getRoot を呼び出してルートのフォルダー ハンドルを取得します。ルート フォドラーの最初のファイルのハンドルを取得するために、フォルダー ハンドルを渡して getFile() を呼び出します。このレベルで 2 番目以降のファイルを取得するには、getNextFile を呼び出して、前のファイル ハンドルを渡します。
これを、次のように一連の C++ インターフェイスの形式でラップしました。
class IEntry
{
public:
...
virtual IFolder* root() = 0;
};
class IFolder
{
public:
...
typedef Iterator<IFile, FolderH, FileH> FileIterator;
virtual FileIterator filesBegin() const = 0;
virtual FileIterator filesEnd() const = 0;
};
class File
{
public:
...
virtual IProperties* properties() = 0;
};
class Properties
{
public:
...
virtual std::string name() = 0;
};
単体テストでは、IEntry、IFolder、IFile などの Google Mock 実装を使用するだけでよく、これは非常に便利です。また、インターフェイスは c API からの関数を理解しやすく操作しやすい方法で整理します。特定のインターフェイスの実装は、関連付けられたハンドルをラップします。
イテレーターを使用して、getFile や getNextFile などの関数呼び出しを結び付けます。この場合は、フォルダー内のファイルを反復処理します。API にはこのような関数のペアが多数あるため、Iterator というテンプレート クラスを使用して C++ スタイルの反復子を作成します。
私は実際には、通常のポインターではなく、std::shared_ptrs を使用しています。
単体テストの例を次に示します。
std::string a(IEntry& e)
{
std::shared_ptr<IFolder> f = e.root();
return f->properties()->name();
}
TEST (FooTest, a)
{
MockEntry e;
std::shared_ptr<MockFolder> f(new MockFolder());
std::shared_ptr<MockProperties> p(new MockProperties());
EXPECT_CALL(e, root()).WillOnce(testing::Return(f));
EXPECT_CALL(*f, properties()).WillOnce(testing::Return(p));
EXPECT_CALL(*p, name()).WillOnce(testing::Return("Root"));
EXPECT_EQ(a(e), "Root");
}
ただし、イテレータの使用に関しては、さらに複雑になります。この場合に私が使用しているアプローチは次のとおりです。
std::string b(IEntry& e)
{
std::shared_ptr<IFolder> folder = e.root();
IFile::FileIterator i = folder->filesBegin();
if(i!=f->filesEnd())
{
return i->properties()->name();
}
else
{
return "";
}
}
TEST (FooTest, b)
{
MockEntry e;
std::shared_ptr<MockFolder> f(new MockFolder());
loadFileIteratorWithZeroItems(f);
loadFileIteratorEnd(f);
std::shared_ptr<MockProperties> p(new MockProperties());
EXPECT_CALL(e, root()).WillOnce(testing::Return(f));
EXPECT_EQ(b(e), "");
}
テストはelse句をテストしています。残りのコード (1 つのファイルと複数のファイル) をテストする別の 2 つのテストがあります。
関数 loadFileIteratorWithZeroItems は、反復子の内部を操作して、ゼロ項目を反復するようにします。loadFileIteratorEnd は、filesEnd() からの戻り値を設定します。これが loadFileIteratorWithZeroItems です:
void loadFileIteratorWithZeroItems (std::shared_ptr<MockFolder> folder)
{
std::shared_ptr<MockFile> file(new MockFile());
std::shared_ptr<MockFileFactory> factory(new MockFileFactory());
std::shared_ptr<MockFileIterator> internalIterator(new MockFileIterator());
FolderH dummyHandle = {1};
EXPECT_CALL(*internalIterator, getFirst(testing::_,testing::_)).WillOnce(testing::Return(false));
MockFolder::FileIterator iterator = MockFolder::FileIterator(factory,internalIterator,dummyHandle);
EXPECT_CALL(*folder, filesBegin()).WillOnce(testing::Return(iterator));
}
ファクトリは、イテレータが指しているアイテムを作成するために使用されます。これは、単体テストの場合のモック バージョンです。内部反復子は、関数 getFile() と getNextFile() のラッパーであり、インターフェイス getFirst() と getNext() を持つすべてのペアです。
loadFileIteratorWithOneItem と loadFileIteratorWithTwoItems という関数もあります。
上記の関数 b をテストするより良い方法を提案できる人はいますか?
私のデザインは根本的に剥がれていますか?イテレータの実装に問題がありますか?