6.8 KiB
6.8 KiB
Test Driven Development in C
TDD recap
- good for unit tests, if requirements are known (specification exists)
cgreen testing framework
- readable
- mocking support
- different test reporters
- align nicely in CI (
cobertura
/xJunit
) - user friendly (
pretty
)
- align nicely in CI (
- test discovery (
cgreen-runner
)
Example: "Calculator"
inc/my_fancy_calculator.h
:
int add(int a, int b);
src/my_fancy_calculator.c
:
#include "my_fancy_calculator.h"
int add(int a, int b) {
return a - b;
}
Unit tests with cgreen
test/my_fancy_test.c
:
#include <cgreen/cgreen.h>
#include <my_fancy_calculator.h>
Ensure(my_fancy_calculator_can_do_addition) {
assert_that(add(2, 3), is_equal_to(5));
}
- automatic test discovery
cgreen_runner libdemo_test.so
:
Running "libdemo_test" (1 test)...
../test/my_fancy_test.c:5: Failure: default -> my_fancy_calculator_can_do_addition
Expected [add(2, 3)] to [equal] [5]
actual value: [-1]
expected value: [5]
"default": 1 failure in 30ms.
Completed "libdemo_test": 1 failure in 30ms.
BDD
Behavioral Driven Development
- isolated tests
- via mocking
- SDKs
- Network
- etc
- via test fixtures (
BeforeEach
/AfterEach
)- context setup and tear down
- via mocking
- good for reproducing bugs
Example: MQTT client
#include "mqtt_example.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <MQTTClient.h>
#define ADDRESS "tcp://localhost:1883"
#define CLIENTID "ExampleClientPub"
#define TOPIC "MQTT Examples"
#define QOS 1
#define TIMEOUT 10000L
int send_mqtt_msg(char *payload, size_t payload_len) {
MQTTClient client;
MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
MQTTClient_message pubmsg = MQTTClient_message_initializer;
MQTTClient_deliveryToken token;
int rc;
MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);
conn_opts.keepAliveInterval = 20;
conn_opts.cleansession = 1;
if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS) {
printf("Failed to connect, return code %d\n", rc);
exit(-1);
}
pubmsg.payload = payload;
pubmsg.payloadlen = payload_len;
pubmsg.qos = QOS;
pubmsg.retained = 0;
MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token);
printf("Waiting for up to %d seconds for publication of %s\n"
"on topic %s for client with ClientID: %s\n",
(int)(TIMEOUT/1000), payload, TOPIC, CLIENTID);
rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
printf("Message with delivery token %d delivered\n", token);
MQTTClient_disconnect(client, 10000);
MQTTClient_destroy(&client);
return rc;
}
Mocks in cgreen
#include <MQTTClient.h>
#include <cgreen/mocks.h>
LIBMQTT_API int MQTTClient_create(MQTTClient* handle, const char* serverURI, const char* clientId, int persistence_type, void* persistence_context) {
return (int)mock(handle);
}
LIBMQTT_API int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options) {
return (int)mock(handle);
}
LIBMQTT_API int MQTTClient_publishMessage(MQTTClient handle, const char* topicName, MQTTClient_message* msg, MQTTClient_deliveryToken* dt) {
return (int)mock(msg->payload, msg->payloadlen);
}
LIBMQTT_API int MQTTClient_waitForCompletion(MQTTClient handle, MQTTClient_deliveryToken dt, unsigned long timeout) {
return (int)mock();
}
LIBMQTT_API int MQTTClient_disconnect(MQTTClient handle, int timeout) {
return (int)mock();
}
LIBMQTT_API void MQTTClient_destroy(MQTTClient* handle) {
mock(handle);
}
BDD-style unit test with cgreen
Describe(mqtt_example);
BeforeEach(mqtt_example) {}
AfterEach(mqtt_example) {}
Ensure(mqtt_example, payload_is_sent) {
// Arrange
MQTTClient mock_handle = NOT_NULL;
char *payload = "Hello World.";
size_t payload_len = strlen(payload);
// Expect
expect(MQTTClient_create,
will_set_contents_of_parameter(handle,
&mock_handle,
sizeof(mock_handle)));
expect(MQTTClient_connect,
when(handle,
is_equal_to(mock_handle)),
will_return(MQTTCLIENT_SUCCESS));
expect(MQTTClient_publishMessage,
when(msg->payload,
is_equal_to_string(payload)),
when(msg->payloadlen,
is_equal_to(payload_len)));
expect(MQTTClient_waitForCompletion,
will_return(MQTTCLIENT_SUCCESS));
expect(MQTTClient_disconnect);
expect(MQTTClient_destroy);
// Act
send_mqtt_msg(payload, payload_len);
}
cgreen learning mock mode
#include <cgreen/cgreen.h>
#include <mqtt_example.h>
// [mock definitions]
Describe(mqtt_example);
BeforeEach(mqtt_example) {
cgreen_mocks_are(learning_mocks);
}
AfterEach(mqtt_example) {}
Ensure(mqtt_example, payload_is_sent) {
// Arrange
char *payload = "Hello World.";
size_t payload_len = strlen(payload);
// Expect
// TODO
// Act
send_mqtt_msg(payload, payload_len);
}
learned mocks
stdout:
Running "libmqtt_example_test" (1 test)...
Waiting for up to 10 seconds for publication of Hello World.
on topic MQTT Examples for client with ClientID: ExampleClientPub
Message with delivery token 0 delivered
"mqtt_example": No assertions.
Completed "libmqtt_example_test": No assertions.
stderr:
mqtt_example -> payload_is_sent : Learned mocks are
expect(MQTTClient_create, when(handle, is_equal_to(140735561153976)));
expect(MQTTClient_connect, when(handle, is_equal_to(0)));
expect(MQTTClient_publishMessage, when(msg->payload, is_equal_to(139884358905951)), when(msg->payloadlen, is_equal_to(12)));
expect(MQTTClient_waitForCompletion);
expect(MQTTClient_disconnect);
expect(MQTTClient_destroy, when(handle, is_equal_to(140735561153976)));
Whats next?
Some disclaimers
- cgreen's buildsystem is
CMAKE
meson
usage withcgreen-runner
does not work out of the box- see repo of this talk for example setup
- but.. cgreen is availabe on most common linux distros (
-lcgreen
,find_program
, etc should work just fine)
[backup] TDD test structure
- arrange
- act -- test function
- assert
[backup] BDD test structure
- arrange (via test fixtures)
- expect -- setup behavior of mocks
- act -- entry function to trigger behavior
- (assert) -- check post conditions