tdd-in-c/slides/TDD in C.md

270 lines
6.8 KiB
Markdown
Raw Permalink Normal View History

2022-08-27 15:11:45 +00:00
# Test Driven Development in C
---
### TDD recap
![[TDD cycle.excalidraw]]
* 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`)
* test discovery (`cgreen-runner`)
---
### Example: "Calculator"
`inc/my_fancy_calculator.h`:
```c
int add(int a, int b);
```
`src/my_fancy_calculator.c`:
```c
#include "my_fancy_calculator.h"
int add(int a, int b) {
return a - b;
}
```
---
### Unit tests with cgreen
`test/my_fancy_test.c`:
```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
* good for reproducing bugs
---
### Example: MQTT client
```c
#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;
}
```
[Eclipse Paho](https://www.eclipse.org/paho/index.php?page=clients/c/index.php)
---
### Mocks in cgreen
```c
#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
```c
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
```c
#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
```c
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?
Try it out!
![cgreen logo](https://github.com/cgreen-devs/cgreen/raw/master/doc/logo.png?s=300)
* [cgreen tutorial](https://cgreen-devs.github.io/cgreen/cgreen-guide-en.html)
* [cgreen repo](https://github.com/cgreen-devs/cgreen)
* [cgreen cheat sheet](https://cgreen-devs.github.io/cgreen/cheat-sheet.html)
---
### Some disclaimers
* cgreen's buildsystem is `CMAKE`
* `meson` usage with `cgreen-runner` does not work out of the box
* see [repo of this talk](https://git.dezentrale.cloud/oniboni/tdd-in-c.git) 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