文章目錄
- 一、單元測試是什么
- 二、單元測試的過程
- 三、為什么需要單元測試
- 四、MQL測試代碼實現
一、單元測試是什么
單元測試是對軟件中最小可測單元(如類或函數)進行獨立驗證和檢查的過程。它是由開發工程師完成的,旨在確保每個單元的功能和邏輯正確性。單元測試通常涉及驅動代碼、樁代碼和模擬代碼。
驅動代碼是用于調用被測試單元的代碼,它提供了測試輸入并捕獲輸出結果。樁代碼是用于模擬被測試單元所依賴的其他組件或模塊的代碼,以便在測試過程中隔離被測試單元。模擬代碼是用于模擬外部依賴的行為,以便在測試過程中控制和驗證被測試單元的交互。
二、單元測試的過程
- 確定要測試的單元:選擇一個具體的類或函數作為測試的目標。
- 編寫測試用例:根據被測試單元的功能和邏輯編寫多個測試用例,覆蓋不同的輸入和邊界情況。
- 編寫測試代碼:使用適當的測試框架編寫測試代碼,包括調用被測試單元并驗證輸出結果的斷言。
- 運行測試:運行測試代碼,確保所有的測試用例都能通過。
- 分析結果:檢查測試結果,查找失敗的測試用例并修復相關問題。
- 重復上述步驟:持續編寫和運行測試,直到所有的測試用例都能夠通過。
通過單元測試,開發工程師可以及早發現和修復代碼中的錯誤,提高代碼質量和可維護性,確保軟件的各個組件能夠正常工作。
三、為什么需要單元測試
-
確保代碼質量:單元測試可以幫助開發者驗證代碼的正確性,確保代碼按照預期工作。通過編寫針對每個函數或方法的單元測試,可以及早發現潛在的問題和錯誤,從而提高代碼的質量。
-
提高代碼可維護性:單元測試可以作為代碼的文檔,幫助開發者理解和維護代碼。當需要修改代碼時,可以通過運行單元測試來驗證修改是否影響了代碼的正確性。
-
支持重構和優化:單元測試可以在重構和優化代碼時提供保障。通過運行單元測試,可以確保重構和優化后的代碼仍然按照預期工作,避免引入新的問題。
-
提高開發效率:雖然編寫單元測試需要一定的時間和精力,但它可以幫助開發者在后期節省大量的調試時間。通過及早發現和解決問題,可以減少調試的時間和精力,提高開發效率。
-
支持持續集成和自動化測試:單元測試是持續集成和自動化測試的基礎。通過編寫可自動運行的單元測試,可以在每次代碼提交后自動運行測試,及早發現問題,確保代碼的穩定性和可靠性。
因此,單元測試是保證代碼質量、提高開發效率和可維護性的重要手段。
四、MQL測試代碼實現
#property link "VX: mtquant"
#property version "1.10"
#property description "MQL語言的一個簡單的單元測試工具。."#define assert_equal(v_1, v_2) _assert_equal(__FILE__, __FUNCTION__, (string)__LINE__, (v_1), (v_2))class TestCase
{protected:string errors[];uint error_len;uint tests_number;uint successful_tests_number;uint start_time;// changed parametersstring output_file_path;public:TestCase() {error_len = 0;tests_number = 0;successful_tests_number = 0;output_file_path = MQLInfoString(MQL_PROGRAM_NAME) + "_unit_test_log.txt";start_time = GetTickCount();}//void set_output_file_path(string _output_file_name){output_file_path = _output_file_name;};//void add_error(string error) {error_len++;ArrayResize(errors, error_len);errors[error_len-1] = error;}//template<typename T1,typename T2>void _assert_equal(string file, string func_sig, string line, T1 v_1, T2 v_2){tests_number++;// ex: TestFunc.mq4(38), MyTest::test_string_len(): 11 != 5if (v_1 != v_2) add_error(file + "(" + line + "), " + func_sig + "(): " + (string)v_1 + " != " + (string)v_2);elsesuccessful_tests_number++;}//string pretty_time(int ms) {return (string)(ms/1000) + " sec";}//bool check_file(int h_file) {if (h_file < 0) {Comment(output_file_path + ": Error with file creation (error: " + (string)GetLastError() + ")");return false;}return true;}//bool init_log_file() {int handle = FileOpen(output_file_path, FILE_WRITE, ";");if (!check_file(handle)) return false;FileWrite(handle, StringFormat("--- %s: Unit Test: running... ---", TimeToString(TimeLocal())));FileClose(handle);return true;}//virtual void declare_tests(){}//void run(){if (!init_log_file()) return;declare_tests();// write logint handle = FileOpen(output_file_path, FILE_WRITE, ";");if (check_file(handle)) {FileWrite(handle, StringFormat("--- %s: Unit Test: passed tests %d from %d (elapsed time: %s) ---",TimeToString(TimeLocal()), successful_tests_number, tests_number, pretty_time(GetTickCount() - start_time)));for (uint i=0;i<error_len;i++)FileWrite(handle, errors[i]);FileClose(handle);}}
};
class SimpleTest: public TestCase
{void test_math_abs() {assert_equal(MathAbs(-1.25), 1.25);assert_equal(MathAbs(2.15), 2.15);}void test_string_len() {assert_equal(StringLen("xxx"), 3);assert_equal(StringLen("some string"), 5); // test fails}void declare_tests() {test_math_abs();test_string_len();}
};double min(double v_1, double v_2)
{if (v_1 > v_2) return v_2;return v_1;
}class MyFunctionTest: public TestCase
{void test_my_function_min() {assert_equal(min(4, 10), 4);assert_equal(min(8, 1), 1);assert_equal(min(5, 0), 5); // test fails}void declare_tests() {test_my_function_min();}
};void OnStart()
{SimpleTest simple_test;simple_test.run();MyFunctionTest my_function_test;my_function_test.set_output_file_path(MQLInfoString(MQL_PROGRAM_NAME) + "_MyFunctionTest_unit_test.log"); // long namemy_function_test.run();
}