- 如何測試函數的正確性
- 如何測試拋出正確的例外處理
//model.h typedef int model_type; typedef int vertex_t; typedef int tringle_t; typedef int mesh_t; typedef int material_t; typedef int joint_t; class model { public: void loadfile(const char* file_path); void render(); void animate(float speed, bool loop = true); //我們利用一些實用函數來加載模型文件 void parse_header_section(char* file_text); void parse_triangle_section(char* file_text); void parse_mesh_section(char* file_text); void parse_material_section(char* file_text); void parse_animation_section(char* file_text); void preare_joints(); //從文件中加載紋理(支援bmp檔) void load_texture(const char* file_path); //讀取檔案資訊的函數 double get_version() const { return m_version; } model_type get_type() const { return m_model_type; } const char* get_name() const { return m_name; } const char* get_author() const { return m_author; } //讀取資料函數 size_t get_number_of_vertices() const { return m_number_of_vertices; } size_t get_number_of_triangles() const { return m_number_of_triangles; } size_t get_number_of_meshes() const { return m_number_of_meshes; } size_t get_number_of_materials() const { return m_number_of_materials; } size_t get_number_of_joints() const { return m_number_of_joints; } private: double m_version; model_type m_model_type; char* m_name; char* m_author; unsigned short m_number_of_vertices; unsigned short m_number_of_triangles; unsigned short m_number_of_meshes; unsigned short m_number_of_materials; unsigned short m_number_of_joints; vertex_t* m_vertices; tringle_t* m_triangles; mesh_t* m_meshes; material_t* m_materials; joint_t* m_joints; };以下,將對一個parse_header_section()做單元測試。
它是一個public的函數,也是一般我們會做單元測試的範圍。
因為「在不更動public函數介面的情況之下,讓private的可讀性提昇」的情況之下重構,就可以在不變更unit test function的情況之下,知道你的程式碼有沒有改壞了。(所以,設計一個有好public function的class,是首要之事呀)
單元測試的內容,包含了正常的測試也包含了非正常情況的測試。
在此,我們都會對parse_header_section()做測試,不過兩種測試都是針對「可預期情況」做測試。
非正常條件的測試
/* 第一個測試例子是一個比較簡單的測試:
向這個函數傳遞一個非法的參數,我們就假設去傳入一個空指標。
我們預期的是,如果傳遞給函數的標記語言是非法的,那麼該函數會發出一個model_invalid_header的例外。
因此,你的測試內容應該向函數parse_animation_section()傳入一個空指標,
並使用CPPUNIT_ASSERT_THROW巨集,驗證這個函數會拋出model_invalid_header例外。
*/
void TestInvlidHeaderNullValue()
{
model my_model;
CPPUNIT_ASSERT_THROW(my_model.parse_animation_section(0), model_invalid_header);
}
/*
另一個測試是傳入一個有問題的標籤。
*/
void TestInvalidHeaderIllFormatted()
{
model my_model;
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER"
"<VERSION>1.1</VERSION>"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"),
model_invalid_header);
/*
在這標籤測試中,在第一個HEADER的後面少了一個">",並且還缺少了一個結束標記</HEADER>
*/
}
/* 第三個測試應該去測試這個情境:
如果標記中缺少了版本標籤"<VERSION>",或缺少了類型標籤"<TYPE>",這個函數會有怎樣的執行結果呢?
如果沒有找到這些資訊,這個函數就應該相對應的發出model_invalid_version例外和model_invalid_type例外。
*/
void TestInvalidVersionSection()
{
model my_model;
//版本標籤缺少內容
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION></VERSION>"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_version);
//缺少版本標籤
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_version);
//版本標籤出錯
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_version);
/*
在這標籤測試中,在第一個HEADER的後面少了一個">",並且還缺少了一個結束標記</HEADER>
*/
}
/*
我們還應該建立另一個類似的測試,驗證type類型。
*/
void TestInvalidType()
{
model my_model;
//版本標籤缺少內容
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<TYPE></TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_type);
//缺少版本標籤
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_type);
//版本標籤出錯
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<TYPE>WorldLevel"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_type);
}
正常條件的測試
/* 用失敗的條件去測試固然重要,但是用可以正常工作的資料去檢查函數的正確性也是很重要的。
對於條件測試,我們可以使用CPPUNIT_ASSERT巨集。
這個和C++標準的assert()幾乎一模一樣。
在此要叫函數parse_header_section,然後再來驗證其版本訊息和類型訊息是否正確。
*/
void TestInvalidValidHeader()
{
model my_model;
my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>");
model_invalid_header);
CPPUNIT_ASSERT(my_model.get_version() == 1.2);
CPPUNIT_ASSERT(my_model.get_type() == world_level);
CPPUNIT_ASSERT(strcmp(my_model.get_name(), "Character Select Gallery") == 0);
CPPUNIT_ASSERT(strcmp(my_model.get_author(), "Blake Madden") == 0);
//沒有包含模型的作者和模型名稱
my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<TYPE>WorldLevel</TYPE>"
"</HEADER>");
model_invalid_header);
CPPUNIT_ASSERT(my_model.get_version() == 1.2);
CPPUNIT_ASSERT(my_model.get_type() == world_level);
CPPUNIT_ASSERT(strcmp(my_model.get_name(), "") == 0);
CPPUNIT_ASSERT(strcmp(my_model.get_author(), "") == 0);
/*
我們傳入函數中的參數是完全合法的標記語言,然後再去確認相對應的版本、類型、名稱以及作者。
我們可以知道哪些成功,至於哪些失敗,CppUnit會把它記錄在Log中。
*/
}
最後,測試治具類別裡的code像這樣#ifndef MODEL_CLASS_H
#define MODEL_CLASS_H
#include "CppunitLib.h"
#include "model.h"
class ModelTest : public CppUnit::TestFixture
{
public:
/* 第一個測試例子是一個比較簡單的測試:
向這個函數傳遞一個非法的參數,我們就假設去傳入一個空指標。
我們預期的是,如果傳遞給函數的標記語言是非法的,那麼該函數會發出一個model_invalid_header的例外。
因此,你的測試內容應該向函數parse_animation_section()傳入一個空指標,
並使用CPPUNIT_ASSERT_THROW巨集,驗證這個函數會拋出model_invalid_header例外。
*/
void TestInvlidHeaderNullValue()
{
model my_model;
CPPUNIT_ASSERT_THROW(my_model.parse_animation_section(0), model_invalid_header);
}
/*
另一個測試是傳入一個有問題的標籤。
*/
void TestInvalidHeaderIllFormatted()
{
model my_model;
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER"
"<VERSION>1.1</VERSION>"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"),
model_invalid_header);
/*
在這標籤測試中,在第一個HEADER的後面少了一個">",並且還缺少了一個結束標記</HEADER>
*/
}
/* 第三個測試應該去測試這個情境:
如果標記中缺少了版本標籤"<VERSION>",或缺少了類型標籤"<TYPE>",這個函數會有怎樣的執行結果呢?
如果沒有找到這些資訊,這個函數就應該相對應的發出model_invalid_version例外和model_invalid_type例外。
*/
void TestInvalidVersionSection()
{
model my_model;
//版本標籤缺少內容
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION></VERSION>"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_version);
//缺少版本標籤
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_version);
//版本標籤出錯
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_version);
/*
在這標籤測試中,在第一個HEADER的後面少了一個">",並且還缺少了一個結束標記</HEADER>
*/
}
/*
我們還應該建立另一個類似的測試,驗證type類型。
*/
void TestInvalidType()
{
model my_model;
//版本標籤缺少內容
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<TYPE></TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_type);
//缺少版本標籤
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_type);
//版本標籤出錯
CPPUNIT_ASSERT_THROW(my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<TYPE>WorldLevel"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>"),
model_invalid_type);
}
/* 用失敗的條件去測試固然重要,但是用可以正常工作的資料去檢查函數的正確性也是很重要的。
對於條件測試,我們可以使用CPPUNIT_ASSERT巨集。
這個和C++標準的assert()幾乎一模一樣。
在此要叫函數parse_header_section,然後再來驗證其版本訊息和類型訊息是否正確。
*/
void TestInvalidValidHeader()
{
model my_model;
my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<TYPE>WorldLevel</TYPE>"
"<NAME>Character Select Gallery</NAME>"
"<AUTHOR>Blake Madden</AUTHOR>"
"</HEADER>");
model_invalid_header);
CPPUNIT_ASSERT(my_model.get_version() == 1.2);
CPPUNIT_ASSERT(my_model.get_type() == world_level);
CPPUNIT_ASSERT(strcmp(my_model.get_name(), "Character Select Gallery") == 0);
CPPUNIT_ASSERT(strcmp(my_model.get_author(), "Blake Madden") == 0);
//沒有包含模型的作者和模型名稱
my_model.parse_header_section(
"<HEADER>"
"<VERSION>1.2</VERSION>"
"<TYPE>WorldLevel</TYPE>"
"</HEADER>");
model_invalid_header);
CPPUNIT_ASSERT(my_model.get_version() == 1.2);
CPPUNIT_ASSERT(my_model.get_type() == world_level);
CPPUNIT_ASSERT(strcmp(my_model.get_name(), "") == 0);
CPPUNIT_ASSERT(strcmp(my_model.get_author(), "") == 0);
/*
我們傳入函數中的參數是完全合法的標記語言,然後再去確認相對應的版本、類型、名稱以及作者。
我們可以知道哪些成功,至於哪些失敗,CppUnit會把它記錄在Log中。
*/
}
public:
CPPUNIT_TEST_SUITE(ModelTest);
CPPUNIT_TEST(TestInvlidHeaderNullValue);
CPPUNIT_TEST(TestInvalidHeaderIllFormatted);
CPPUNIT_TEST(TestInvalidVersionSection);
CPPUNIT_TEST(TestInvalidType);
CPPUNIT_TEST(TestInvalidValidHeader);
CPPUNIT_TEST_SUITE_END();
};
#endif
沒有留言:
張貼留言
(什麼是留言欄訊息?)