views:

394

answers:

3

In the type of embedded programming I'm getting into, determinism and transparency of the running code are highly valued. What I mean by transparency is, for instance, being able to look at arbitrary sections of memory and know what variable is stored there. So, as I'm sure embedded programmers expect, new is to be avoided if at all possible, and if it can't be avoided, then limited to initialization.

I understand the need for this, but don't agree with the way my coworkers have gone about doing this, nor do I know a better alternative.

What we have are several global arrays of structures, and some global classes. There is one array of structs for mutexes, one for semaphores, and one for message queues (these are initialized in main). For each thread that runs, the class that owns it is a global variable.

The biggest problem I have with this is in unit testing. How can I insert a mock object when the class I want to test #includes global variables that I don't?

Here's the situation in pseudo-code:

foo.h

#include "Task.h"
class Foo : Task {
public:
  Foo(int n);
  ~Foo();
  doStuff();
private:
  // copy and assignment operators here
}

bar.h

#include <pthread.h>
#include "Task.h"

enum threadIndex { THREAD1 THREAD2 NUM_THREADS };
struct tThreadConfig {
  char      *name,
  Task      *taskptr,
  pthread_t  threadId,
  ...
};
void startTasks();

bar.cpp

#include "Foo.h"

Foo foo1(42);
Foo foo2(1337);
Task task(7331);

tThreadConfig threadConfig[NUM_THREADS] = {
  { "Foo 1", &foo1, 0, ... },
  { "Foo 2", &foo2, 0, ... },
  { "Task",  &task, 0, ... }
};

void FSW_taskStart() {
    for (int i = 0; i < NUMBER_OF_TASKS; i++) {
        threadConfig[i].taskptr->createThread(  );
    }
}

What if I want more or less tasks? A different set of arguments in the constructor of foo1? I think I would have to have a separate bar.h and bar.cpp, which seems like a lot more work than necessary.

+4  A: 

If you want to unit test such code first I would recommend reading Working Effectively With Legacy Code Also see this.

Basically using the linker to insert mock/fake objects and functions should be a last resort but is still perfectly valid.

However you can also use inversion of control, without a framework this can push some responsibility to the client code. But it really helps testing. For instance to test FSW_taskStart()

tThreadConfig threadConfig[NUM_THREADS] = {
  { "Foo 1", %foo1, 0, ... },
  { "Foo 2", %foo2, 0, ... },
  { "Task",  %task, 0, ... }
};

void FSW_taskStart(tThreadConfig configs[], size_t len) {
    for (int i = 0; i < len; i++) {
        configs[i].taskptr->createThread(  );
    }
}

void FSW_taskStart() {
    FSW_taskStart(tThreadConfig, NUM_THREADS);
}

void testFSW_taskStart() {
    MockTask foo1, foo2, foo3;
    tThreadConfig mocks[3] = {
          { "Foo 1", &foo1, 0, ... },
          { "Foo 2", &foo2, 0, ... },
          { "Task",  &foo3, 0, ... }
        };
    FSW_taskStart(mocks, 3);
    assert(foo1.started);
    assert(foo2.started);
    assert(foo3.started);
}

Now you can can can pass mock version of you're threads to 'FSW_taskStart' to ensure that the function does in fact start the threads as required. Unfortunatly you have to rely on the fact that original FSW_taskStart passes the correct arguments but you are now testing a lot more of your code.

iain
Well, I'm lucky enough that it's not legacy code yet, which is why I want to get things right now. I already had IOC in mind, but seeing an example helped me believe it's possible. This way, the production code can use the global variables, so you can predict where they'll be in memory, but the test code can simply not include bar.cpp. I'm still not sure though. The way you've proposed, I won't need to change any .h files, just include different .cpp files in the unit test's project?
drhorrible
+1. DIP and IOC are good architecture pattern particularly for .. .testing with mock object. If you can't modify you functions signature, you can do with some indirection. Instead of passing your context object you can call a function that returns it. This function can use IOC and return real context objects or mock object depending on its initialization ...
neuro
@drhorrible, the name of the book might be a little misleading ;-) His definition of legacy code is code without unit test. It is mainly collection of techniques for unit testing in different hard scenarios.
iain
Just got a copy of the book, and so far it's blowing my mind. Totally changes the way I think about working with untested code, as well as how to start a new project properly. Thanks!
drhorrible
I'm glad you like it, it has some great tips for testing code that doesn't look testable.
iain
A: 

You can allocate memory using malloc and then get the new operator to make the object at that position

void* mem = malloc(3*sizeof(SomeClass));
SomeClass *a = new(mem) SomeClass();
mem += sizeof(SomeClass);
SomeClass *b = new(mem) SomeClass();
mem += sizeof(SomeClass);
SomeClass *c = new(mem) SomeClass();

so you can malloc all the memory then allocate it as you wish. Note: make sure you call the deconstruction manually as it wont when you call delete

Lodle
-1, malloc is just as banned as new in these environments. This adds pain without gain.
MSalters
So mallocing one block of memory at the start is banned? Seems to be a silly rule.
Lodle
+3  A: 

Would dependency injection help in your situation? This could get rid of all global variables and allow for easy substitution of dependencies in your unit tests.

Each thread main function is passed a map containing dependencies (drivers, mailboxes, etc.) and stores them in the classes that will use them (instead of accessing some global variable).

For each environment (target, simulator, unit test...) you create one "configuration" function that creates all needed objects, drivers and all threads, giving threads their list of dependencies. For example, the target configuration could create a USB driver and inject it into some comms thread, while the comms unit test configuration could create a stub USB driver that the tests controls.

If you absolutely need this "transparency" for important variable, create classes for them, which will hold them at a known address, and inject these classes where needed.

It's quite a bit more work than static lists of objects, but the flexibility is fantastic, especially when you hit some tricky integration issues and want to swap components for testing.

Roughly:

// Config specific to one target.
void configure_for_target_blah(System_config& cfg)
{   // create drivers
    cfg.drivers.push_back("USB", new USB_driver(...))
    // create threads
    Thread_cfg t;
    t.main = comms_main; // main function for that thread
    t.drivers += "USB"; // List of driver names to pass as dependencies
    cfg.threads += t;
}

// Main function for the comms thread.
void comms_main(Thread_config& cfg)
{
    USB_driver* usb = cfg.get_driver("USB");
    // check for null, then store it and use it...
}

// Same main for all configs.
int main()
{
    System_config& cfg;
    configure_for_target_blah(cfg);
    //for each cfg.drivers
    //    initialise driver
    //for each cfg.threads
    //    create_thread with the given main, and pass a Thread_config with dependencies
}
squelart