在我所涉及的嵌入式编程类型中,运行代码的确定性和透明度受到高度重视。我的意思是透明度,例如,能够查看内存的任意部分并知道存储在那里的变量。因此,正如我确信嵌入式程序员所期望的那样,如果可能的话,应该避免新的,如果无法避免,那么仅限于初始化。
我理解这种需要,但不同意我的同事这样做的方式,也不知道更好的选择。
我们拥有几个全局结构数组和一些全局类。有一个用于互斥锁的结构数组,一个用于信号量,一个用于消息队列(这些是在main中初始化的)。对于每个运行的线程,拥有它的类是一个全局变量。
我遇到的最大问题是单元测试。如何在我想测试#include
的全局变量的类中插入模拟对象?
以下是伪代码的情况:
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( );
}
}
如果我想要更多或更少的任务怎么办? foo1的构造函数中有一组不同的参数?我想我必须有一个单独的bar.h和bar.cpp,这似乎比必要的工作要多得多。
答案 0 :(得分:4)
如果您想首先对此类代码进行单元测试,我建议您阅读Working Effectively With Legacy Code。另请参阅this。
基本上使用链接器插入mock / fake对象和函数应该是最后的手段,但仍然完全有效。
但是,您也可以使用控制反转,如果没有框架,这可以将一些责任推给客户端代码。但它确实有助于测试。例如,测试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',以确保该函数确实根据需要启动线程。不幸的是,您必须依赖原始FSW_taskStart
传递正确参数的事实,但您现在正在测试更多代码。
答案 1 :(得分: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
}
答案 2 :(得分:-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
时手动调用解构