เห็นคนรู้จักหลายๆ คนทำ TDD กัน เลยอยากทำบาง โดยมากมักใช้ Watchr เพื่อช่วนในการรัน Test Case ทันทีที่บันทึกไฟล์ เห็นแล้วสะดวกดี แต่พอดีเขียน Ruby ไม่เป็นเลยมากหาไลบราลีภาษา Python แทน ก็ไปเจอ Watchdog อาศัยแรงของตั้วเลยได้ Python script อย่างง่ายมาเล่น TDD สำหรับ Python กัน
สำหรับไดเร็กทอรีโปรเจคจะประกอบไปด้วย src tests CmakeLists.txt และ cpp-testrunner
ไฟล์ tests/CMakeLists.txt
ไฟล์ tests/external/gtest/CMakeLists.txt
วันนี้อยากเอามันมาใช้กับ C++ บ้างเล่นเอาเหนื่อยเหมือนกันกว่าจะเอา Python script ของตั้วมาใช้กับ C++ ซึ่งจากที่ดูๆ มา Google Test กับ CMake น่าจะตอบโจทย์ TDD ได้ระดับนึ่ง แต่ต้องอาศัยการจัดรูปแบบของไดเร็กทอรีเข้าร่วมด้วย ตอนนี้สคริปต์ยังมีการกำหนดหลายๆ อย่างตายตัวอยู่มาก อาจจะยังไม่เรียบร้อยดีเท่าที่ควร
หน้าตาไดเร็กทอรีประมาณนี้
หน้าตาไดเร็กทอรีประมาณนี้
. ├── src │ └── xxx │ └── xxx.cpp ├── tests │ ├── CMakeLists.txt │ ├── external │ │ └── gtest │ │ └── CMakeLists.txt │ └── units │ └── xxx │ └── test_xxx.cpp ├── cpp-testrunner │ └── CMakeLists.txt
สำหรับไดเร็กทอรีโปรเจคจะประกอบไปด้วย src tests CmakeLists.txt และ cpp-testrunner
- src ใช้เก็บซอร์ซโค้ดของโปรแกรม
- tests ใช้เก็บ Test Case ต่างๆ
- CMakeLists.txt เป็น CMake หลักของโปรเจค
- cpp_testrunner คือสคริปต์ที่เอามาช่วยรัน Test Case
นอกจากนี้ยังมี tests/CMakeLists.txt และ tests/external/gtest/CMakeLists.txt สองไฟล์นี้ทำหน้าที่สำหรับคอมไฟล์ Test ให้กับโปรแกรม โดยที่ tests/CMakeLists.txt จะเป็นส่วนหลักในการคอมไพล์และจะเรียก tests/external/gtest/CMakeLists.txt เพื่อคอมไพล์ไลบรารี Gtest อีกทีนึง นั้นก็หมายความว่า เครื่องที่จะรันจะต้องติดตั้งเพกเกจ libgtest-dev ไว้แล้ว โดยตัวอย่างการเขียน CMake ทั้งหมดกับ Gtest ได้มาจาก google-test-examples
สำหรับส่วนของการเขียน Test จะต้องสร้าง Unit Test ไฟล์ไว้ใน tests/units และมีได้เร็กทอรีที่ล้อกับ src ดังตัวอย่าง src/xxx/xxx.cpp จะต้องสร้าง Unit Test ไฟล์ไว้ที่ tests/units/xxx/test_xxx.cpp ใช้ test_ นำหน้าที่ไฟล์ สำหรับ cpp-testrunner หน้าตาจะเหมือนด้านล่าง ใช้ Python3 นะครับ
สำหรับส่วนของการเขียน Test จะต้องสร้าง Unit Test ไฟล์ไว้ใน tests/units และมีได้เร็กทอรีที่ล้อกับ src ดังตัวอย่าง src/xxx/xxx.cpp จะต้องสร้าง Unit Test ไฟล์ไว้ที่ tests/units/xxx/test_xxx.cpp ใช้ test_ นำหน้าที่ไฟล์ สำหรับ cpp-testrunner หน้าตาจะเหมือนด้านล่าง ใช้ Python3 นะครับ
#!/usr/bin/env python import os import sys import time import re from watchdog.observers import Observer from watchdog.events import PatternMatchingEventHandler def when_file_changed(filename): def projectname(): """ Projectname from name of current directory ('.') if there are dash '-' in its name replace to underscore '_' """ return os.path.abspath(".").rsplit("/", 1)[1] def get_test_cases(filename): test_case = [] try: with open(test_file(filename), 'r') as f: src = f.read() test_case = list(set(re.findall('TEST\s*\((.*),', src))) except: pass return test_case def test_file(filename): basename = os.path.basename(filename) if not basename.startswith("test_"): filename = filename.replace("src", "tests/units") filename = filename.replace(basename, "test_" + basename) return filename def make_test_runner(): build_test_path = os.path.abspath(".")+'/build-test' test_path = os.path.abspath(".")+'/tests' os.system("cmake -B{build_test_path} -H{test_path}".format(build_test_path=build_test_path, test_path=test_path)) os.system("make -C {build_test_path}".format(build_test_path=build_test_path)) cls() print(os.path.abspath(filename)) make_test_runner() # extract testcase test_cases = get_test_cases(test_file(filename)) if len(test_cases) == 0: print('There are no unit tests, skip test running') return cmd = 'build-test/'+projectname()+'-test'\ + ' --gtest_filter='+'.*:'.join(test_cases)+'.*' print(cmd) os.system(cmd) def cls(): os.system('cls' if os.name == 'nt' else 'clear') class ModifiedHandler(PatternMatchingEventHandler): patterns = ['*.cpp', '*.hpp', '*cc', '*.h'] # Monitor only matched patterns def on_created(self, event): """" For Vim :w - not modify that deleted and created file instead. """ if '/build-test/' in event.src_path: return when_file_changed(event.src_path) if __name__ == '__main__': args = sys.argv[1:] path = args[1] if args else '.' build_path = path + '/build-test' if not os.path.exists(build_path): os.mkdir(build_path) event_handler = ModifiedHandler() observer = Observer() observer.schedule(event_handler, path=args[1] if args else '.', recursive=True) observer.start() try: while True: time.sleep(1) except KeyboardInterrupt: observer.stop() observer.join()
ไฟล์ tests/CMakeLists.txt
cmake_minimum_required(VERSION 2.8.8) set(PROJECT_NAME HelloGtest) set(PROJECT_TEST_NAME ${PROJECT_NAME}-test) project(${PROJECT_NAME} C CXX) set(EXT_PROJECTS_DIR ${PROJECT_SOURCE_DIR}/external) add_subdirectory(${EXT_PROJECTS_DIR}/gtest) link_libraries( ) file(GLOB_RECURSE ${PROJECT_NAME}_CXX_SRC RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "../src/*.cpp") file(GLOB_RECURSE ${PROJECT_NAME}_CXX_TESTS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} "test*.cpp") # remove main function file from test list(REMOVE_ITEM ${PROJECT_NAME}_CXX_SRC ${CMAKE_CURRENT_SOURCE_DIR} "../src/main.cpp") # complier set (CMAKE_CXX_COMPILER g++) add_definitions(-std=c++1y -pthread) set (CMAKE_VERBOSE_MAKEFILE TRUE) set (CMAKE_CXX_FLAGS_DEBUG, "-g -Wall") set (CMAKE_CXX_FLAGS "-Wall") include_directories ("${PROJECT_BINARY_DIR}" "${PROJECT_SOURCE_DIR}/../src" "${PROJECT_SOURCE_DIR}/../tests") add_executable (${PROJECT_TEST_NAME} ${${PROJECT_NAME}_CXX_TESTS} ${${PROJECT_NAME}_CXX_SRC}) # add_executable (${PROJECT_TEST_NAME} ${${PROJECT_NAME}_CXX_SRC}) add_dependencies(${PROJECT_TEST_NAME} googletest) target_link_libraries (${PROJECT_TEST_NAME} ${GTEST_LIBS_DIR}/libgtest.a ${GTEST_LIBS_DIR}/libgtest_main.a pthread )
ไฟล์ tests/external/gtest/CMakeLists.txt
cmake_minimum_required(VERSION 2.8.8) project(gtest_builder C CXX) include(ExternalProject) ExternalProject_Add(googletest SOURCE_DIR /usr/src/gtest CMAKE_ARGS -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG:PATH=DebugLibs -DCMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE:PATH=ReleaseLibs -DCMAKE_CXX_FLAGS=${MSVC_COMPILER_DEFS} -Dgtest_force_shared_crt=ON PREFIX "${CMAKE_CURRENT_BINARY_DIR}" # Disable install step INSTALL_COMMAND "" ) # Specify include dir ExternalProject_Get_Property(googletest source_dir) set(GTEST_INCLUDE_DIRS ${source_dir}/include PARENT_SCOPE) # Specify MainTest's link libraries ExternalProject_Get_Property(googletest binary_dir) set(GTEST_LIBS_DIR ${binary_dir} PARENT_SCOPE)
เวลาใช้งานก็แค่ รัน cpp-testrunner ทิ้งไว้ ทุกครั้งที่มีการบันทึกไฟล์ สคลิปต์ จะรัน cmake และ make ให้อัตโนมัติ พร้อมกับรัน Test Case ของไฟล์นั้นให้เลย
ความคิดเห็น
แสดงความคิดเห็น