# Ceedling 基本教學與心得
C 語言也可以做TDD,C 語言也可以做Unit Test
# 主要參考此網頁的Code
https://spin.atomicobject.com/2019/02/07/cmock-get-started/
[他的Github](https://github.com/joaniedavis/cmock_example)
# Ceedling 概念
主要有個Ruby 的Tool 可以幫助C 語言的使用者達到Unit Test
Unit Test 當中有一個最重要的概念是需要純電腦執行,環境是被假設成理想的環境
編寫Unit Test 的人必須知道理想上會得到什麼結果
C 語言最麻煩的部分就是會常常跟硬體有連結,例如I2C ,UART 之類的,但是Ceedling 這個工具包裡面有含有可以模仿(Mock) 的工具,CMock 可以幫助你解決這個問題。
例如:我們讀取I2C Address 0x3E 的溫度IC,他會回傳0x55。我們會利用這個0x55 數值來計算溫度。
```cpp
//File Name get_tempature.h
#include <stdint.h>
//This Will Return the Tempature
uint8_t get_tempature(uint8_t I2C_address);
```
```cpp=
//File Name get_tempature.c
#include "get_tempature.h"
uint8_t get_tempature(uint8_t I2C_address)
{
return I2C_GetData(I2C_address) * 5 / 10; // 5/10 就是除與2
}
```
一般執行的主程式
```cpp=
//File Name mainget.c
#include "mainget.h"
uint8_t mainget(void)
{
uint8_t nowTempature = 0;
uint8_t I2C_address = 0x3E;
nowTempature = get_tempature(I2C_address);
return nowTempature;
}
```
要開始使用Ceedling 之前,需要開始學會假設:
(這是理想的目標,但如果I2C 不是這樣子想,那也是我們誤會IC 了,需要重新假設。)
> I2C Address 0x3E 的溫度IC,他會回傳0x55
例如以下這一段程式碼就是會有假設->執行->確認結果,這三個步驟。
```cpp=
#include "mock_get_tempature.h" //mock_ 這個前綴是固定的(以後也可以改設定改)
void test_mainget_NeedToImplement(void)
{
uint8_t testTempature = 0;
get_tempature_ExpectAndReturn(0x3E, 42);
testTempature = mainget();
TEST_ASSERT_EQUAL(testTempature, 42 );
}
```
# Ceedling 實際執行步驟
## 建置環境
### VS Code Extension
https://marketplace.visualstudio.com/items?itemName=numaru.vscode-ceedling-test-adapter
## 指令
### 建立Project
```bash=
ceedling new TempatureSensor
cd .\TempatureSensor\
```
### 建議手動步驟
如果輸入Ceedling Help 看到: Unknown alias: common_defines
可以先把project.yml 當中的 Common Define 刪掉

> 刪除後

### 建立一個新的模組(Moudle)
```bash=
ceedling module:create[mainget]
```
### 測試編譯環境
```bash=
ceedling test:all
```
應該會看到

### 編寫程式碼(以VS Code 為範例)
#### 新增需要的.c 檔案
```bash=
ceedling module:create[get_tempature]
```
| 一開始的狀態 | 新增需要的檔案後與刪除多餘Teset 檔案 |
| ---------- | -------- |
| |  |
#### C 語言程式碼如下
> test_get_tempature.c
```cpp=
#ifdef TEST
#include "unity.h"
#include "mainget.h"
#include "mock_get_tempature.h"
void setUp(void)
{
}
void tearDown(void)
{
}
void test_mainget_NeedToImplement(void)
{
uint8_t testTempature = 0;
get_tempature_ExpectAndReturn(0x3E, 42);
testTempature = mainget();
TEST_ASSERT_EQUAL(testTempature, 42 );
}
#endif // TEST
```
### 如果故意Expect 輸入填錯,Test code 也會檢查出來

# Ceedling 其他 example Project 的程式碼如下
```cpp=
#include <stdbool.h>
void rectangle_init(uint16_t length, uint16_t width);
uint16_t rectangle_get_area(void);
uint16_t rectangle_get_perimeter(void);
bool rectangle_is_square(void);
```
```cpp=
#include "rectangle.h"
typedef struct rectangle
{
uint16_t r_length;
uint16_t r_width;
} rectangle_t;
static rectangle_t rect;
void rectangle_init(uint16_t length, uint16_t width)
{
rect.r_length = length;
rect.r_width = width;
}
uint16_t rectangle_get_area(void)
{
return rect.r_width * rect.r_length;
}
uint16_t rectangle_get_perimeter(void)
{
return (rect.r_length + rect.r_width) * 2;
}
bool rectangle_is_square(void)
{
return (rect.r_length == rect.r_width);
}
```
```cpp=
#include "shape_container.h"
void shape_container_init(uint16_t r_length, uint16_t r_width)
{
rectangle_init(r_length, r_width);
}
bool shape_container_calc_rect(uint16_t r_length, uint16_t r_width)
{
rectangle_init(r_length, r_width);
rectangle_get_area();
rectangle_get_perimeter();
return rectangle_is_square();
}
```
```cpp=
#ifdef TEST
#include "unity.h"
#include "shape_container.h"
#include "mock_rectangle.h"
void setUp(void)
{
}
void tearDown(void)
{
}
void test_shape_container_init(void)
{
// Set up known values
uint16_t length = 4;
uint16_t width = 3;
//State, in order of call, what expectations we have, and the expected values to be returned, if any
rectangle_init_Expect(length, width);
//Run Actual Function Under Test
shape_container_init(length, width);
}
void test_shape_container_calc_rect_is_square(void)
{
// Set up known values
uint16_t length = 4;
uint16_t width = 4;
uint16_t x_area = length * width;
uint16_t x_perimeter = length + width + length + width;
bool x_is_square = true;
//State, in order of call, what expectations we have, and the expected values to be returned, if any
rectangle_init_Expect(length, width);
rectangle_get_area_ExpectAndReturn(x_area);
rectangle_get_perimeter_ExpectAndReturn(x_perimeter);
rectangle_is_square_ExpectAndReturn(x_is_square);
//Run Actual Function Under Test
bool is_square = shape_container_calc_rect(length, width);
//We can still verify whatever things we normally would after
TEST_ASSERT_EQUAL(x_is_square, is_square);
}
void test_shape_container_calc_rect_is_not_square(void)
{
// Set up known values
uint16_t length = 4;
uint16_t width = 3;
uint16_t x_area = length * width;
uint16_t x_perimeter = length + width + length + width;
bool x_is_square = false;
//State, in order of call, what expectations we have, and the expected values to be returned, if any
rectangle_init_Expect(length, width);
rectangle_get_area_ExpectAndReturn(x_area);
rectangle_get_perimeter_ExpectAndReturn(x_perimeter);
rectangle_is_square_ExpectAndReturn(x_is_square);
//Run Actual Function Under Test
//This will Test the shape_container_calc_rect Function inside
//rectangle_is_square rectangle_init rectangle_get_area rectangle_get_perimeter
bool is_square = shape_container_calc_rect(length, width);
//We can still verify whatever things we normally would after
TEST_ASSERT_EQUAL(x_is_square, is_square);
}
#endif // TEST
```
# CMock Creating Skeletons
Creating Skeletons 可以加速讓編譯完成,之後再來考慮哪些是需要mock 還是stub 的功能
## Creating Skeletons:
https://github.com/ThrowTheSwitch/CMock/blob/master/docs/CMock_Summary.md
## 遇到的問題
需要先手動建立一個資料夾 mocks,因為預設的設定檔案是這樣子設定。
把需要mock 的header 檔案放在資料夾當中

## treat_externs 也可以使用使用命令列的方式傳入,用來debug 使用
ruby C:\Ruby32-x64\lib\ruby\gems\3.2.0\gems\ceedling-0.31.1\vendor\cmock\lib\cmock.rb --skeleton --treat_externs=":include" --skeleton_path=".\mocks"