1

I have a function that implements a state machine, test_hw(), i e. First state is idle, and it has not calling any mocked function inside. But this machine has 6 states more. The idea is that machine goes from first state to the last sequently, when certainly condition specially, timer result conditions, occures. In the test I have only one mocked function that returns a time value. But this function must be called at every state as a part of condition to return the next state value. It also will be called inside of some states implementation.

First I tried to test every state as a test case. After run it, too much fails the test have found. I started to think that testing states machine will be a special testing case. I was following a TDD book but it doesn't talk about testing machine states.

The second thing I tried was testing all the test_hw machine as a only test case. But it failed again because test intercept more than 6 calls from source code and the test has only 6 (one call to this mocked function per state in test code). I don't understand why source code (production code) call it more than one time per state. But this is what happens. I guess due this is not the correct policy for unit testing a state machine.

I will share with you a reduced state machine based on mine one:

 eError TestHW()
 {
   static unsigned char last_state = FMINUS_DONE; //IDLE state
   ......... //more needed var declaration

  if (FLAG_busy==OFF)
  {
      if (last_state==FMINUS_DONE)          /*IDLE */
      {
          ...... //some assignations            
          timerfcentral=CaptureTimer(); //timer function to be mocked in test mode
          last_state= FCENTRAL_COUNT;

      }

     if ((CaptureTimer()-timerfcentral>=STABLISH_TIME) && (last_state==FCENTRAL_COUNT)) //timer function call to be mocked in test mode
     {    //next state
          last_state =FCENTRAL_DONE;
          ......//some assignments
     }
     else if (last_state ==FCENTRAL_DONE)
     {    //next state

          timerfplus=CaptureTimer(); //timer function to be mocked in test mode 
          last_state= FPLUS_COUNT;
          .....//some assignments
     }
     else if ((last_state ==FPLUS_COUNT) && (CaptureTimer()-timerfminus>=STABLISH_TIME)) //timer function to be mocked in test mode 
    {
          .....//some assignments
          last_state = FMINUS_DONE;
    }

   return last_state;

   }
   else     //FLAG_busy=1
     {
           last_state= FMINUS_DONE; //machine state loop is closed
           return last_state; /* busy system */
     }

}

test code:

 TEST(TestHW,TestHW_main)
 {

   unsigned char error_val;
   FLAG_busy =1;

   error_val=TestHW();
   CHECK_EQUAL(error_val,FMINUS_DONE);  //check busy state
   mock().enable();

   FLAG_busy =0;

   mock().expectOneCall("CaptureTimer").andReturnValue(1000);
   error_val=TestHW();
   CHECK_EQUAL(error_val,FCENTRAL_COUNT)    /*check state idle*/

   mock().expectOneCall("CapturaTimer").andReturnValue(15000);  
   error_val=TestHW();
   CHECK_EQUAL(error_val,FCENTRAL_DONE);//check first state

   mock().expectOneCall("CaptureTimer").andReturnValue(15000);
   error_val=TestHW();
   CHECK_EQUAL(error_val,FPLUS_COUNT);//check second state

   mock().expectOneCall("CaptureTimer").andReturnValue(30000);
   error_val=TestHW();
   CHECK_EQUAL(error_val,FMINUS_DONE);//check last state

   mock().disable();

}

mocked timer function implementation in Mock.c file:

unsigned long CaptureTimer(void)
{
     mock().actualCall("CaptureTimer");
     return mock().unsignedIntReturnValue();
}

As I said before, this test fails due to "second not expected mocked call is produced".

What could be a correct strategy? --> if no test cases for every state, nor only one test entity, which one? Hence, is there any suggestion about the way of implementing a test code for the state machine shown above?

Could someone improve this test code?

Suvi_Eu
  • 255
  • 1
  • 3
  • 16
  • Comment unrelated with your question, but mixing languages in your code is not a good idea in term of readability. You should pick one, and English is usually the best candidate. – Tim Mar 07 '17 at 12:06
  • 2
    The industry standard way to design finite state machines is by using an enum together with a function pointer jump table. [Example](http://electronics.stackexchange.com/questions/95569/best-practice-to-keep-main-in-embedded-systems/96415#96415). It might also be a good idea to centralize all error checks in the caller, and also if possible, to centralize the next state decision making. When you have gotten the core functionality straightened out, then you can worry about TDD fluff. – Lundin Mar 07 '17 at 12:22
  • @Lundin when you say "to centralize all error checks in the caller" are you talking about the test code or state machine code? And what do you mean with "to centralize the next state decision"? – Suvi_Eu Mar 07 '17 at 12:46
  • Something like `result = state_machine[state](params); if(result != GOOD) { error_handler(result); }`. Similarly, you can sometimes let the caller decide which should be the next state to execute, based on the outcome of the previous state. This is best practice, since you don't have to hunt down state changes all over the code, but sometimes not possible to achieve. – Lundin Mar 07 '17 at 12:52
  • Thank you. I will think about it. – Suvi_Eu Mar 07 '17 at 13:23
  • `... else if (last_state == ...` Have you considered a switch ? State machines can often be impemented as `for( ...) { switch(state) { ... } }`, even if the transitions are obtained from tables ( see lex&yacc) – wildplasser Mar 07 '17 at 15:10
  • @wildplasser and how does it affect to the TDD strategy?The state machine works. But my part of work is to test this state machine. – Suvi_Eu Mar 07 '17 at 15:22
  • `static unsigned char last_state = FMINUS_DONE;` Making it static seems like a bad plan, to me. Will only be initialized once, and used once or you'll have to add a dummy transition to reset it. – wildplasser Mar 07 '17 at 15:55

1 Answers1

0

If I understand your code correctly, once FLAG_busy is not 1 anymore the CaptureTimer() function is called at least once and the remaining time twice. Just after your first test the state is at FMINUS_DONE which in the next cycle calls CaptureTimer() twice and that is why you have the error.

I believe you are using cpputest and I don't think you can have a mock called twice inside a single function call, someone can gladly correct me if I am wrong.

Fisho
  • 1
  • 2