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