第四章:测试语言基础(上)——变量与数据类型、运算符与表达式、控制流语句

各位同学,欢迎来到测试语言基础的第一部分。说实话,很多刚入行的工程师觉得语言基础太简单,不就是变量、循环、判断吗?但我在ATE测试这个行当里摸爬滚打这么多年,可以负责任地告诉你:基础不牢,地动山摇。测试程序里一个变量类型用错,可能让你排查三天三夜。

4.1 变量与数据类型——给数据找个合适的“家”

变量是什么?说白了,就是内存里的一块空间,用来存数据。但ATE测试对数据类型的要求,比普通软件开发要严格得多。为什么?因为我们要跟硬件寄存器、测试仪器打交道,数据位宽、符号、精度,差一点都不行。

4.1.1 基本数据类型

我习惯把测试语言的数据类型分成三类:

  • 整数型:int、short、long。ATE里最常用的是int,32位有符号。但注意,有些测试机台的编译器int是16位的,踩过坑的人都知道。
  • 浮点型:float、double。测电压、电流、频率这些模拟量,必须用浮点。我个人建议能用double就别用float,精度差一个数量级,关键时刻会坑你。
  • 字符型:char、string。存测试项名称、bin号、日志信息。

重点提醒:ATE测试程序里,intdouble是绝对的主力。我见过有人用float存电压值,结果因为精度不够,导致良率误判。嗯,那场面,挺尴尬的。

4.1.2 变量声明与初始化

变量声明这件事,看起来简单,但有个原则我必须强调:先声明,后使用。而且,一定要初始化

// 正确的做法
int test_count = 0;
double vdd_voltage = 0.0;
char* test_name = "OPEN_SHORT";

// 我曾经见过这样的代码
int result;
// ... 中间隔了200行
if (result == 1) {  // result根本没赋值!
    // 逻辑永远不会执行
}

避坑指南:我曾经在调试一个量产程序时,发现某个测试项偶尔会误判。查了两天,最后发现是一个局部变量没初始化,每次调用时它的值是随机的。从那以后,我声明变量必初始化,成了强迫症。

4.1.3 作用域与生命周期

变量不是在哪都能用的。全局变量、局部变量、静态变量,它们的“活动范围”不一样。

类型作用域生命周期ATE场景举例
全局变量整个文件程序运行期间测试系统配置参数
局部变量函数内部函数执行期间循环计数器
静态变量函数内部程序运行期间测试项调用次数统计

你想想看,如果把一个循环计数器声明成全局变量,多线程调用时会发生什么?数据错乱!所以,能用局部就别用全局,这是铁律。

4.2 运算符与表达式——让数据“动”起来

变量是食材,运算符就是厨具。怎么把原始数据加工成我们想要的测试结果?靠的就是运算符。

4.2.1 算术运算符

加减乘除、取模,这些大家都熟。但ATE测试里有个特殊场景:除法要小心

int a = 5;
int b = 2;
double c = a / b;  // 结果是2.0,不是2.5!
double d = (double)a / b;  // 这才是2.5

为什么会这样?因为整数除法会截断小数部分。我在项目中遇到过有人用整数除法算频率,结果所有测试值都偏小,整批货差点报废。

4.2.2 关系运算符与逻辑运算符

判断测试通过还是失败,全靠它们。

  • 关系运算符:>、<、>=、<=、==、!=
  • 逻辑运算符:&&、||、!

个人经验:判断浮点数相等,千万别用 ==!因为浮点精度问题,0.1 + 0.2 可能不等于 0.3。我习惯这样写:fabs(a - b) < 0.0001。记住这个写法,能救你命。

4.2.3 位运算符

这是ATE测试的“杀手锏”。寄存器操作、标志位判断、数据打包解包,全得靠它。

// 读取寄存器bit3的值
unsigned int reg_value = 0xA5;  // 1010 0101
int bit3 = (reg_value >> 3) & 0x01;  // 结果是0

// 设置bit5为1
reg_value |= (1 << 5);  // 变成1100 0101

// 清除bit2
reg_value &= ~(1 << 2);  // 变成1100 0001

嗯,这里要注意:位运算一定要用无符号类型。有符号数的右移是算术右移,会补符号位,结果跟你想象的可能完全不一样。

4.3 控制流语句——让程序有“脑子”

没有控制流的程序,就是一条道走到黑。测试程序需要判断、需要循环、需要根据条件选择不同的测试路径。

4.3.1 if-else 判断

最基础的条件判断。但ATE测试里,if-else 用得特别多,因为每个测试项都要判断是否在规格范围内。

double measured_voltage = 1.25;
double vmin = 1.20;
double vmax = 1.30;

if (measured_voltage >= vmin && measured_voltage <= vmax) {
    // 测试通过
    printf("PASS: Voltage = %.3fV\n", measured_voltage);
} else {
    // 测试失败
    printf("FAIL: Voltage = %.3fV (out of range)\n", measured_voltage);
}

避坑指南:我曾经在写多条件判断时,把 && 写成了 &。结果逻辑完全错了,一批良率90%的货被误判成50%。从那以后,我写条件判断都会多看一眼运算符。

4.3.2 switch-case 多分支

当判断条件很多时,switch-case 比 if-else 更清晰。比如根据测试bin号执行不同操作:

int bin = get_test_bin();

switch (bin) {
    case 1:
        // 良品处理
        break;
    case 5:
    case 6:
        // 参数偏差,需要复测
        break;
    case 10:
        // 致命失效,直接报废
        break;
    default:
        // 未知bin,报警
        break;
}

注意,别忘了 break!忘了的话,程序会“穿透”执行下一个case。我刚开始写程序时就犯过这个错,调试了一下午才发现。

4.3.3 for 循环

ATE测试里,for循环是绝对的主力。遍历所有测试项、重复测量多次取平均、扫描多个电压点……全得靠它。

// 测量5次取平均值
double sum = 0.0;
int i;

for (i = 0; i < 5; i++) {
    sum += measure_voltage();
}

double average = sum / 5.0;

我个人习惯把循环变量 i 声明在循环外面。为什么?因为有些编译器在循环结束后,i 的值还能用,方便调试。

4.3.4 while 循环

当循环次数不确定时,用 while。比如等待测试仪器准备好:

int timeout = 1000;
while (!is_instrument_ready() && timeout > 0) {
    delay(1);  // 等待1ms
    timeout--;
}

if (timeout == 0) {
    printf("ERROR: Instrument timeout!\n");
}

重要提醒:while循环一定要有退出条件,否则就是死循环。我在调试一个老化测试程序时,忘了加超时机制,结果程序卡在等待仪器响应上,整个测试批次都停了。嗯,那次被领导骂得不轻。

4.3.5 break 与 continue

这两个是循环的“控制杆”。

  • break:跳出整个循环
  • continue:跳过本次循环,进入下一次
// 找到第一个失败的测试项就停止
for (i = 0; i < total_tests; i++) {
    if (run_test(i) == FAIL) {
        printf("First failure at test %d\n", i);
        break;  // 跳出循环
    }
}

// 跳过不需要测试的项目
for (i = 0; i < total_tests; i++) {
    if (is_test_skipped(i)) {
        continue;  // 跳过本次
    }
    run_test(i);
}

4.4 综合实战:一个简单的测试流程

好了,理论讲完了。咱们来写一个完整的测试片段,把今天学的都用上。

// 变量声明与初始化
int test_count = 0;
int pass_count = 0;
int fail_count = 0;
double voltage_reading = 0.0;
const double VDD_MIN = 1.10;
const double VDD_MAX = 1.30;

// 测试主循环
for (test_count = 0; test_count < 100; test_count++) {
    // 测量电压
    voltage_reading = measure_vdd();
    
    // 判断是否在范围内
    if (voltage_reading >= VDD_MIN && voltage_reading <= VDD_MAX) {
        pass_count++;
        printf("Test %d: PASS (%.3fV)\n", test_count, voltage_reading);
    } else {
        fail_count++;
        printf("Test %d: FAIL (%.3fV)\n", test_count, voltage_reading);
        
        // 连续失败3次,终止测试
        if (fail_count >= 3) {
            printf("Too many failures, aborting...\n");
            break;
        }
    }
}

// 输出统计结果
printf("Total: %d, Pass: %d, Fail: %d\n", test_count, pass_count, fail_count);

这段代码虽然简单,但包含了变量声明、常量定义、for循环、if-else判断、break控制。你想想看,如果把这个逻辑扩展到几百个测试项、几十个参数,是不是就能撑起一个完整的量产测试程序了?

最后说一句:语言基础就像盖房子的地基。我见过太多工程师,急着学高级特性、学仪器控制,结果连个变量作用域都搞不清楚,写出来的程序bug百出。把今天的内容消化透,后面的课程你会学得轻松很多。