导师让我添加自测用例,请问这种scanf输入的代码,如何添加自测用例#include<stdio.h> #define BIT_DIGIT_32 32 unsigned long pattern_replace(unsigned long input_int) { int bit1,bit2,bit3=0; /*从高位起,自每一位扫描连续的三位*/ for(int k = BIT_DIGIT_32-1; k >= 2; k--){ bit1 = (input_int >> k) & 1; bit2 = (input_int >> (k - 1)) & 1; bit3 = (input_int >> (k - 2)) & 1; if (1 == bit1 && 0 == bit2 && 1 == bit3){ /*“101”——“011”,修改k位为0,k-1位为1*/ input_int = input_int & ~(1 << k); input_int = input_int | (1 << (k - 1)); /*避免重复修改,跳过后两个k*/ k -= 2; } } return input_int; } int main() { unsigned long n; printf("请输入一个32位正整数:\n"); if (scanf("%lu", &n) != 1){ printf("输入无效,请输入一个合法的整数!\n"); return 1; } n = pattern_replace(n); printf("%lu\n", n); return 0; }
时间: 2025-08-05 21:06:31 AIGC 浏览: 14
<think>我们面对的问题是如何为使用`scanf`输入的C语言程序添加单元测试用例。由于`scanf`直接从标准输入(stdin)读取数据,这使得在单元测试中直接调用函数变得困难,因为我们需要模拟输入数据。
解决方案通常包括:
1. 重构代码,将输入部分与处理逻辑分离,使得我们可以直接测试处理逻辑。
2. 使用重定向或模拟标准输入的方法。
参考引用[2]提到,`fscanf`可以从文件流读取数据,这给我们一个思路:我们可以将程序改为从文件读取,或者使用重定向。但在单元测试中,我们更希望不改变原有代码结构,因此可以考虑使用重定向或者模拟输入。
具体方法:
方法一:使用重定向(在测试时临时重定向stdin)
- 在测试代码中,我们可以将stdin重定向到一个文件,这样调用`scanf`时就会从文件读取数据。
- 但这种方法需要创建临时文件,并且可能影响其他测试。
方法二:使用管道或内存文件(更推荐)
- 在C语言中,我们可以使用`freopen`将stdin重定向到一个文件流,或者使用`dup2`将管道重定向到stdin。
- 另一种方法是使用内存文件(如Linux下的`fmemopen`,但这不是标准C函数)。
方法三:重构代码,将输入来源抽象为函数参数
- 修改函数,使其接受一个`FILE*`参数,这样在测试时我们可以传递一个文件流(包含测试数据)而不是stdin。
- 例如,将原来的:
void my_function() {
int a;
scanf("%d", &a);
// ... 处理逻辑
}
改为:
void my_function(FILE *input) {
int a;
fscanf(input, "%d", &a);
// ... 处理逻辑
}
这样在main函数中调用时传入`stdin`,而在测试时传入一个文件流(包含测试数据)。
方法四:使用宏定义在测试时替换`scanf`
- 在测试环境下,我们可以定义宏将`scanf`替换为从我们指定的缓冲区读取的函数。但这种方法可能不够灵活。
考虑到单元测试的可靠性和代码的可维护性,方法三(重构代码)是最为推荐的。
然而,如果无法修改原有代码(比如需要测试的是一个已经写死的函数),我们可以考虑使用重定向。下面分别给出两种方法的示例。
示例1:重构代码(方法三)
假设我们有一个函数,它读取两个整数并求和:
原始代码:
```c
#include <stdio.h>
void sum() {
int a, b;
scanf("%d %d", &a, &b);
printf("%d\n", a+b);
}
int main() {
sum();
return 0;
}
```
重构后:
```c
#include <stdio.h>
void sum(FILE *input) {
int a, b;
fscanf(input, "%d %d", &a, &b);
printf("%d\n", a+b); // 注意:输出还是stdout,测试时可能需要重定向stdout
}
int main() {
sum(stdin);
return 0;
}
```
现在,我们可以为`sum`函数编写单元测试(使用CUnit等测试框架):
```c
// 测试用例
void test_sum(void) {
// 创建一个临时文件,写入测试输入
FILE *input = fopen("test_input.txt", "w+");
if (input == NULL) {
// 错误处理
}
fprintf(input, "10 20\n"); // 写入测试数据
rewind(input); // 将文件指针重置到文件开头
// 重定向stdout到文件,以捕获输出(可选,如果需要检查输出)
// 这里我们假设我们直接检查函数内部状态或通过其他方式验证,或者我们也可以重定向stdout
// 但为了简单,我们这里只关注函数执行(不检查输出),或者我们可以修改函数使其返回结果。
// 调用函数
sum(input);
// 检查输出?这里我们无法直接获取,所以可能需要进一步重构将结果返回或通过其他方式验证。
// 因此,更好的做法是让函数返回结果,而不是直接打印。
fclose(input);
}
```
但是,如果函数是直接输出到stdout,我们可以重定向stdout到一个文件,然后读取该文件来检查输出。例如:
进一步重构,让函数返回结果:
```c
int sum(FILE *input) {
int a, b;
fscanf(input, "%d %d", &a, &b);
return a+b;
}
// 这样测试时就可以直接检查返回值了。
```
示例2:使用重定向(方法一)测试整个程序
如果我们不想重构,可以测试整个程序(即通过运行程序并检查输出)。这更像是集成测试。
在测试代码中,我们可以:
1. 创建一个临时文件,写入测试输入。
2. 将stdin重定向到这个文件(使用`freopen`)。
3. 运行程序。
4. 重定向stdout到一个文件,然后读取该文件来检查输出。
示例代码(在测试函数中):
```c
#include <stdio.h>
#include <stdlib.h>
void test_program() {
// 准备输入文件
FILE *in = fopen("test_in.txt", "w+");
fprintf(in, "10 20\n");
rewind(in);
// 重定向stdin
freopen("test_in.txt", "r", stdin);
// 重定向stdout到文件
freopen("test_out.txt", "w", stdout);
// 调用主函数(注意:这里需要调用原来的main函数,但main函数只能有一个,所以需要调整)
// 一种方法是将main函数改名为其他名字,然后在这里调用。
// 或者,我们可以单独编译整个程序,然后在测试中运行该程序(使用system或exec),然后检查输出文件。
// 这里演示在同一个程序中调用main函数(需要将main改名为testable_main,并声明)
testable_main(); // 假设我们将main函数改名为testable_main
// 恢复重定向(可选,但后续测试可能需要)
freopen("/dev/tty", "r", stdin);
freopen("/dev/tty", "w", stdout);
// 读取输出文件
FILE *out = fopen("test_out.txt", "r");
char buffer[100];
fgets(buffer, 100, out);
// 检查输出是否为"30"
// 使用断言比较buffer和"30\n"
fclose(in);
fclose(out);
}
```
注意:这种方法比较复杂,而且需要修改main函数的名字(或者将整个程序作为一个进程启动),并且需要处理文件I/O。
综上所述,推荐的重构步骤:
1. 将输入部分从业务逻辑中分离,使用`FILE*`作为输入源。
2. 在业务函数中,使用`fscanf`代替`scanf`。
3. 在main函数中,传入`stdin`。
4. 在测试中,我们可以创建文件流(可以是实际文件,也可以是内存文件)传递给函数,并检查函数的返回值或输出(如果输出到stdout,也可以重定向stdout并捕获输出)。
对于输出,同样可以重构:将输出改为`fprintf`到一个`FILE*`,这样在测试时可以重定向到文件或字符串缓冲区。
另外,引用[3]中展示了使用文件进行输入输出的示例,我们可以借鉴其文件操作。
如果使用内存文件(如GNU扩展的`fmemopen`),则不需要实际文件,但这样会降低可移植性。
因此,在可移植性要求高的情况下,使用临时文件是可行的。
最后,如果使用C++,我们可以使用`std::istringstream`等来模拟输入,但这里要求C语言,所以只能使用文件流。
总结:为使用`scanf`的C程序添加单元测试,最可行且推荐的方法是重构代码,将输入来源参数化(改为`fscanf`和`FILE*`)。
相关问题:
1. 如何在C语言单元测试中模拟标准输入?
2. 如何测试C语言中输出到标准输出的函数?
3. 在C语言中,如何将stdin重定向到字符串(不通过文件)?
4. 如何编写可测试的C语言代码?</think>### 如何为使用`scanf`的C语言函数添加单元测试
为使用`scanf`输入的函数添加单元测试,核心思路是**模拟标准输入(stdin)数据流**。以下是三种实用方法:
---
### 方法一:重构代码使用`FILE*`参数(推荐)
通过将输入源抽象为`FILE*`参数,使函数不再依赖`stdin`,便于注入测试数据。
```c
// 原始函数(依赖stdin)
void process_input() {
int a, b;
scanf("%d %d", &a, &b);
// ...处理逻辑...
}
// 重构后(可测试版本)
void process_input_stream(FILE *input) {
int a, b;
fscanf(input, "%d %d", &a, &b); // 关键修改:用fscanf替代scanf
// ...处理逻辑...
}
// 测试用例示例
void test_process_input() {
// 创建模拟输入流
FILE *test_input = fopen("test_input.txt", "w+");
fprintf(test_input, "10 20\n"); // 写入测试数据
rewind(test_input); // 重置文件指针
// 注入模拟输入
process_input_stream(test_input);
// 验证输出结果...
fclose(test_input);
}
```
**优点**:不改变外部行为,通过参数注入实现测试[^2]。
---
### 方法二:临时重定向`stdin`
使用`freopen`将标准输入重定向到临时文件:
```c
void test_with_redirect() {
// 创建临时输入文件
FILE *tmp_in = fopen("test_input.txt", "w+");
fprintf(tmp_in, "5 7\n");
rewind(tmp_in);
// 重定向stdin
freopen("test_input.txt", "r", stdin);
// 调用被测函数
process_input(); // 原函数直接使用scanf
// 恢复stdin(可选)
freopen("/dev/tty", "r", stdin); // Linux/macOS
// freopen("CON", "r", stdin); // Windows
fclose(tmp_in);
}
```
**注意**:需处理平台特定的终端设备路径[^3]。
---
### 方法三:使用管道实现内存重定向(Linux/macOS)
通过`popen`/`pclose`在内存中模拟输入流:
```c
void test_with_pipe() {
// 创建可写管道
FILE *virtual_input = popen("echo '3 4'", "r");
// 重定向stdin
int saved_stdin = dup(fileno(stdin)); // 备份原stdin
dup2(fileno(virtual_input), fileno(stdin));
// 调用被测函数
process_input();
// 恢复stdin
dup2(saved_stdin, fileno(stdin));
pclose(virtual_input);
}
```
**优点**:无需物理文件,但跨平台兼容性较差。
---
### 关键注意事项
1. **输出验证**:若函数有`printf`输出,可类似重定向`stdout`到文件并验证内容
2. **缓冲区刷新**:在重定向前调用`fflush(stdin)`避免残留数据
3. **错误处理**:测试非法输入(如字符代替数字),验证`scanf`返回值:
```c
if (scanf("%d", &a) != 1) {
/* 处理输入错误 */
}
```
4. **多用例管理**:使用测试框架(如Unity)组织多个测试用例[^4]
> **重构建议**:优先采用方法一,它符合软件工程的高内聚低耦合原则,使代码更健壮且易于维护[^1]。
---
阅读全文
相关推荐



















