7

在我进入的嵌入式编程类型中,运行代码的确定性和透明度受到高度重视。例如,我所说的透明度是指能够查看内存的任意部分并知道那里存储了哪些变量。因此,正如我确信嵌入式程序员所期望的那样,如果可能的话,应该避免使用 new,如果无法避免,那么就仅限于初始化。

我理解这样做的必要性,但不同意我的同事这样做的方式,我也不知道更好的选择。

我们拥有的是几个全局结构数组和一些全局类。有一组结构用于互斥体,一组用于信号量,一组用于消息队列(这些在 main 中初始化)。对于每个运行的线程,拥有它的类是一个全局变量。

我遇到的最大问题是单元测试。#include当我想测试我不想测试的全局变量的类时,如何插入模拟对象?

这是伪代码中的情况:

foo.h

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

酒吧.h

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

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

酒吧.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(  );
    }
}

如果我想要更多或更少的任务怎么办?foo1 的构造函数中的一组不同的参数?我想我必须有一个单独的 bar.h 和 bar.cpp,这似乎比必要的工作要多得多。

4

3 回答 3

4

如果您想先对此类代码进行单元测试,我建议您阅读有效地使用旧代码另请参阅

基本上使用链接器插入模拟/假对象和函数应该是最后的手段,但仍然完全有效。

但是,您也可以使用控制反转,如果没有框架,这可能会将一些责任推给客户端代码。但它确实有助于测试。例如测试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);
}

现在您可以将您的线程的模拟版本传递给“FSW_taskStart”,以确保该函数确实根据需要启动线程。不幸的是,您必须依靠 originalFSW_taskStart 传递正确参数的事实,但您现在正在测试更多代码。

于 2009-08-13T21:58:57.663 回答
3

依赖注入对您的情况有帮助吗?这可以摆脱所有全局变量,并允许在单元测试中轻松替换依赖项。

每个线程主函数都被传递一个包含依赖项(驱动程序、邮箱等)的映射,并将它们存储在将使用它们的类中(而不是访问一些全局变量)。

对于每个环境(目标、模拟器、单元测试......),您创建一个“配置”函数,该函数创建所有需要的对象、驱动程序和所有线程,为线程提供它们的依赖项列表。例如,目标配置可以创建一个 USB 驱动程序并将其注入某个通信线程,而通信单元测试配置可以创建一个测试控制的存根 USB 驱动程序。

如果您绝对需要重要变量的这种“透明度”,请为它们创建类,将它们保存在已知地址,并在需要的地方注入这些类。

它比静态对象列表要多得多,但灵活性非常好,尤其是当您遇到一些棘手的集成问题并想要交换组件进行测试时。

大致:

// 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
}
于 2009-08-14T09:12:35.023 回答
-1

您可以使用 malloc 分配内存,然后让 new 运算符在该位置创建对象

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();

因此您可以 malloc 所有内存,然后根据需要分配它。注意:确保手动调用解构,因为调用 delete 时不会

于 2009-08-14T08:08:02.640 回答