[英]TDD for C. How to compile and run my first test with CppUTest?
[英]CppUTest not running on target - How to “fake” register
我正在對嵌入式C代碼進行單元測試而沒有在目標硬件上運行,這是代碼的一部分:
uint8 tempReadback = 0;
write_to_i2c( msg->addres, msg->value );
tempReadback = read_from_i2c( msg->addres);
if( tempReadback == msg->value )
{
somethingA;
}
else
{
somethingB;
}
函數write_to_i2c()將值寫入特定的寄存器。 函數read_from_i2c()從寄存器中讀回該值。 此外,我使用變量tempReadback來比較讀回的值是否與寫入的值相同。 到目前為止,一切正常,並且可以在目標硬件上使用。 現在,我正在執行Uni測試,而不在目標硬件(循環中的軟件)上運行代碼。 這意味着,表達式tempReadback == msg-> value永遠不會為true(tempReadback為0),並且每次都會在語句somethingB中運行 。 有沒有辦法偽造讀回的寄存器? 我正在使用CppUTest作為框架。
會感激的!
CppUTest非常適合嵌入式C開發,因為它是唯一一個可以模擬自由函數的測試框架(在您的情況下為write_to_i2c()
和read_from_i2c()
)。
現在,您確實應該閱讀CppUTest文檔或出色的書籍《嵌入式C的測試驅動開發》 。
無論如何,以下代碼顯示了如何執行此操作。
您的被測單元(UUT),以可編譯的方式編寫(下次您對SO提出問題時,請付出努力):
#include "temperature.h"
#include "i2c.h"
void somethingA(void) { }
void somethingB(void) { }
void temperature_do(uint8_t address, uint8_t value) {
write_to_i2c(address, value);
const uint8_t tempReadback = read_from_i2c(address);
if (tempReadback == value) {
somethingA();
} else {
somethingB();
}
}
如您所寫,我們需要“偽造”,或更確切地說,我們需要“模擬” write_to_i2c()
和read_from_i2c()
。 我們將模擬文件放在一個單獨的文件中,例如i2c_mock.cpp,以便在構建單元測試時,我們針對該模擬文件而不是針對實際的實現進行鏈接:
extern "C" {
#include "i2c.h"
};
#include "CppUTestExt/MockSupport.h"
void write_to_i2c(uint8_t address, uint8_t value) {
mock().actualCall(__FUNCTION__)
.withParameter("address", address)
.withParameter("value", value);
}
uint8_t read_from_i2c(uint8_t address) {
mock().actualCall(__FUNCTION__)
.withParameter("address", address);
uint8_t ret = mock().returnIntValueOrDefault(0);
return ret;
}
有關詳細信息,請參考CppUMock文檔。 這只是經典的CppUMock樣板。
最后一部分是單元測試:
extern "C" {
#include "temperature.h" // UUT
};
#include "CppUTest/TestHarness.h"
#include "CppUTest/CommandLineTestRunner.h"
#include "CppUTestExt/MockSupport.h"
TEST_GROUP(Temperature)
{
void setup() {}
void teardown() {
mock().checkExpectations();
mock().clear();
}
};
TEST(Temperature, somethingA)
{
const uint8_t value = 10;
mock().ignoreOtherCalls();
mock().expectOneCall("read_from_i2c").ignoreOtherParameters()
.andReturnValue(value);
temperature_do(10, value);
}
TEST(Temperature, somethingB)
{
const uint8_t value = 10;
mock().ignoreOtherCalls();
mock().expectOneCall("read_from_i2c").ignoreOtherParameters()
.andReturnValue(value+1);
temperature_do(10, value);
}
int main(int argc, char** argv) {
return CommandLineTestRunner::RunAllTests(argc, argv);
}
該UT實際上將提供100%的分支覆蓋率。 同樣,我無法解釋所有細節。 如果觀察並比較測試用例somethingA
和somethingB
,您將看到使UUT進入調用somethingA()
的路徑並一次進入調用somethingB()
的路徑所需要的內容。
讓我們舉個例子
mock().expectOneCall("read_from_i2c")
.ignoreOtherParameters()
.andReturnValue(value+1);
在這里,我們說的是CppUmock期望對函數read_from_i2c()
的調用,忽略參數是什么,這對返回value + 1
(或其他您喜歡的不同於value
)至關重要。 這將導致UUT進入調用somethingB()
的路徑。
快樂的嵌入式C開發和快樂的單元測試!
我看了一下您提議的有關TDD和模擬對象的書。 好的,據我所知,例如在這一行中(已經創建了虛擬機):
mock().expectOneCall("read_from_i2c").ignoreOtherParameters()
.andReturnValue(value);
temperature_do(10, value);
程序使用自己定義的參數“跳”到模擬的“ read_from_i2c”函數(來自i2c_mock.cpp),而不是被測單元的真實參數? 還是我們真的從被測單元調用了我們的函數,但是我們使用模擬中定義的參數來操縱該函數?
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.