先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新软件测试全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
如果你需要这些资料,可以添加V获取:vip1024b (备注软件测试)
正文
*/
public Map<String,String> readAllProperties(){
//保存所有的键值
Map<String,String> map=new HashMap<String,String>();
Enumeration<?> en = props.propertyNames();
while (en.hasMoreElements()) {
String key = (String) en.nextElement();
String property = props.getProperty(key);
map.put(key, property);
}
return map;
}
到这里,解决了配置读取的问题,还需要解决代码运行时如何让它自己去选择正确的集群配置文件的问题。我是将选择配置文件的逻辑全部封装到了一个工厂类BaseConfigFactory.java中,在实际测试使用时,我只需要通过工厂类的静态方法BaseConfigFactory.getInstance()去获取想要的配置信息,而不需要关心它到底是如何去选择正确的配置文件的。工厂类的实现可以参考:
public class BaseConfigFactory {
private static final String testEnv= System.getenv(“TEST_ENV”) == null ? “null” : System.getenv(“TEST_ENV”);
private static Logger logger = Logger.getLogger(BaseConfigFactory.class);
private static BaseConfig baseConfig;
private static HashMap<String, String> clusterConfigMap;
public static synchronized BaseConfig getInstance(){
if (null == baseConfig){
PropertyConfigurator.configure(“log4j.properties”);
initMap();
setupConfig();
}
return baseConfig;
}
public static void initMap(){
clusterConfigMap = new HashMap<>();
clusterConfigMap.put(“TEST-BJ”, “test-bj.properties”);
clusterConfigMap.put(“ONLINE-BJ”, “online-bj.properties”);
clusterConfigMap.put(“ONLINE-XS”, “online-xs.properties”);
clusterConfigMap.put(“ONLINE-LT”, “online-lt.properties”);
clusterConfigMap.put(“ONLINE-BEIJING”, “online-beijing.properties”);
clusterConfigMap.put(“ONLINE-HD”, “online-hd.properties”);
clusterConfigMap.put(“null”, “test-local.properties”);
}
public static void setupConfig(){
logger.info("TEST ENV: " + testEnv);
String propertyFile = clusterConfigMap.get(testEnv);
logger.info(“Using '” + propertyFile + “’ as property file.”);
baseConfig = new BaseConfig(propertyFile);
}
}
即,将所有的集群的配置放入到一个Map中,然后通过读取环境变量TEST_ENV的值来选取具体的集群配置文件clusterConfigMap.get(testEnv)。
3.2.3 log4j日志管理
良好的日志输出是帮助定位问题的关键环节,尤其是定位服务器上执行时出现的问题。这边贴一个log4j的配置:
set log levels
log4j.rootLogger = debug, stdout, D, E
输出到控制台
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS Z} %p [%c{1}] [Thread-%t] %m%n
输出到日志文件
log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File = logs/console.log
log4j.appender.D.Append = true
##输出Debug级别以上的日志##
log4j.appender.D.Threshold = INFO
log4j.appender.D.layout = org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS Z} %p [%c{1}] [Thread-%t] %m%n
保存异常信息到单独文件
log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
##异常日志文件名##
log4j.appender.E.File = logs/error.log
log4j.appender.E.Append = true
##只输出ERROR级别以上的日志##
log4j.appender.E.Threshold = ERROR
log4j.appender.E.layout = org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern=%d{yyyy/MM/dd HH:mm:ss.SSS Z} %p [%c{1}] [Thread-%t] %m%n
##Hibernate日志级别设置
log4j.logger.org.hibernate.ps.PreparedStatementCache=WARN
log4j.logger.org.hibernate=ERROR
Changing the log level to DEBUG will result in Hibernate generated
SQL to be logged.
log4j.logger.org.hibernate.SQL=ERROR
Changing the log level to DEBUG will result in the PreparedStatement
bound variable values to be logged.
log4j.logger.org.hibernate.type=ERROR
该配置将INFO级别和ERROR级别的日志分别定位输出到不同的文件,且日志文件会按照日期进行自动归档,输出的格式包含了日志的日期、级别、类信息、线程信息、日志内容等。
一般情况下,对于接口测试,当接口测试用例失败时,我们要打印的日志包括:请求的url、参数、方法、实际响应、期望响应等等。
3.3 分层设计、解耦
首先看一下项目的工程目录:
可以看到,项目中包含了多个package,各个package的作用已经在图片中标示了。以前好多测试人员的习惯是将api代码的调用、测试方法的编写、data Provider的编写、测试数据的构造全部写在一个类文件中,这样做其实会有几个问题:
- 可读性差
- 代码复用性低
- 维护性差
- 难以调试
- 耦合带来的其它各类问题
此外,如果不同集群的测试数据不同,会有大量的if判断,结果是灾难性的。
下面以一个用例为例,展示代码的结构:
测试api:
public class ScheduleApi extends BaseAzkabanApi{
…
…
/**
- 使用默认公共账号、email、失败策略、sla报警邮箱新增正常调度。
- @param projectName
- @param flow
- @param projectId
- @param scheduleTime
- @param scheduleDate
- @param period
- @return
*/
public ResponseCode addNormSched(String projectName, String flow, String projectId, String scheduleTime, String scheduleDate,String period){
return scheduleFlow(projectName, flow, projectId, scheduleTime, scheduleDate, defaultProxyUser, defaultProxyEmail, period, defaultSlaEmail);
}
…
…
}
测试代码test:
@Test(singleThreaded=true)
public class ScheduleTest{
…
…
/**
- 新增正常调度
- @param projectName
- @param flow
*/
@Test(priority=1, dataProvider=“addNormSched”, dataProviderClass=ScheduleDataProvider.class, testName=“1410356”)
public void addNormSched(String projectName, String flow, String expectedStatus, String hasScheduleId, String message){
ResponseCode rc= scheduleApi.addNormSched(projectName, flow);
Assert.assertEquals(rc.getStatus(), expectedStatus, message+rc.getDebugInfo(“返回结果中的状态status对应值”));
Assert.assertEquals(rc.hasProperty(“scheduleId”), Boolean.parseBoolean(hasScheduleId), message+rc.getDebugInfo(“返回结果中是否包含scheduleId”));
}
…
…
}
测试用例dataProvider:
public class ScheduleDataProvider {
@DataProvider(name = “addNormSched”, parallel=true)
public static Object [][] addNormSched(){
return new Object[][]{
ScheduleTestData.validNormSchedule,
ScheduleTestData.notExistedProject,
ScheduleTestData.notExistedFlow
};
}
…
…
}
测试数据testdata:
public class ScheduleTestData extends BaseTestData{
…
…
//Testdata for addNormSched
public static Object[] validNormSchedule={VALID_PROJECT_NAME, VALID_NORMAL_SCHEDULE_FLOW, “success”, “true”, “设置有效的正常调度”};
public static Object[] notExistedProject={NOT_EXIST_PROJECT_NAME, VALID_NORMAL_SCHEDULE_FLOW, “error”, “false”, “不存在的project”};
public static Object[] notExistedFlow={VALID_PROJECT_NAME, NOT_EXIST_FLOW_NAME, “error”, “fasle”, “不存在的flow”};
…
…
}
可以看到,用例的测试代码test类是非常简洁的,只要调用api类封装的接口,然后进行assert判断即可。
关于测试数据,将dataprovider与testdata进行分离,也是为了后续可能会灵活地调整下架用例,只需要去除dataprovider类中的用例行即可,而testdata中的数据仍然可以留着复用。
另外,前面提到了不同集群测试数据的管理。再介绍下我这边的实现方式:
- 不同测试类使用的公共数据,存放于BaseTestData基类中,让其它testdata类继承于基类
- 不同集群可以共用的数据,尽量共用,以常量的方式存储于testdata类中
- 不同集群无法共用的数据,统一存放于特定的json文件管理
关于json文件管理数据,其实跟配置文件的管理类似,如下图所示:
History.json:
{
“validTotalFetch”:{
“key”:“”,
“beginTime”:“2017-06-30%2015:30”,
“endTime”:“2017-06-30%2015:50”,
“expectedTotal”:“7”
},
“validImmediatelyFetch”:{
“key”:“instant_execute_job”,
“beginTime”:“2017-06-30%2013:30”,
“endTime”:“2017-06-30%2013:40”,
“expectedTotal”:“1”
},
“validScheduledFetch”:{
“key”:“online_schedule_job”,
“beginTime”:“2017-06-30%2014:30”,
“endTime”:“2017-06-30%2014:40”,
“expectedTotal”:“2”
}
}
3.4 改进与提升
在自动化的实施过程中,还遇到了一些问题可能对其它项目也会有一定的借鉴意义。这边罗列下几个我觉得比较有意思的问题。
3.4.1 webserver高可用的支持
我们的后台webserver是支持高可用的,所以每次运维上线后webserver的host可能会发生变化,以及在服务运行过程中也可能会发生webserver切换。如果每次去手动调整自动化用例的配置信息,是一件非常麻烦的事情。
解决的方式就是在配置文件中,将主从webserver的host都填写进去,在测试过程中,如果发生请求失败,则允许切换一次host。
3.4.2 用例并发执行
由于我们的一部分用例是异步的场景用例,需要执行一个数据开发的任务,然后等待其执行完成。这些用例的执行比较费时,如果顺序执行的话会消耗非常多的时间。因此可以通过并发执行测试的方式,解决用例耗时的问题。
3.4.3 单例模式解决session问题和host重复切换问题
- 问题1: Azkaban的每个接口,都需要一个必传参数seesion。这个session可以通过/login接口获取。如果每个接口在执行的时候都去调用一次/login接口重新获取session,就会显得很冗余,也可能导致旧的session失效。
- 问题2: 上述提到的对webserver高可用的支持,当多条用例并行执行时如果同时去切换host,可能会造成host切换回原来的不可用host。
对于问题1,可以将session作为单例的方式进行存储。
对于问题2,可以借鉴单例模式的“双重检查”思想,对切换host的代码进行部分同步,在防止host重复切换的同时,不会降低httpclient请求的并发性。
3.4.4 “变”与“不变”
其实这也是所有设计模式的基本思想,即区分自动化测试中的“可变因素”和“不变因素”。我觉得ycwdaaaa大神(飞哥)有两句话是非常棒的:
- 封装"一切"可能的可控的变化因素
- 为了稳定使尽"一切"手段
4. 结合研发过程的应用
上面介绍了一些自动化的实施过程,这边再介绍下实施之后在项目研发过程中的应用。
目前在项目中,主要有以下几方面的应用。
(1)提测后的自动化回归验收
下图是项目的一条持续集成pipeline。在开发提测后,我会自动化地完成以下事情:
- 编译代码
- 将服务部署到各个机器,并完成Jacocod Agent的部署
- 执行静态代码检查
- 执行接口测试
- 完成覆盖率统计
- 将覆盖率统计数据接入到CR平台
当自动化用例全部执行通过时,说明系统的核心功能回归没有问题,然后开始版本的细粒度功能的测试。
(2)Bug修复后的回归验收
在测试过程中,开发肯定会经常修复bug重新提交代码,每次有代码重新提交时,我都可以一键完成部署、测试、覆盖率统计。
(3)上线后的回归验证
目前,项目的上线验证已经完全由自动化验证来替代。
(4)作为开发冒烟的一部分(未完成)
目前已经跟开发达成一致,开发非常欢迎将自动化用例接入到开发环境,用于他们每次变更时的环境正确性验证,可以尽早帮助他们发现研发过程中出现的问题。并且在提测前,只有100%通过自动化测试才可以进行提测。
(5)线上监控
目前各个线上集群,都部署了自动化测试用例,这部分用例会每隔4小时执行一次。用于确保线上环境的稳定性。从效果上来看,线上监控的成效是非常明显的,提前发现了很多集群的延迟问题,环境问题等,让开发可以及时地收到报警,了解线上集群的情况。
(6)关于持续集成
可能有人会发现,上述的执行过程其实不是真正意义上的持续集成,真正意义上的持续集成应该是:每次开发提交代码,自动触发构建。
必须要承认的是,确实是如此。但是不管怎么样,我觉得可以先从优化测试工作量的角度慢慢去推开整个流程,其实业界目前也并没有确切的定论说只有持续集成才是最佳的实践。相反,一味地持续集成可能会增加我们的维护成本。只要我们能切实提升自己的工作效率,达到目的就可以了。
5. 成效
当自动化做的比较完善后,你真的会发现:生活原来可以变得如此简单美好。
自动编译部署:测试过程中开发修复bug提交代码是非常频繁的,每次的手动编译部署可能都会耗费十几分钟,并且测试人员的关注点还不能离开。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注软件测试)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
们的维护成本。只要我们能切实提升自己的工作效率,达到目的就可以了。
5. 成效
当自动化做的比较完善后,你真的会发现:生活原来可以变得如此简单美好。
自动编译部署:测试过程中开发修复bug提交代码是非常频繁的,每次的手动编译部署可能都会耗费十几分钟,并且测试人员的关注点还不能离开。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注软件测试)
[外链图片转存中…(img-zHcYR6ya-1713254201951)]
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!