From f59657bfe143a0df5197a1a4862ffd3195e17742 Mon Sep 17 00:00:00 2001 From: lymslive <403708621@qq.com> Date: Sat, 27 Apr 2024 00:33:04 +0800 Subject: [PATCH] feat: support unit test setup and teardown once #10 --- CMakeLists.txt | 11 ++- include/unittast.hpp | 151 ++++++++++++++++++++++++++++++++++ utest/test-unittast.cpp | 176 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 334 insertions(+), 4 deletions(-) create mode 100644 include/unittast.hpp create mode 100644 utest/test-unittast.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4cea691..1eafd72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -50,6 +50,7 @@ add_executable(utCoutTast utest/test-tinytast.cpp utest/test-file-compare.cpp utest/test-classtast.cpp + utest/test-unittast.cpp utest/test-systemtast.cpp ) @@ -65,6 +66,7 @@ target_link_libraries(utCoutTast add_executable(utCxx98Tast utest/test-tinytast.cpp utest/test-classtast.cpp + utest/test-unittast.cpp ) target_compile_definitions(utCxx98Tast PRIVATE USE_TINY_MAIN) @@ -91,15 +93,16 @@ endif() set(COUTTAST_HEADER include/tinytast.hpp include/couttast.hpp - include/tinymain.hpp + include/unittast.hpp + include/classtast.hpp + include/tastargv.hpp include/coutstd.hpp include/extra-macros.hpp include/gtest-macros.hpp + include/tinymain.hpp + include/coutdebug.hpp include/logprint.hpp include/private-access.hpp - include/classtast.hpp - include/tastargv.hpp - include/coutdebug.hpp include/couttast.h include/tinyini.h include/systemtast.h diff --git a/include/unittast.hpp b/include/unittast.hpp new file mode 100644 index 0000000..69c43ef --- /dev/null +++ b/include/unittast.hpp @@ -0,0 +1,151 @@ +/** + * @file classtast.hpp + * @author lymslive + * @date 2024-04-25 + * @brief Support group test suite based on file unit or namespace in the file. + * @details A testing source file or compile unit is considered as a test + * module. The test cases defined in one unit may have some relation and share some + * expensive resource, such as global(better static) varibles. So an unit + * usually need run a `setup()` function once before run any test case, and + * it is better to have another opposite `teardown()` function to run after + * all tast case. + * @note The setup and teardown function handle auto tast(like from `DEF_TAST`) + * and manual tool(like from `DEF_TOOL`) differently. So we use `tast` and + * `tool` word for each type, and `test` for both. + * @note Thought it is no encouraged, for large source file, you can have + * multiple namespace in one file, and each namespace is can become seperate + * test unit. But it is not easy to share test unit in the same namespace + * across different source file. + * */ +#pragma once +#ifndef UNITTAST_HPP__ +#define UNITTAST_HPP__ + +#include "tinytast.hpp" + +namespace tast +{ + +/// The state of current unit. +struct CUnitState +{ + int tastCount; //< Count of tast(but not tool) in this unit defined. + int runCount; //< Count of tast finished run. + voidfun_t setup; //< Function to run once before the first test/tool. + voidfun_t teardown; //< Function to run after the last tast. + bool ready; //< The setup function has been called. + + CUnitState(voidfun_t fn = NULL) + : tastCount(0), runCount(0), setup(fn), teardown(NULL), ready(false) + {} + + /// Add count of tast in the unit. + struct AddTast + { + AddTast(CUnitState& unit, bool autoRun) + { + if (autoRun) + { + unit.tastCount++; + } + } + }; + + /// Define teardown function in the unit. + struct Defer + { + Defer(CUnitState& unit, voidfun_t fn) + { + unit.teardown = fn; + } + }; + + /// Check to run setup function once before any tast/tool. + void CheckSetupFirst() + { + if (!ready && setup != NULL) + { + setup(); + ready = true; + } + } + + /// Check to run teardown function after all tast. + void CheckTeardownLast() + { + if (teardown == NULL) + { + return; + } + if (G_TASTMGR->GetTastRun()->m_autoRun == false) + { + return; + } + runCount++; + if (runCount >= tastCount) + { + teardown(); + ready = false; + } + } +}; + +/// The base class wrapper for test in current refered unit. +struct CUnitTast +{ + CUnitState& unit; + + CUnitTast(CUnitState& refer) : unit(refer) + {} +}; + +/// The running entrance function for each test. +template <typename testT> +void run_unit_tast() +{ + testT self; + self.unit.CheckSetupFirst(); + self.run(); + self.unit.CheckTeardownLast(); +} + +} /* tast:: */ + +/// Setup current unit for following test cases. +/// Must use this macro the eable test unit tracking. +#define SETUP() \ + static void s_setup(); \ + static tast::CUnitState s_unit(s_setup); \ + void s_setup() + +/// Define optional teardown function for current test unit. +#define TEARDOWN() \ + static void s_teardown(); \ + static tast::CUnitState::Defer s_unitDefer(s_unit, s_teardown); \ + void s_teardown() + + +/// Helper macros to simply the unit test case definition. +#define UTAST_COUNTER(name) tast_##name##_counter +#define UTAST_CLASS(name) CUnitTast_ ## name +#define UTAST_RUNNER(name) tast::run_unit_tast<UTAST_CLASS(name)> + +/// Define a test case in current unit. +#define DEU_TAST_IMPL(name, autoRun, ...) \ + static void TAST_RUNNER(name)(); \ + struct UTAST_CLASS(name) : public tast::CUnitTast \ + { \ + UTAST_CLASS(name)() : tast::CUnitTast(s_unit) {} \ + void run(); \ + }; \ + static tast::CTastBuilder TAST_BUILDER(name)(UTAST_RUNNER(name), #name, __FILE__, __LINE__, autoRun, ## __VA_ARGS__); \ + static tast::CUnitState::AddTast UTAST_COUNTER(name)(s_unit, autoRun); \ + void UTAST_CLASS(name)::run() + +/// Define an auto tast case. Must use after SETUP unit. +#define DEU_TAST(name, ...) DEU_TAST_IMPL(name, true, ## __VA_ARGS__) + +/// Define a manual tool case. Must use after SETUP unit. +#define DEU_TOOL(name, ...) DEU_TAST_IMPL(name, false, ## __VA_ARGS__) + +#endif /* end of include guard: UNITTAST_HPP__ */ diff --git a/utest/test-unittast.cpp b/utest/test-unittast.cpp new file mode 100644 index 0000000..437b485 --- /dev/null +++ b/utest/test-unittast.cpp @@ -0,0 +1,176 @@ +#include "unittast.hpp" + +int* s_pBuffer = NULL; +void cout_counter(); + +SETUP() +{ + s_pBuffer = new int[8]; + DESC("file SETUP called"); + cout_counter(); + s_pBuffer[0] = s_unit.tastCount; +} + +TEARDOWN() +{ + delete[] s_pBuffer; + s_pBuffer = NULL; + DESC("file TEARDOWN called"); + cout_counter(); +} + +void cout_counter() +{ + COUT(s_pBuffer); + COUT(s_unit.tastCount); + COUT(s_unit.runCount); +} + +DEF_TAST(unit_normal, "normal test not in uint") +{ + cout_counter(); + // s_pBuffer may not inited. + // COUT(s_pBuffer[0], s_unit.tastCount); +} + +DEU_TAST(unit_auto1, "test in uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); + int* pBuffer = new int[16]; + pBuffer[15] = 1; + COUT(pBuffer); + delete[] pBuffer; +} + +DEU_TAST(unit_auto2, "test in uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); +} + +DEU_TOOL(unit_tool1, "tool in uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); +} + +DEU_TOOL(unit_tool2, "tool in uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); +} + +namespace unest +{ + +double* s_pBuffer = NULL; +void cout_counter(); + +SETUP() +{ + s_pBuffer = new double[4]; + DESC("unest::SETUP called"); + cout_counter(); + s_pBuffer[0] = 2.0; +} + +TEARDOWN() +{ + delete[] s_pBuffer; + s_pBuffer = NULL; + DESC("unest::TEARDOWN called"); + cout_counter(); +} + +void cout_counter() +{ + COUT(s_pBuffer); + COUT(s_unit.tastCount); + COUT(s_unit.runCount); +} + +DEU_TAST(unest_auto1, "test in namespace uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); + int* pBuffer = new int[16]; + pBuffer[15] = 1; + COUT(pBuffer); + delete[] pBuffer; +} + +DEU_TAST(unest_auto2, "test in namespace uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); +} + +} // endof unest:: + +namespace umore +{ + +double* s_pBuffer = NULL; +void cout_counter(); + +SETUP() +{ + s_pBuffer = new double[4]; + DESC("umore::SETUP called"); + cout_counter(); + s_pBuffer[0] = 3.0; +} + +TEARDOWN() +{ + delete[] s_pBuffer; + s_pBuffer = NULL; + DESC("umore::TEARDOWN called"); + cout_counter(); +} + +void cout_counter() +{ + COUT(s_pBuffer); + COUT(s_unit.tastCount); + COUT(s_unit.runCount); +} + +// yes, the test name is the same as above unest_auto1 +DEU_TAST(unest_auto1, "test in namespace uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); +} + +DEU_TAST(unest_auto2, "test in namespace uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); +} + +DEU_TAST(unest_auto3, "test in namespace uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); +} + +} // end of umore:: + +namespace unot +{ + +DEU_TAST(unit_auto2, "test in uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); +} + +DEU_TAST(unit_auto3, "test in uint") +{ + cout_counter(); + COUT(s_pBuffer[0], s_unit.tastCount); +} + +} // end of umore