diff --git a/src/cmocka.c b/src/cmocka.c
index ede5b22..ec47f4e 100644
--- a/src/cmocka.c
+++ cmocka/src/cmocka.c
@@ -2532,6 +2532,7 @@ static void cmprintf_group_finish_xml(const char *group_name,
         if (fp == NULL) {
             fp = fopen(buf, "w");
             if (fp != NULL) {
+                xml_printed = 0;
                 file_append = 1;
                 file_opened = 1;
             } else {
@@ -2554,13 +2555,15 @@ static void cmprintf_group_finish_xml(const char *group_name,
     }
 
     if (!xml_printed || (file_opened && !file_append)) {
-        fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
+        fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n<testsuites>\n");
         if (!file_opened) {
             xml_printed = 1;
         }
+    } else {
+        fseek(fp, strlen("</testsuites>\n") * -1, SEEK_END);
+        ftruncate(fileno(fp), ftell(fp));
     }
 
-    fprintf(fp, "<testsuites>\n");
     fprintf(fp, "  <testsuite name=\"%s\" time=\"%.3f\" "
                 "tests=\"%u\" failures=\"%u\" errors=\"%u\" skipped=\"%u\" >\n",
                 group_name,
diff --git a/tool/cmocka_implement.py b/tool/cmocka_implement.py
new file mode 100644
index 0000000..11d2842
--- /dev/null
+++ cmocka/tool/cmocka_implement.py
@@ -0,0 +1,213 @@
+# -*- coding: utf-8 -*-
+import os
+import re
+import typer
+import copy
+import traceback
+
+TESTSUITE_TEMPLATE = """
+/*
+ * Copyright (C) 2023 Xiaomi Corporation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <cmocka.h>
+
+/****************************************************************************
+ * Name: cmocka_{suite_file}_main
+ ****************************************************************************/
+
+int main(int argc, char* argv[])
+{
+
+  /* Add Test Cases */
+  const struct CMUnitTest {suite_name}[] = {
+    cmocka_unit_test_setup_teardown(write case name here, NULL, NULL),
+  };
+
+    /* Run Test cases */
+    cmocka_run_group_tests({suite_name}, NULL, NULL);
+
+    printf("hello cmocka auto-tests\\n");
+
+    return 0;
+}
+"""
+
+TESTCASE_TEMPLATE = """
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+#include <syslog.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <cmocka.h>
+
+
+/****************************************************************************
+ * Name: {case_file}
+ * Description: Testing for scene "describe scene here".
+ * The detail test steps are as following:
+ * 1. describe step 1 here
+ * 2. describe step 2 here
+ * 3. describe step 3 here
+ ****************************************************************************/
+
+void {case_name}(FAR void **state)
+{
+    printf("case: {case_name}\\n");
+    assert(true);
+}
+"""
+
+
+class CmockaGen:
+
+    def __init__(self, path):
+        self.path = path
+        self.suite_path = None
+        self.suite_file = None
+        self.suite_name = None
+        self.case_path = None
+        self.case_file = None
+        self.case_name = None
+
+    def check_path(self):
+        if not self.path:
+            print("request correct path option")
+            return 1
+        if not os.path.exists(self.path):
+            os.makedirs(self.path)
+        return 0
+
+    def check_suite_option(self, suite_option):
+        if not suite_option:
+            return 1
+        opts = suite_option.split("::")
+        if len(opts) != 2:
+            print("suite option must like aaa/bbb/ccc.c::VelaAutoTestSuite")
+            return 1
+        else:
+            self.suite_path = opts[0]
+            self.suite_name = opts[1]
+            paths = self.suite_path.split("/")
+            if not paths[-1].endswith(".c"):
+                print("suite option must like aaa/bbb/ccc.c::VelaAutoTestSuite")
+                return 1
+            else:
+                self.suite_file = paths[-1]
+                return 0
+
+    def generate_suite(self):
+        content = copy.deepcopy(TESTSUITE_TEMPLATE)
+        file_without_ext = self.suite_file.replace(".c", "")
+        content = content.replace("{suite_file}", file_without_ext)
+        content = content.replace("{suite_name}", self.suite_name)
+        full_path = os.path.join(self.path, self.suite_path)
+        dir_path = os.path.dirname(full_path)
+        if not os.path.exists(dir_path):
+            os.makedirs(dir_path)
+        with open(full_path, "w") as fl:
+            fl.write(content)
+
+    def check_case_option(self, case_option):
+        if not case_option:
+            return 1
+        opts = case_option.split("::")
+        if len(opts) != 2:
+            print("case option must like aaa/bbb/ccc.c::test_playback_uv_01")
+            return 1
+        else:
+            self.case_path = opts[0]
+            self.case_name = opts[1]
+            if not self.case_name.startswith("test"):
+                print("case function name start with 'test'")
+                return 1
+            paths = self.case_path.split("/")
+            if not paths[-1].endswith(".c"):
+                print("case option must like aaa/bbb/ccc.c::VelaAutoTestcase")
+                return 1
+            file_parts = paths[-1].split("_")
+            pattern = r"[0-9]{2,3}\.c$"
+            if file_parts[0] != "test":
+                print("case file name must start with 'test'")
+                return 1
+            elif not re.search(pattern, file_parts[-1]):
+                print("case file name must end with '00-99' or '000-999'")
+                return 1
+            else:
+                self.case_file = paths[-1]
+                return 0
+
+    def generate_case(self):
+        content = copy.deepcopy(TESTCASE_TEMPLATE)
+        content = content.replace("{case_file}", self.case_file)
+        content = content.replace("{case_name}", self.case_name)
+        full_path = os.path.join(self.path, self.case_path)
+        dir_path = os.path.dirname(full_path)
+        if not os.path.exists(dir_path):
+            os.makedirs(dir_path)
+        with open(full_path, "w") as fl:
+            fl.write(content)
+
+    def main(self, suite_option, case_option):
+        try:
+            if self.check_path():
+                return
+            if not self.check_suite_option(suite_option):
+                self.generate_suite()
+                print("generate suite success")
+            if not self.check_case_option(case_option):
+                self.generate_case()
+                print("generate case success")
+        except Exception as e:
+            traceback.print_exc()
+
+
+app = typer.Typer()
+
+@app.command()
+def main(
+    path: str = typer.Option("", help="where to gnerate suite/case file"),
+    suite: str = typer.Option(
+        default="",
+        help="suite file name and suite function name, path/suite::name, eg aaa/bbb/ccc.c::VelaAutoTestcase"
+    ),
+    case: str = typer.Option(
+        default="",
+        help="case file name and case function name, path/case::function, eg ddd/eee/fff.c::test_playback_uv_01"
+    ),
+):
+    """
+    :param path: where to gnerate suite/case file
+    :param suite: suite file name and suite function name
+    :param case: case file name and case function name
+    :return:
+    """
+    gen = CmockaGen(path)
+    gen.main(suite, case)
+
+if __name__ == '__main__':
+    app()
