Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

Have you ever written a test which mostly consists of the same block of code over and over again with different parameters?

// paraphrased from projects/charger/test/test_control_pilot.c
void test_various_duty_cycles(void) {
  uint16_t answer = 0;
  uint16_t actual = 0;

  // case 1
  s_duty_cycle = 10;
  answer = 0;
  actual = control_pilot_get_current();
  TEST_ASSERT_EQUAL(answer, actual);

  // case 2
  s_duty_cycle = 97;
  answer = 60;
  actual = control_pilot_get_current();
  TEST_ASSERT_EQUAL(answer, actual);

  // case 3
  s_duty_cycle = 600;
  answer = 360;
  actual = control_pilot_get_current();
  TEST_ASSERT_EQUAL(answer, actual);
  
  // more cases...
}

Parameterized tests give us a way to perform this kind of repetitive yet thorough test concisely.

We can rewrite the above test like this:

TEST_CASE(10, 0)
TEST_CASE(97, 60)
TEST_CASE(600, 360)
// ... more TEST_CASE calls
void test_various_duty_cycles(uint32_t duty_cycle, uint16_t current) {
  s_duty_cycle = duty_cycle;
  TEST_ASSERT_EQUAL(current, control_pilot_get_current());
}

Parameterized tests are a great way to test your functions for a lot of edge cases at once. Each test case will run as a separate test; setup_test will run before each test case and teardown_test will run after each test case, and each case will print out on a separate line. You’ll get output that looks like this:

projects/charger/test/test_control_pilot.c:22:test_various_duty_cycles(10, 0):PASS
projects/charger/test/test_control_pilot.c:22:test_various_duty_cycles(97, 60):PASS
projects/charger/test/test_control_pilot.c:22:test_various_duty_cycles(600, 360):PASS
...

Warning: only integer literals, symbols defined outside the test file, and derived expressions are currently supported in TEST_CASE calls. TEST_CASE calls also can’t be split over multiple lines. So:

// OK
TEST_CASE(0x1234)

// OK - test logs will have "test_function(GPIO_PORT_A)"
#include "gpio.h"
TEST_CASE(GPIO_PORT_A)

// OK - test logs will have "test_function((GpioAddress)CONTROLLER_BOARD_ADDR_LED_BLUE)"
#include "controller_board_pins.h"
TEST_CASE((GpioAddress)CONTROLLER_BOARD_ADDR_LED_BLUE)

// OK - test logs will have "test_function(10*50/(5+5))", not "test_function(50)"
TEST_CASE(10*50/(5+5))

// Not OK - FOO is defined in the current file
#define FOO 0x1234
TEST_CASE(FOO)

// Not OK - strings aren't allowed
TEST_CASE("foo")

// Not OK - arrays aren't allowed
TEST_CASE(uint8_t[]{1, 2, 3})

// Not OK - Foo is defined in the current file, and no compound literals
typedef struct Foo {
  uint8_t bar;
} Foo;
TEST_CASE((Foo){ .bar = 2 })

// Not OK - compound literals aren't allowed
TEST_CASE((GpioAddress){ .port = GPIO_PORT_A, .pin = 4 })

// Not OK - multiple lines
TEST_CASE(
  1, 2, 3
)

This is because our old version of the Unity test framework (http://www.throwtheswitch.org/unity) literally copies the arguments you put in the TEST_CASE call to the generated test runner file, which runs your tests. It doesn’t have access to any of the symbols defined in your test file, but it does include everything your test file does.

A lot of this weirdness will be fixed when we update Unity: SOFT-420 - Getting issue details... STATUS

Aside: the TEST_CASE macro is defined in test_helpers.h, so you’ll have to include it.

  • No labels