LebGeeks

A community for technology geeks in Lebanon.

You are not logged in.

#1 August 21 2011

saeidw
Member

[C++] Testable Design

This is a long post, you might want to grab a cup of coffee before reading.

Recently I've been exposed to Miško Hevery's blog and his Guide to Writing Testable Code.

Specifically, the following guideline: Do not create collaborators in your constructor, but pass them in.
This is meant to make it easy to pass in fake objects to test your class. It's just basic dependency injection, and it helps make code reusable.

With languages like C# and Java, this is usually not a problem since objects are cleaned up by the garbage collector when they're no longer in use. In C++, we have an issue.

[ A ] Who is responsible for destroying the collaborators?

There are three options:

1. Whoever created the collaborators.
2. The object that depends on the collaborators.
3. Use smart pointers (either strict ownership or shared ownership).

Assume we don't have access to smart pointers, so I'll discard option 3 for now.

With option 1, whoever created the collaborators will have to exist for the duration of the lifetime of the object that depends on them. If the creator (let's say it's a Factory) gets destroyed while the Dependent object still exists, the Dependent will no longer have a valid handle to the collaborators.

With option 2, the Factory will have to trust that the Dependent object will properly dispose of the collaborators.

Additionally, C++ allows for stack-allocated and heap-allocated objects. We have to take that into account.

[ B ] RAII
Languages like C++ handle resource management using the RAII idiom. It stands for Resource Acquisition is Initialization. Essentially, we release all acquired resources in the destructor of an object, ensuring that when the object is destroyed, it closes open files, connections, etc.

[ C ] A Solution
I decided to go with a variation on option 2. Let the collaborators be passed in to the Dependent. The Dependent object will then store copies of the collaborators and ensure that they're tied to its own scope. In this way, an object can be passed out of the scope of the factory and still maintain valid handles to all its collaborators.

Basically, the Dependent object owns its collaborators and ensures they get destroyed.

Cost: we'll be making copies of things.

[ D ] Example

Consider an Eater class that eats something edible.

class Eater
{
    public:
    explicit Eater(const IEdible& edible);
    ~Eater();

    void consumeEdible();

    private:
    IEdible* myEdible;
};

Where IEdible is the following interface, it's implemented by many foods, such as Apple:

class IEdible
{
    public:
    virtual ~IEdible() { }

    virtual IEdible* clone() const = 0;

    virtual void eat() const = 0;

    private:
    IEdible& operator=(const IEdible&);
};

I want to be able to write the following code:

int main (int argc, char **argv)
{
    Apple myApple;
    Eater myEater(myApple);

    myEater.consumeEdible();

    return 0;
}

Here, the Factory is my main function. It creates the collaborator myApple and passes it to the Eater. From that point on, myEater owns a copy of myApple that is independent of the original copy. In this way, it is not bound to the lifetime of the original.

Here's how this is accomplished:

Eater::Eater(const IEdible& edible)
    : myEdible(edible.clone())
{ }

Eater::~Eater()
{
    delete myEdible;
}

The key to all this is the clone function. It is an implementation of the Virtual Copy Constructor Idiom. It's defined in the IEdible interface, so each edible food must have a clone function. This function will simply return a copy of the edible food. Here's the implementation for Apple:

Apple* Apple::clone() const
{
    return new Apple(*this);
}

This is just a call to Apple's copy constructor, which handles the heavy lifting.

[ E ] Thoughts
For me this mostly solves the problem. The Eater depends on an interface, so different edible objects can be substituted into it. The interface defines the mechanism through which ownership is granted to the object. Using RAII, the object manages the lifetime of its collaborators and destroys them deterministically (when it gets destroyed). This is all transparent from the outside, as demonstrated in main.

Please forgive me for the wall of text. Let me know if this is useful. I'd like to read questions and comments. This was just the result of a bit of experimenting and I'm interested in knowing how it can be improved, or if you have some other cool solution.

Offline

#2 August 21 2011

arithma
Member

Re: [C++] Testable Design

What happens when you have a fruit basket group (which contains pointers to items) that is to be eaten by the eater. Please try to apply the same design principles here.

Please note the following:
- To assess a design you should try to break it rather than giving it the "it just works" case scenario.
- You ought to mix stack allocated items and heap allocated ones in order to fully understand what your system needs in terms of memory management.
- When you're going fully fledged manual memory management, stack allocated variables should become a thing of the past unless those variables should live only in the function scope they are declared in (which is their purpose anyway)
- In your above example, you have a skewed scope since everything's lifetime is scoped within the factory's lifetime. Try factoring out a factory to get more valid results and better steer your design

Hope it helps

Offline

#3 August 21 2011

arithma
Member

Re: [C++] Testable Design

Let me try to clear up what am thinking:
- You are basing a big deal of your application design based on memory management. It should be the reverse. Memory management should be based on your application design.

If Apple can only be consumed by a single eater, then perhaps you should think of passing memory management (ownership) to the one eating it. If you look at it from another perspective, once the apple factory creates an apple it forgets about it. Additionally, creating an apple on stack and assigning ownership to any object is a big no no. If an apple should never stand on its own without having an owner (doesn't really make sense in real life) then you can even pass in the owner in the constructor.

Think now of a cake. Multiple eaters will dig into it. That's shared ownership for you. Each cake may have a list of owners. On creation, at least one eater must be known. Once the list drops down to zero, the cake is disposed into the garbage (tongue in cheek).

Problems will arise with this very basic design when multiple types of owners can exist (a fruit basket can hold an apple, an eater can eat the apple, a car can smash the apple under its tire). So you'd refactor in a two fold step. First an apple owner. Then as an object owner. That's of course us going in the other direction of creating shared ownership counters on objects (by reference). Continuing in that direction we can aim for a memory scope. A memory scope contains disposable garbage collectible items. A memory scope itself is garbage collectible. With this design you can just have absolute manual memory management while assigning objects to pools of memory scope. When a task is done with its memory scope, it destroys everything downward recursively. This can be a powerful design, and I believe it's close to what Qt does.

Offline

#4 December 18 2011

saeidw
Member

Re: [C++] Testable Design

My last post tried to address the issue of storing pointers to objects in other objects without having to worry about what gets destroyed first, and who's going to be destroying it.

[ A ] Problems

Thanks to arithma's input and some experimentation, it is clear that the solution I presented has shortcomings. By depending on a clone() function to make copies of the object being passed, I exclude a large amount of functionality from my program.

Consider the following Node class in a hypothetical linked list implementation:

class Node
{
    public:
    explicit Node(const int data, const Node* next);
    Node* clone() const;
    ~Node();
    Node* getNextAddr();

    private:
    int _data;
    Node* _next;
};

What would happen if we used the previous method to construct a linked list out of two Nodes?

int main(int argc, char **argv)
{
    Node na(1, 0);  // The next element is the end of the list
    Node nb(2, &na);

    cout << "na is really at: " << &na << endl;
    cout << "na appears to be at: " << nb.getNextAddr() << endl;

    return 0;
}

Running the above code, you will notice that to nb, the next element in the list appears to be at an address that is different from the address of na. This is because nb stores its own copy of the next node instead of simply pointing to it.

[ B ] Revisiting Ownership

You might think that such a problem is consistent with what we were trying to achieve. After all, the relationship between nodes in a linked list isn't really ownership.

However, we still need to manage those nodes somehow. What happens when you destroy a node in the middle of the chain? What happens when you destroy the first node in the chain? These are all problems that cannot be resolved with our previous method of enforcing ownership. We've only managed to solve the problem for a limited number of cases.

In my next post I'll discuss a different way of thinking about ownership, and try to demonstrate that it covers a wider range of situations where we might need to pass an object to another object, and not worry about having to keep track of it. If you would like to try the code in this post, please let me know and I can send you an implementation.

Last edited by saeidw (December 18 2011)

Offline

#5 December 19 2011

xterm
Moderator

Re: [C++] Testable Design

Edit: Scratch what was said below, you obviously mean fake not mock objects.

saeidw wrote:

Specifically, the following guideline: Do not create collaborators in your constructor, but pass them in.
(1) This is meant to make it easy to pass in fake objects to test your class. It's just basic dependency injection, and it helps make code reusable.

(2) With languages like C# and Java, this is usually not a problem since objects are cleaned up by the garbage collector when they're no longer in use.

Interesting topic, I just wanted to comment on the bold part.

Is this intended to make a relation between passing fake objects and the availability of a garbage collector? Because honestly there's no relation whatsoever. I understand that it is not your intent to imply that C#/Java code is implicity testable but to form a relation between fake objects and those languages is simply incorrect.

I didn't spend too much time reading the posts, I just skimmed over it but it seems what you're trying to do is implement mocking in C++.

Have a look at googlemock.

Here's the Getting Started.

P.S.: My knowledge of C++ is weak at best, so if this is not what you're trying to do, please disregard it.

P.P.S.: I'm not implying that you use googlemock, just have a look to get some ideas.

Offline

#6 December 19 2011

saeidw
Member

Re: [C++] Testable Design

Thanks for the comments, xterm. I think you're right. When I initially wrote that post I was confusing the concepts of ownership and testability and I could have been much clearer about where I was going with it. I thought it would be easier to test objects if they handled their own memory, which is obviously not true. I'm now trying to tear that post apart with logic and evolve a better style out of it.

I had looked into googlemock and other frameworks, but I felt like I was working against the language, instead of going with its natural flow. The main idea I took away is that I should have a consistent method of expressing ownership, and that I should minimize the amount of stuff that goes on in functions and classes that is not directly related to their purpose. I'm going to write a follow-up with a different way of doing things once I finish my example code.

Offline

Board footer