目录

用AI对一段代码进行单元测试

用AI对一段代码进行单元测试

1 题目

测试代码如下:

#include <stdio.h>

#define MIN_RUN 32

// 插入排序算法
void insertionSort(int arr[], int left, int right) {
for (int i = left + 1; i <= right; i++) {
int key = arr[i];
int j = i - 1;
while (j >= left && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}

// 归并函数
void merge(int arr[], int left, int mid, int right) {
int len1 = mid - left + 1;
int len2 = right - mid;
int L[len1], R[len2];

    int i = 0, j = 0, k;
    for (i = 0; i < len1; i++)
        L[i] = arr[left + i];
    for (j = 0; j < len2; j++)
        R[j] = arr[mid + 1 + j];

    i = 0;
    j = 0;
    k = left;

    while (i < len1 && j < len2) {
        if (L[i] <= R[j])
            arr[k++] = L[i++];
        else
            arr[k++] = R[j++];
    }

    while (i < len1)
        arr[k++] = L[i++];
    while (j < len2)
        arr[k++] = R[j++];
    while(i>j && len1<len2)
    	arr[i++] = arr[j++];

}

// Timsort 算法
void timSort(int arr[], int n) {
for (int i = 0; i < n; i += MIN_RUN)
insertionSort(arr, i, (i + MIN_RUN - 1) < n ? (i + MIN_RUN - 1) : (n - 1));

    for (int size = MIN_RUN; size < n; size *= 2) {
        for (int left = 0; left < n; left += 2 * size) {
            int mid = left + size - 1;
            int right = (left + 2 * size - 1) < (n - 1) ? (left + 2 * size - 1) : (n - 1);
            merge(arr, left, mid, right);
        }
    }

}

利用 AI 工具对以上代码编写单元测试用例

2 说明

ai 工具 :文心大模型 4.0 Turbo。

测试环境 :在 Linux 环境下,用 gcc、gcov 和 lcov。

测试方式 :将以上代码放到 sort.c 文件中,用 ai 工具生成测试用例,放到 test.c 文件中,然后在 Linux 环境下编译运行,测试是否所有测试用例可以通过,用 lcov 生成 html 格式的测试报告。

3 操作过程

3.1 用 AI 优化需求

首先编写一个让 ai 生成单元测试代码的需求,然后发给 ai 让其优化一下。

输入

对这段代码编写单元测试案例,实现 100%语句覆盖、判定覆盖、条件覆盖(CC)、条件/判定覆盖(C/DC)、条件组合覆盖(MCC)、修正判定条件覆盖(MCDC)要求:
1、验证结果是否正确,不用预期结果与输出结果对比的方式,用遍历输出结果,判断是否前一个数据比后一个小
2、打印出哪些测试用例通过了,哪些测试用例没通过,生成一个结果测试报告。
3、除了生成一些常规的测试数据,再生成一些超长的,里面是随机数的输入数组,数组长度一个是 500,一个是 1000
4、生成一些长度不同的输入数组,长度从 100 连续到 299,共 200 组,里面都是随机数
5、每个测试用例封装成一个函数。
6、对于第 3 条和第 4 条要求的测试用例,打印测试结果时要标明测试数组长度。
7、用 malloc 申请内存存放输入数据时,测试完要及时释放。
9、常规测试数据里类型多一些,都是一些容易测试出代码问题的数据。
10、测试代码和被测试代码不在同一个文件中,被测试代码已经写好不需要 ai 再写一遍,只需要生成测试代码

以上是一个我要输入给文心一言的需求,现在对这个需求进行优化,让文心一言输出效果更好

输出

为了优化您的需求描述,使其更清晰、具体,并引导生成更高质量的测试代码,以下是对原始需求的优化版本:

---

测试代码生成需求优化版
目标:
为给定的排序函数编写单元测试,确保实现 100%的语句覆盖、判定覆盖、条件覆盖(CC)、条件/判定覆盖(C/DC)、条件组合覆盖(MCC)、修正判定条件覆盖(MCDC)具体要求:
1. 验证方法:
不通过预期结果与输出结果对比,而是通过遍历输出结果,判断数组是否已排序(即前一个数据是否比后一个小)。
2. 测试报告:
打印出哪些测试用例通过了,哪些没通过。
生成一个详细的测试报告,包括测试用例名称和测试结果。
3. 测试用例生成:
常规测试数据:
包含多种类型的数据,如空数组、单元素数组、已排序数组、逆序数组、含重复元素的数组、特殊值数组(如最大/最小值、负数、零等)。
这些数据应易于触发代码中的潜在问题。
超长随机数组:
生成两个长度分别为 5001000 的随机数组。
打印测试结果时标明测试数组长度。
连续长度随机数组:
生成长度从 100299200 组随机数组。
每组数组长度连续递增,打印测试结果时标明测试数组长度。
4. 测试用例封装:
每个测试用例应封装成一个独立的函数,便于管理和复用。
5. 内存管理:
使用 malloc 申请内存存放输入数据时,测试完成后要及时释放内存,避免内存泄漏。
6. 测试代码与被测试代码分离:
假设被测试代码已经写好,只需编写测试代码。
测试代码应位于与被测试代码不同的文件中。
优化后的详细步骤:
1. 编写测试框架:
创建一个测试框架,能够执行测试用例并记录结果。
提供函数用于打印测试报告。
2. 实现测试用例:
常规测试用例:
空数组测试。
单元素数组测试。
已排序数组测试。
逆序数组测试。
含重复元素的数组测试。
特殊值数组测试(如全为正数、全为负数、混合正负数、含零等)。
超长随机数组测试用例:
生成并测试长度为 5001000 的随机数组。
连续长度随机数组测试用例:
生成并测试长度从 100299200 组随机数组。
3. 验证排序结果:
在每个测试用例中,遍历排序后的数组,检查是否满足排序条件(前一个元素小于或等于后一个元素)。
4. 内存管理:
确保在每个测试用例中,使用 malloc 分配的内存都在测试完成后被正确释放。
5. 生成测试报告:
在所有测试用例执行完毕后,生成并打印详细的测试报告,包括每个测试用例的名称、测试结果(通过/失败)以及任何额外的信息(如数组长度)。
期望输出:
• 一个清晰的测试报告,显示所有测试用例的执行结果。
• 对于失败的测试用例,应提供足够的信息以便定位问题。

---

通过优化后的需求描述,您可以更清晰地指导文心一言生成高质量的测试代码,确保覆盖所有必要的测试场景,并生成详细的测试报告。

3.2 用 AI 生成单元测试代码

将被测试代码和需求发给 AI,让其生成相应的单元测试代码。

输入

#include <stdio.h>

#define MIN_RUN 32

// 插入排序算法
void insertionSort(int arr[], int left, int right) {
for (int i = left + 1; i <= right; i++) {
int key = arr[i];
int j = i - 1;
while (j >= left && arr[j] > key) {
arr[j + 1] = arr[j];
j--;
}
arr[j + 1] = key;
}
}

// 归并函数
void merge(int arr[], int left, int mid, int right) {
int len1 = mid - left + 1;
int len2 = right - mid;
int L[len1], R[len2];

    int i = 0, j = 0, k;
    for (i = 0; i < len1; i++)
        L[i] = arr[left + i];
    for (j = 0; j < len2; j++)
        R[j] = arr[mid + 1 + j];

    i = 0;
    j = 0;
    k = left;

    while (i < len1 && j < len2) {
        if (L[i] <= R[j])
            arr[k++] = L[i++];
        else
            arr[k++] = R[j++];
    }

    while (i < len1)
        arr[k++] = L[i++];
    while (j < len2)
        arr[k++] = R[j++];
    while(i>j && len1<len2)
    	arr[i++] = arr[j++];

}

// Timsort 算法
void timSort(int arr[], int n) {
for (int i = 0; i < n; i += MIN_RUN)
insertionSort(arr, i, (i + MIN_RUN - 1) < n ? (i + MIN_RUN - 1) : (n - 1));

    for (int size = MIN_RUN; size < n; size *= 2) {
        for (int left = 0; left < n; left += 2 * size) {
            int mid = left + size - 1;
            int right = (left + 2 * size - 1) < (n - 1) ? (left + 2 * size - 1) : (n - 1);
            merge(arr, left, mid, right);
        }
    }

}

测试代码生成需求优化版

目标:
为给定的排序函数编写单元测试,确保实现 100%的语句覆盖、判定覆盖、条件覆盖(CC)、条件/判定覆盖(C/DC)、条件组合覆盖(MCC)、修正判定条件覆盖(MCDC)
具体要求:

验证方法:
不通过预期结果与输出结果对比,而是通过遍历输出结果,判断数组是否已排序(即前一个数据是否比后一个小)。
测试报告:
打印出哪些测试用例通过了,哪些没通过。
生成一个详细的测试报告,包括测试用例名称和测试结果。
测试用例生成:
常规测试数据:
包含多种类型的数据,如空数组、单元素数组、已排序数组、逆序数组、含重复元素的数组、特殊值数组(如最大/最小值、负数、零等)。
这些数据应易于触发代码中的潜在问题。
超长随机数组:
生成两个长度分别为 5001000 的随机数组。
打印测试结果时标明测试数组长度。
连续长度随机数组:
生成长度从 100299200 组随机数组。
每组数组长度连续递增,打印测试结果时标明测试数组长度。
测试用例封装:
每个测试用例应封装成一个独立的函数,便于管理和复用。
内存管理:
使用 malloc 申请内存存放输入数据时,测试完成后要及时释放内存,避免内存泄漏。
测试代码与被测试代码分离:
假设被测试代码已经写好,只需编写测试代码。
测试代码应位于与被测试代码不同的文件中。
优化后的详细步骤:

编写测试框架:
创建一个测试框架,能够执行测试用例并记录结果。
提供函数用于打印测试报告。
实现测试用例:
常规测试用例:
空数组测试。
单元素数组测试。
已排序数组测试。
逆序数组测试。
含重复元素的数组测试。
特殊值数组测试(如全为正数、全为负数、混合正负数、含零等)。
超长随机数组测试用例:
生成并测试长度为 5001000 的随机数组。
连续长度随机数组测试用例:
生成并测试长度从 100299200 组随机数组。
验证排序结果:
在每个测试用例中,遍历排序后的数组,检查是否满足排序条件(前一个元素小于或等于后一个元素)。
内存管理:
确保在每个测试用例中,使用 malloc 分配的内存都在测试完成后被正确释放。
生成测试报告:
在所有测试用例执行完毕后,生成并打印详细的测试报告,包括每个测试用例的名称、测试结果(通过/失败)以及任何额外的信息(如数组长度)。
期望输出:

一个清晰的测试报告,显示所有测试用例的执行结果。
对于失败的测试用例,应提供足够的信息以便定位问题。

输出

为了实现你要求的测试框架和测试用例,我们将创建一个新的 C 文件来包含测试代码,并确保与被测试代码(即 timSort 函数)分离。以下是详细的实现步骤:
1. 测试框架
首先,我们创建一个测试框架,能够执行测试用例并记录结果。
c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
// 假设 timSort 函数声明在这里(通常通过包含头文件)
extern void timSort(int arr[], int n);
// 检查数组是否已排序
bool isSorted(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
if (arr[i] > arr[i + 1]) {
return false;
}
}
return true;
}
// 打印测试报告
void printTestReport(const char* testName, bool result, int length = -1) {
if (length == -1) {
printf("Test Case: %s - %s\n", testName, result ? "Passed" : "Failed");
} else {
printf("Test Case: %s (Length: %d) - %s\n", testName, length, result ? "Passed" : "Failed");
}
}
// 测试用例模板
void runTestCase(const char* testName, int* arr, int n) {
timSort(arr, n);
bool result = isSorted(arr, n);
printTestReport(testName, result, n);
// 释放内存(如果适用)
// 注意:在这个框架中,假设传入数组不是动态分配的,因此不需要释放
}
2. 实现测试用例
接下来,我们实现具体的测试用例。
c 复制代码
void testEmptyArray() {
int arr[0];
runTestCase("Empty Array", arr, 0);
}
void testSingleElementArray() {
int arr[1] = {1};
runTestCase("Single Element Array", arr, 1);
}
void testSortedArray() {
int arr[] = {1, 2, 3, 4, 5};
runTestCase("Sorted Array", arr, 5);
}
void testReverseSortedArray() {
int arr[] = {5, 4, 3, 2, 1};
runTestCase("Reverse Sorted Array", arr, 5);
}
void testArrayWithDuplicates() {
int arr[] = {3, 3, 3, 2, 2, 1, 1};
runTestCase("Array With Duplicates", arr, 7);
}
void testSpecialValuesArray() {
int arr[] = {INT_MAX, INT_MIN, 0, -1, 1, 99999, -99999};
runTestCase("Special Values Array", arr, 7);
}
void testLongRandomArray(int length) {
int* arr = (int*)malloc(length * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed for length %d\n", length);
return;
}
// 填充随机数
srand(time(NULL));
for (int i = 0; i < length; i++) {
arr[i] = rand() % 100000 - 50000; // 随机数范围 [-50000, 49999]
}
runTestCase("Long Random Array", arr, length);
// 释放内存
free(arr);
}
void testSequentialLengthRandomArrays() {
for (int length = 100; length < 300; length++) {
testLongRandomArray(length);
}
}
int main() {
// 运行常规测试用例
testEmptyArray();
testSingleElementArray();
testSortedArray();
testReverseSortedArray();
testArrayWithDuplicates();
testSpecialValuesArray();
// 运行超长随机数组测试用例
testLongRandomArray(500);
testLongRandomArray(1000);
// 运行连续长度随机数组测试用例
testSequentialLengthRandomArrays();
return 0;
}
3. 解释与注意事项
• 测试框架:runTestCase 函数用于执行排序并检查结果,同时打印测试报告。
• 测试用例:每个测试用例都是一个独立的函数,便于管理和复用。
• 内存管理:在 testLongRandomArray 中,使用 malloc 分配内存,并在测试完成后使用 free 释放内存。
• 随机数组生成:使用 rand 函数生成随机数,并用 srand(time(NULL))确保每次运行产生不同的随机数序列。
• 测试报告:printTestReport 函数用于打印每个测试用例的结果,包括名称、是否通过以及数组长度(如果适用)。
这个测试框架和测试用例覆盖了多种可能的输入情况,确保了较高的代码覆盖率,并能够帮助定位潜在的问题。

3.3 搭建测试环境

将 ai 生成的代码放到 test.c 中,并做一些优化。内容如下:

#define MIN_RUN 32

#define INT_MAX 2147483647
#define INT_MIN -2147483648

#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>

// 假设 timSort 函数声明在这里(通常通过包含头文件)
extern void timSort(int arr[], int n);

// 检查数组是否已排序
bool isSorted(int arr[], int n) {
for (int i = 0; i < n - 1; i++) {
if (arr[i] > arr[i + 1]) {
return false;
}
}
return true;
}

// 打印测试报告
void printTestReport(const char* testName, bool result, int length) {
if (length == -1) {
printf("Test Case: %s - %s\n", testName, result ? "Passed" : "Failed");
} else {
printf("Test Case: %s (Length: %d) - %s\n", testName, length, result ? "Passed" : "Failed");
}
}

// 测试用例模板
void runTestCase(const char_ testName, int_ arr, int n) {
timSort(arr, n);
bool result = isSorted(arr, n);
printTestReport(testName, result, n);
// 释放内存(如果适用)
// 注意:在这个框架中,假设传入数组不是动态分配的,因此不需要释放
}

void testEmptyArray() {
int arr[0];
runTestCase("Empty Array", arr, 0);
}

void testSingleElementArray() {
int arr[1] = {1};
runTestCase("Single Element Array", arr, 1);
}

void testSortedArray() {
int arr[] = {1, 2, 3, 4, 5};
runTestCase("Sorted Array", arr, 5);
}

void testReverseSortedArray() {
int arr[] = {5, 4, 3, 2, 1};
runTestCase("Reverse Sorted Array", arr, 5);
}

void testArrayWithDuplicates() {
int arr[] = {3, 3, 3, 2, 2, 1, 1};
runTestCase("Array With Duplicates", arr, 7);
}

void testSpecialValuesArray() {
int arr[] = {INT_MAX, INT_MIN, 0, -1, 1, 99999, -99999};
runTestCase("Special Values Array", arr, 7);
}

void testLongRandomArray(int length) {
int_ arr = (int_)malloc(length * sizeof(int));
if (arr == NULL) {
printf("Memory allocation failed for length %d\n", length);
return;
}

    // 填充随机数
    srand(time(NULL));
    for (int i = 0; i < length; i++) {
        arr[i] = rand() % 100000 - 50000; // 随机数范围 [-50000, 49999]
    }

    runTestCase("Long Random Array", arr, length);

    // 释放内存
    free(arr);

}

void testSequentialLengthRandomArrays() {
for (int length = 100; length < 300; length++) {
testLongRandomArray(length);
}
}

int main() {
// 运行常规测试用例
testEmptyArray();
testSingleElementArray();
testSortedArray();
testReverseSortedArray();
testArrayWithDuplicates();
testSpecialValuesArray();

    // 运行超长随机数组测试用例
    testLongRandomArray(500);
    testLongRandomArray(1000);

    // 运行连续长度随机数组测试用例
    testSequentialLengthRandomArrays();

    return 0;

}

将被测试代码放到 sort.c 中,编写一个脚本用于编译生成可执行文件,然后运行并生成测试覆盖率报告。测试目录内容如下:

https://i-blog.csdnimg.cn/direct/ce86b4806d5346288626569b273adf9f.png

其中 test.sh 的内容如下:

https://i-blog.csdnimg.cn/direct/424e7ce92740403a883f3d6363625669.png

sort.c 是被测试代码。

3.4 第 1 次测试

运行 test.sh 脚本,生成的测试报告如下:

https://i-blog.csdnimg.cn/direct/9e0ede6677a24cdca2582510ccf719e3.png

从测试报告可以看出,在处理长度为 129 的数组时,程序运行发生的错误,在 sort.c 文件中加一些打印信息用于调试,添加过程如下:

https://i-blog.csdnimg.cn/direct/e1b6dfdb841143c0ab7c35424a22da4e.png

然后只测试长度 129 的数组,运行结果如下:

https://i-blog.csdnimg.cn/direct/c1c18603d9574914a0630f7af2ce8701.png

可以看到出错原因是因为运行到这里时,mid 比 right 的值大,而 merge 函数内容如下:

https://i-blog.csdnimg.cn/direct/6c651483712c401a974356263db0ba7a.png

当 mid 大于 right 时,len2 的计算结果为负数,这使得声明数组 R 时出现错误。所以当 mid 大于 right 时,表示不能分成两段进行合并,因此就不需要合并。根据以上分析优化代码如下:

https://i-blog.csdnimg.cn/direct/7ae352e7e7aa4738b7f086e856a9d001.png

再一次测试,测试用例全部通过。

3.5 第 2 次测试

在分析第一个问题时发现,merge 函数中声明 L 数组和 R 数组的方式有些问题,代码如下:

https://i-blog.csdnimg.cn/direct/893afe86971c42f997eb0085ab277e80.png

这种方式声明的 L 数组和 R 数组存放在栈空间,栈空间相对较小,当输入的排序数组过大时,可能会出现内存溢出。为了验证是否会出现内存溢出,增加一个 100M 的输入数组用来测试。修改 test.c 内容如下:

https://i-blog.csdnimg.cn/direct/f56d8c05199a4199a5688f0fa22f9a01.png

用修改完的 test.c 文件进一步测试,发现果然在处理 100M 大数组时出现程序运行错误,如下:

https://i-blog.csdnimg.cn/direct/42ea86ccf43e46af9360c42c006746d1.png

根据以上分析,决定将 L 和 R 用 malloc 的方式申请内存,使其数据存放到内存较大的堆空间,修改代码如下:

https://i-blog.csdnimg.cn/direct/a6ba5b8175ae41a8934db798daf5f198.png

然后再一次测试发现测试用例全部通过,测试结果如下:

https://i-blog.csdnimg.cn/direct/52bd02c1000e419c8afc5daa12ea54a7.png

https://i-blog.csdnimg.cn/direct/ad7cfa1a3d4f4d9ea789d1ae90f0e024.png

这时查看代码运行覆盖率报告,结果如下:

https://i-blog.csdnimg.cn/direct/a097ff8ee07b4ca3b7ea52a2ef8a202f.png

可以看到其中有一段代码没有被运行到。

3.5 第 3 次测试

以上所写操作方式并不是第一次就是这么做的,经历过多次的迭代,初期让 ai 生成测试代码时,并没有附加多条要求,这时 ai 生成的测试代码总是有几条无法覆盖。将覆盖率报告反馈给 ai 让其优化测试代码时,ai 指出来一段错误代码,如下:

https://i-blog.csdnimg.cn/direct/f0a9e211c6284d78a79ba0d343ba9935.png

原始代码如下:

https://i-blog.csdnimg.cn/direct/c2b67e995a9d46d2a56f12bcac559ede.png

前经过前面的代码处理后,这时应该 i=len1、j=len2,所以不会出现(i>j && len1<len2)的情况。而且前面步骤已经将数据处理完毕,所以 45 行、46 行是错误的代码行。

将错误代码删除,修改如下:

https://i-blog.csdnimg.cn/direct/53fc68d901724f689814d70e1d332165.png

用修改之后的代码再测试,此时测试用例全部通过,并且代码运行覆盖率为 100%,结果如下:

https://i-blog.csdnimg.cn/direct/ffd2fcc6cd224bb69a17c09f6b7b791a.png

https://i-blog.csdnimg.cn/direct/4be16c72b51949a3baf2810c3bdeb0f5.png