背景介绍
目前有很多测试架构,大部分是python的,比如unittest、pytest、robot framework,但是基于tcl的很少,就只有一个tcltest,并且功能太弱,所有就自己开发了一个,取名为OutTest。目前已经把该架构发布到了github上,gitbhub的链接:OurTest on Github。
以这个测试架构为例子,带大家一起实现自动化测试架构。
测试架构的基本功能
测试架构都有几点核心功能:
- 发现并运行多个测试;
- 一个脚本可以包含多个测试,组成文件测试套,实际项目中一般一个小功能点对应一个文件测试套;
- 一个文件夹可以包含多个文件测试套,实际项目中一般一个特性对应一个文件夹,当然一大特性也可以对应多个小特性,也就是说一个大文件里面可以包含多个文件夹;
- 每个文件夹都有自己的前置配置和后置清理,每个文件也有自己的前置配置和后置清理,每个测试也有自己的前置配置和后置清理
- 标记测试通过或者失败的关键字
- 生成测试报告
OurTest架构功能
OurTest架构包含上面描述的全部功能,大文件里面可以包含子文件夹,并且支持多层套圈,每个层次的文件夹里面都可以包含脚本文件和子文件夹,一个脚本文件包含多个测试。其核心功能是由以下几个函数实现的。
testcase函数
该函数需要传入以下的几部分内容
- caseid用于唯一区分测试的字符串
- condition运行条件,目前是norun或者always,如果norun测试结果会标记为skip
- description用于描述测试功能的描述
- setup_script 是前置配置脚本
- body_script测试脚本主体
- teardown_script是后置清理脚本,后置清理脚本必须一个语句一行,如果需要一个语句多行在末尾加上反斜杠,因为为了保证清理干净,每个语句都要执行,即使前面出现异常,也会每个语句执行
一个testcase语句的调用组成一个测试,在测试脚本主体里面可以调用标记测试结果的函数。参考最后的文件测试脚本示例。
结果标记函数
这边提供了两个函数,一个是checking,checking需要传递一个描述性的字符串,说明将要到来的判断是什么,怎么判断的;另一个是expect,需要三个参数,一个是cnt(内容),一个是opt(操作),一个是ptn(模式),就是内容是否等于、不等于、match或者regmatch后面的模式。
比如,cnt是“this is result get from device in test”,要判断里面有没有result这个字符串,可以这样:
checking "判断结果里面是否包含result"
expect "this is result get from device in test" match *result*
opt可以是:
eq: 判断cnt和ptn是否相等
neq:判断cnt和ptn不相等
match:判断cnt是否glob匹配ptn
regmatch:判断cnt是否正则匹配ptn
contain:判断cnt是否包含ptn
SuiteSetup函数,前置配置
这个文件测试套的前置配置,需要放在所有testcase的调用的前面,用于整体文件测试的前置配置
SuiteTeardown函数,后置清理
这是整个文件后置清理,需要放在所有testcase的调用的后面,这个也是跟case teardown一样一行一行调用的,保证一个语句一行,无论前面是否异常,每个语句都会执行。
脚本的运行
单个脚本运行
单个脚本直接可以运行,执行脚本测试前会自动化探测当前文件夹和高层级的文件夹里面是否有SuiteSetup.tcl文件,执行所有的文件夹级别的前置配置。执行结束的时候,会自动化探测当前文件夹和高层级的文件夹里面是否有SuiteTeardown.tcl文件,执行所有的文件夹级别的后置清理。
测试集运行
测试集运行需要调用run_test,如果调用run_test脚本放在最高层文件夹里面,会运行该文件夹里面的所有测试,文件测试套的名字需要以.test.tcl或者_test.tcl结尾。如果是测试文件的文件名不以这两个结尾也可以把自己的匹配模式作为参数传递给run_test,也可以以参数的形式指定要运行的文件夹里面的测试套。
run_test有两个参数:第一个参数是文件夹列表,也就是包含测试的文件夹列表;另外一个是被看作是测试套文件的文件名匹配模式。两个参数的参数是当前文件夹中的以.test.tcl或者_test.tcl结尾的文件
package require OurTest
namespace import OurTest::*
config_log_file_id [open log.txt w]
config_report_file_id [open report.csv w]
our_puts "hello, nice to see you!"
run_test
make_clear
日志
默认的情况下,运行日志只在标准输出中,如果在运行脚本前配置了日志文件id(用config_log_file_id函数),那边日志会同时输出到日志文件里面;如果配置了测试报告文件id(用config_report_file_id函数),那么会生成一个csv文件。例如:
package require OurTest
namespace import OurTest::*
#配置日志文件
config_log_file_id [open log.txt w]
#配置测试报告文件
config_report_file_id [open report.csv w]
run_test
#关闭日志文件和测试报告文件
make_clear
如果想在日志文件和标准输出中输出信息,用成员函数our_puts,例如下面的our_puts语句,在测试日志里面输出“OurTest Tue May 07 14:29:56 CST 2024: hello, nice to see you!”
package require OurTest
namespace import OurTest::*
config_log_file_id [open log.txt w]
config_report_file_id [open report.csv w]
#
our_puts "hello, nice to see you!"
run_test
make_clear
测试套文件结构示例
文件测试脚本示例
package require OurTest
namespace import OurTest::*
######################################################################################
#setup
######################################################################################
SuiteSetup {
puts "suite setup of pppoe_test_suite_1."
}
######################################################################################
#Test procedure
######################################################################################
testcase test_case_1 always {test_case_1 of pppoe_test_suite_1.} {
puts "setup of test_case_1 in pppoe_test_suite_1"
} {
checking "Marking test result step 1 for contain"
expect 112 contain 1
checking "Marking test result step 2"
expect 1 eq 1
checking "Marking test result step 3"
expect 1 ne 2
checking "Marking test result step 4"
expect "results string" rmatch .*stri.*
checking "Marking test result step 5"
expect "results string" match *stri*
} {
puts "teardown of test_case_1 in pppoe_test_suite_1"
}
testcase test_case_2 always {test_case_2 of pppoe_test_suite_2.} {
puts "setup of test_case_2 in pppoe_test_suite_1"
} {
checking "Marking test result step 1"
expect 1 eq 1
checking "Marking test result step 2"
expect 1 ne 2
checking "Marking test result step 3"
expect "results string" rmatch .*stri.*
checking "Marking test result step 4"
expect "results string" match *stri*
} {
puts "teardown of test_case_2 in pppoe_test_suite_1"
}
testcase test_case_3 always {test_case_3 of pppoe_test_suite_3.} {
puts "setup of test_case_3 in pppoe_test_suite_1"
} {
checking "Marking test result step 1"
expect 1 eq 1
checking "Marking test result step 2"
expect 1 ne 2
checking "Marking test result step 3"
expect "results string" rmatch .*stri.*
checking "Marking test result step 4"
expect "results string" match *stri*
} {
puts "teardown of test_case_3 in pppoe_test_suite_1"
}
######################################################################################
#Test teardown
######################################################################################
SuiteTeardown {
puts "suite teardown of pppoe_test_suite_1."
}