1

私は、次の構造を使用する 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 をテストするより良い方法を提案できる人はいますか?

私のデザインは根本的に剥がれていますか?イテレータの実装に問題がありますか?

4

1 に答える 1

2

あなたは実際にモッキングを最大限に活用していないようです。この場合のテストbには、単純に次のテストケースを使用します。

TEST (FooTest, b) 
{
    MockEntry e;
    MockFolder f;
    IFile::FileIterator it; // I don't know how you construct one,
                            // just make sure that it == it

    ON_CALL(e, root()).WillByDefault(Return(&f));
    ON_CALL(f, filesBegin()).WillByDefault(Return(it));
    ON_CALL(f, filesEnd()).WillByDefault(Return(it));

    EXPECT_CALL(e, root()).Times(1);
    EXPECT_CALL(f, filesBegin()).Times(1);
    EXPECT_CALL(f, filesEnd()).Times(1);

    EXPECT_EQ(b(e), "");
}

私の意見では、これはモックを使用した最もエレガントなアプローチです。何が起こっているかは明らかであり、動作をセットアップするために他のコードに依存していません。これは、関数の else 節だけをテストしますb

于 2013-01-14T12:24:30.523 に答える