2009-08-13 2 views
7

실행중인 코드의 결정론과 투명성을 높이려는 임베디드 프로그래밍 유형이 있습니다. 제가 투명성을 의미한다는 것은 예를 들어 메모리의 임의 섹션을보고 거기에 어떤 변수가 저장되어 있는지를 알 수 있다는 것입니다. 따라서 임베디드 프로그래머가 기대하는대로 new는 가능한 한 피해야합니다. 피할 수없는 경우 초기화로 제한됩니다.임베디드 프로그래밍에서 전역 변수 사용하지 않기

이 문제에 대한 필요성을 이해하고 있지만 동료가이 작업을 수행하는 방식에 동의하지 않으며 더 나은 대안을 알지 못합니다.

우리는 구조의 전역 배열과 일부 글로벌 클래스를 가지고 있습니다. mutex를위한 하나의 구조체 배열, 하나는 세마포어를위한 배열, 다른 하나는 메시지 대기열을위한 구조체 (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

나는 당신이 '& foo1은'이 아닌 '%의 foo1은'(모듈러스 연산자)를 의미하는 것 같은데요? – DaveR

+0

감사합니다. 그게 내가 복사/붙여 넣기 대신 재 작성을 위해 얻는 것입니다. –

답변

4

이러한 단위 코드를 먼저 테스트하고 싶다면 Working Effectively With Legacy Code을 읽어 보시기 바랍니다. this도 참조하십시오.

기본적으로 모의 객체/함수를 삽입하기 위해 링커를 사용하는 것이 최후의 수단이어야하지만 여전히 완벽하게 유효합니다.

그러나 프레임 워크없이 컨트롤의 반전을 사용하면 클라이언트 코드에 책임감을 부여 할 수 있습니다. 하지만 실제로 테스트하는 데 도움이됩니다. 인스턴스가 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이 올바른 인수를 전달하지만 현재 코드를 더 많이 테스트하고 있다는 사실에 의존해야합니다.

+0

글쎄요, 나는 아직 유산 코드가 아니기 때문에 운이 좋았습니다. 그래서 나는 지금 당장 물건을 얻고 싶습니다. 나는 이미 IOC를 염두에두고 있었지만 모범을 보았을 때 그것이 가능하다고 믿을 수있었습니다. 이렇게하면 프로덕션 코드가 전역 변수를 사용할 수 있으므로 메모리에있는 위치를 예측할 수 있지만 테스트 코드에는 bar.cpp가 포함될 수 없습니다. 나는 아직도 확실하지 않다. 제안한 방식에 따라 .h 파일을 변경할 필요가 없으며 단위 테스트의 프로젝트에 다른 .cpp 파일을 포함하면됩니까? –

+1

+1. DIP와 IOC는 특히 mock 객체를 이용한 좋은 아키텍처 패턴이다. 함수 서명을 수정할 수없는 경우 간접적 인 작업을 수행 할 수 있습니다. 컨텍스트 개체를 전달하는 대신 반환하는 함수를 호출 할 수 있습니다. 이 함수는 IOC를 사용하여 실제 컨텍스트 객체를 반환하거나 초기화에 따라 객체를 조롱 할 수 있습니다. – neuro

+1

@drhorrible, 책의 이름이 약간 잘못 될 수 있습니다 ;-) 그의 레거시 코드 정의는 단위 테스트가없는 코드입니다. 그것은 주로 다양한 하드 시나리오에서 단위 테스트를위한 기법 모음입니다. – iain

-1

는 당신의 malloc를 사용하여 메모리를 할당 한 다음 새 운영자가

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을 할 수있는 위치에 객체를 만들 수 있습니다. 참고 : 삭제 전화를 할 때 수동으로 해체를하지 말아야합니다.

+0

-1, malloc은 이러한 환경에서 새로운 것과 마찬가지로 금지됩니다. 이것은 이득없이 고통을 더합니다. – MSalters

+0

시작시 한 블록의 메모리를 mallocing 할 수 있습니까? 어리석은 규칙처럼 보입니다. – Lodle

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 
}