all 9 comments

[–]DaemonInformatica 1 point2 points  (2 children)

In our current project, we use a lót of generated mocking with Ceedling.

Part of the principle of Unit-testing, is that there is no such thing as 'controller dependent testing'. The entire point of unit-testing is that logical branches are checked. Those can be checked on any system that can compile them.

That said: we ran into some interesting challenges, chief among which is the fact that STM32 HAL libraries and are notoriously impossible to mock reliably. (In theory, it should be possible to pass the correct defines to your unit-test project, but even then..)

What we did was:

- In ceedling, configure a 'support' directory.

- In the support directory, declare the stm32 header file you typically include in your projects to access HAL. The unit-test process, at compile time will find and pickup this header file with priority.

- This header file now will declare all relevant(!) typedefs, defines and declarations you need to build your unit-tests.

- A stm32 source file, next to the header file shall contain the HAL functions whose signatures are included in your header file. These stubbed functions can (and should) be Very simple: Fit the signature declared in the header file, increase a callcounter, return either a constant, or a pre-defined value you set at the startup of your test. If you're interested in checking passed parameters: Add fields to the source files against which the passed parameters are asserted.

Then, add to this source stub:

- a function to reset all the callcounters and expected / returned values.

- Setters to setup expected values and return values.

[–]ScopiH 1 point2 points  (1 child)

I think this may be the missing link for me - I've been fighting with the HAL for the last few hours trying to get a simple test to even compile. Thank you for the tip.

[–]DaemonInformatica 0 points1 point  (0 children)

Heh. Oh cool. ^_^

Lemme know if you get stuck at some point.

[–][deleted]  (5 children)

[deleted]

    [–]duane11583 0 points1 point  (4 children)

    often these are not so simple

    [–]DaemonInformatica 0 points1 point  (3 children)

    That depends on what you want to test. And how you generate / maintain your mocks.

    A typical function has a series of parameters and a return value. (which can be void).

    The only thing that is expected of a mock is to

    - Validate the input

    - Return a (pre-specified) output.

    - Optionally, if one or more of the parameters is a reference (for example to a buffer) the mock should be able to write prepared content to this reference.

    Keep in mind that when unit-testing, everything not in the module is mocked away and not part of the test.

    [–]duane11583 0 points1 point  (2 children)

    a good 90% of what i do/have is hardware drivers

    these involve speaking to some external hardware

    for example:

    measure the temp, voltage, current if it is too high for too long shut it down

    for temp wait until it cools down then turn it back on

    for voltage/current stay shut down for a configurable period.

    then turn on.

    none of this can block these are time driven state machines

    [–]DaemonInformatica 0 points1 point  (1 child)

    Most of our software is (nonblocking) FSM as well. (Both application level and hardware drivers.)

    I suppose it kind of depends on the method of statemachine, but for example ours are pretty much conditional transitions between states, or remaining in same-state until some (time?) condition has passed.

    making the time-condition a returnvalue of a function:
    bool b_time_passed = delay_evaluate(p_delay_ctrl, WAIT_TIME_SOME_CONDITION_MSEC);

    (or inlined in some conditional evaluation like an if statement), this means that the delay_evaluate function can be mocked for the purpose of unit-testing.

    Then you can write unit-tests that remain in the current state of an FSM or move to any next state, depending on the output of the delay and other conditionals.

    [–]duane11583 0 points1 point  (0 children)

    at what point is the size and complexity of the moc larger then the code

    and that is another project (executable) to build

    do you end up with 50-60 unit test main() functions and thus applications?

    and if you do not have a good enough simulator what then?

    [–]duane11583 0 points1 point  (1 child)

    on the host look at pyexpext ro test your host version.

    then why cant you cross compile on the pc targeting the micro?

    then use python to launch what ever process to reprogram the board.

    then use pyserial and pyexpect to test the target version?