当前位置: 首页 > Linux

Zsh开发指南(Part21测试方法和编写可测试代码的方法)

时间:2023-04-07 00:15:07 Linux

介绍正式场景下,代码写完之后需要进行测试,shell脚本也不例外。但是shell脚本的特性使得测试方法不同于其他语言。单元测试作为一种重要的测试方法,在许多编程语言的程序测试中占有重要的地位。不幸的是,单元测试基本上不适用于shell脚本。并不是说shell脚本不能进行单元测试,而是单元测试需要大量的投入才能检测出很少的问题。为了对shell脚本进行单元测试,可能需要将50行代码重写为100多行或更多。更重要的是,shell脚本对外部环境的依赖性很强,大部分问题只能通过对脚本整体进行功能测试才能发现,而不是对单个功能进行单元测试。在单元测试上投入的精力可能会减少在功能测试上投入的精力。因此,不建议对shell脚本进行单元测试。这不仅会让开发者非常痛苦,也很难降低出问题的几率,甚至可能适得其反。单个脚本的功能测试shell脚本的最小测试粒度是单个脚本。必须保证单个脚本易于测试,不能将多个脚本耦合得太紧以至于难以单独测试其中一个。主要逻辑脚本所依赖的外部环境必须易于模拟。例如,需要从数据库中读取数据,对数据进行处理,然后写入文件。这些功能不能在同一个脚本中完成。因为数据库的外部环境不容易模拟,会给测试带来困难。读写数据库的功能需要分离成单独的脚本。该功能应尽可能简单。测试脚本时,只需要关心数据是否正常读取,格式转换是否正确等,不需要关心数据处理的具体逻辑。处理数据的主要逻辑代码应独立形成一个(或多个)脚本。测试脚本时,无需准备数据库环境,直接将读取数据库的脚本替换为其他脚本或数据文件,提供测试数据。如果写入文件的环境复杂(例如文件或目录结构复杂,或者要写入分布式文件系统等),写入文件的脚本也需要分离,以便更容易测试。无法手动完成具有主要逻辑的脚本的功能测试。必须编写测试脚本并可以自动运行。每次脚本更改后都会执行回归测试。待项目稳定后,每次代码提交后可自动运行测试脚本。测试脚本必须涵盖正常和异常情况,而不仅仅是正常情况。异常的数量取决于脚本的复杂性。对于外部依赖复杂的脚本,功能一定要单一,逻辑尽量简单,代码尽量稳定,不经常改动。例如读写数据库、启动和停止进程、复杂的目录文件操作等外部依赖复杂的脚本,必须功能单一,只与特定的外部依赖进行交互,提供与外部依赖一样独立的中间数据可能来自外部依赖,尽量不要包含外部依赖。上下文无关的逻辑。这样的脚本应该很容易模拟,以便测试的其他部分不需要依赖外部环境。对于外部依赖复杂的脚本,可以编写脚本进行自动测试或手动测试。测试需要包括正常和异常情况,而不仅仅是正常情况。功能测试示例需要编写脚本完成以下功能:如果进程process1和process2都存在,则将process2的cwd目录下的data/output.txt作为输入,做一些复杂的处理,然后输出到process2的cwd目录下process1data/input.txt文件(如果该文件已经存在则不处理),处理完后删除之前的data/output.txt。分析:process1和process2都是复杂的外部依赖,在主逻辑脚本中不能直接依赖,所以检查进程是否存在的逻辑应该分离到单独的脚本中。输入和输出文件的路径取决于进程路径。为了方便测试,获取文件路径的逻辑也应该单独放到一个单独的脚本中。脚本函数实现:util.zsh脚本检查进程是否存在,获取进程的cwd目录:#!/bin/zshcheck_process(){pidof$1}get_process_cwd(){readlink/proc/$1/cwd}主要逻辑脚本main.zsh:#!/bin/zsh#有错误就退出,可以省去很多错误处理代码set-e#切换到脚本当前目录cd${0:h}#加载依赖脚本source./util.zsh#检查进程是否存在localprocess1_pid=$(check_processprocess1)localprocess2_pid=$(check_processprocess2)#这里输入输出相对于脚本是localinput_file=$(get_process_cwd$process2_pid)/data/output.txtlocaloutput_file=$(get_process_cwd$process1_pid)/data/input.txt#如果输入文件不存在,直接退出[[-e$input_file]]||{未找到echo$input_file。exit1}#如果输出文件已经存在,直接退出[[-e$output_file]]&&{echo$output_filealreadyexists.exit0}#处理$input_file的内容#Omit#将结果输出到$output_file#Omit函数测试方法:util.zsh中的两个函数太简单了,无法测试。在测试main.zsh的时候,需要构造一系列的util.zsh来模拟各种情况进行测试:#如果进程存在check_process(){echo$$}#如果进程不存在check_process(){return1}#进程process1存在,process2不存在check_process(){[[$1==process1]]&&echo1234&&return[[$1==process2]]&&return1}#输出的是进程号,但实际是进程不存在Check_process(){echo0}#其他情况#省略#当路径存在时get_process_cwd(){[[$1==process1]]&&echo/path/to/cwd1&&return[[$1==process2]]&&echo/path/to/cwd2&&return}#如果路径不存在get_process_cwd(){return1}#输出路径,但路径实际不存在get_process_cwd(){echo/wrong/path}#Other情况#Omit然后结合这些情况写一个测试脚本来判断main.zsh的处理是否达到预期。测试脚本示例之一:util_test1.zsh内容:#!/bin/zsh#进程存在check_process(){echo$$}#直接返回正确路径get_process_cwd(){[[$1==process1]]&&echo/path/to/cwd1&&return[[$1==process2]]&&echo/path/to/cwd2&&return}test.zsh内容:#!/bin/zsh#用于测试的函数可以独立成单独的脚本重用assert_ok(){(($1==0))||{echoError,retcode:$1exit1}}check_output_file(){#检查输出文件是否符合预期#Omit}#Applyutil_test1.zshln-sfutil_test1.zshutil.zsh#运行脚本。/main.zsh#检查返回值是否正常assert_ok$?#检查输出文件是否符合预期check_output_file/path/to/output/file#其他检查#省略#应用util_test2。zshln-sfutil_test2.zshutil.zsh#省略集成测试测试完各个脚本的功能后,需要将各个脚本与其他程序集成,测试相互调用的过程是否正常。如果功能比较复杂,需要批量集成,测试各个逻辑单元是否能正常工作。在这部分测试中,如果与外部环境交互的脚本逻辑比较简单,则没有必要参与,而是使用模拟脚本。可以手动或自动测试。也不要只测试正常情况。系统测试集成了所有相关组件来测试整个系统或子系统的功能。模拟脚本不能参与系统测试,必须使用真实的外部环境。系统测试通常需要手动完成,可以借助自动化测试系统进行辅助。需要覆盖尽可能多的案例,而不仅仅是测试系统的正常功能。总结本文简要介绍了如何测试shell脚本以及如何编写可测试的代码。本文不再更新,全系列文章更新维护在这里:github.com/goreliu/zshguideWindows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua等相关问题的付费解决方案,定价灵活,欢迎咨询,微信ly50247。