准备工作数据库校验用户准备工作
Posted wsfj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了准备工作数据库校验用户准备工作相关的知识,希望对你有一定的参考价值。
准备工作
1、添加依赖
<!--redis依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--fastjson依赖--> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.33</version> </dependency> <!--jwt依赖--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
2、添加Redis相关配置
import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.serializer.SerializerFeature; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.type.TypeFactory; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.SerializationException; import com.alibaba.fastjson.parser.ParserConfig; import org.springframework.util.Assert; import java.nio.charset.Charset; /** * Redis使用FastJson序列化 * * @author sg */ public class FastJsonRedisSerializer<T> implements RedisSerializer<T> public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class<T> clazz; static ParserConfig.getGlobalInstance().setAutoTypeSupport(true); public FastJsonRedisSerializer(Class<T> clazz) super(); this.clazz = clazz; @Override public byte[] serialize(T t) throws SerializationException if (t == null) return new byte[0]; return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); @Override public T deserialize(byte[] bytes) throws SerializationException if (bytes == null || bytes.length <= 0) return null; String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz); protected JavaType getJavaType(Class<?> clazz) return TypeFactory.defaultInstance().constructType(clazz);
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig @Bean @SuppressWarnings(value = "unchecked", "rawtypes" ) public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class); // 使用StringRedisSerializer来序列化和反序列化redis的key值 template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(serializer); // Hash的key也采用StringRedisSerializer的序列化方式 template.setHashKeySerializer(new StringRedisSerializer()); template.setHashValueSerializer(serializer); template.afterPropertiesSet(); return template;
3、响应类
import com.fasterxml.jackson.annotation.JsonInclude; @JsonInclude(JsonInclude.Include.NON_NULL) public class ResponseResult<T> /** * 状态码 */ private Integer code; /** * 提示信息,如果有错误时,前端可以获取该字段进行提示 */ private String msg; /** * 查询到的结果数据, */ private T data; public ResponseResult(Integer code, String msg) this.code = code; this.msg = msg; public ResponseResult(Integer code, T data) this.code = code; this.data = data; public Integer getCode() return code; public void setCode(Integer code) this.code = code; public String getMsg() return msg; public void setMsg(String msg) this.msg = msg; public T getData() return data; public void setData(T data) this.data = data; public ResponseResult(Integer code, String msg, T data) this.code = code; this.msg = msg; this.data = data;
4、工具类
import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Base64; import java.util.Date; import java.util.UUID; /** * JWT工具类 */ public class JwtUtil //有效期为 public static final Long JWT_TTL = 60 * 60 *1000L;// 60 * 60 *1000 一个小时 //设置秘钥明文 public static final String JWT_KEY = "sangeng"; public static String getUUID() String token = UUID.randomUUID().toString().replaceAll("-", ""); return token; /** * 生成jtw * @param subject token中要存放的数据(json格式) * @return */ public static String createJWT(String subject) JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间 return builder.compact(); /** * 生成jtw * @param subject token中要存放的数据(json格式) * @param ttlMillis token超时时间 * @return */ public static String createJWT(String subject, Long ttlMillis) JwtBuilder builder = getJwtBuilder(subject, ttlMillis, getUUID());// 设置过期时间 return builder.compact(); private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; SecretKey secretKey = generalKey(); long nowMillis = System.currentTimeMillis(); Date now = new Date(nowMillis); if(ttlMillis==null) ttlMillis=JwtUtil.JWT_TTL; long expMillis = nowMillis + ttlMillis; Date expDate = new Date(expMillis); return Jwts.builder() .setId(uuid) //唯一的ID .setSubject(subject) // 主题 可以是JSON数据 .setIssuer("sg") // 签发者 .setIssuedAt(now) // 签发时间 .signWith(signatureAlgorithm, secretKey) //使用HS256对称加密算法签名, 第二个参数为秘钥 .setExpiration(expDate); /** * 创建token * @param id * @param subject * @param ttlMillis * @return */ public static String createJWT(String id, String subject, Long ttlMillis) JwtBuilder builder = getJwtBuilder(subject, ttlMillis, id);// 设置过期时间 return builder.compact(); public static void main(String[] args) throws Exception String token = "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiJjYWM2ZDVhZi1mNjVlLTQ0MDAtYjcxMi0zYWEwOGIyOTIwYjQiLCJzdWIiOiJzZyIsImlzcyI6InNnIiwiaWF0IjoxNjM4MTA2NzEyLCJleHAiOjE2MzgxMTAzMTJ9.JVsSbkP94wuczb4QryQbAke3ysBDIL5ou8fWsbt_ebg"; Claims claims = parseJWT(token); System.out.println(claims); /** * 生成加密后的秘钥 secretKey * @return */ public static SecretKey generalKey() byte[] encodedKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; /** * 解析 * * @param jwt * @return * @throws Exception */ public static Claims parseJWT(String jwt) throws Exception SecretKey secretKey = generalKey(); return Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(jwt) .getBody();
import java.util.*; import java.util.concurrent.TimeUnit; @SuppressWarnings(value = "unchecked", "rawtypes" ) @Component public class RedisCache @Autowired public RedisTemplate redisTemplate; /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 */ public <T> void setCacheObject(final String key, final T value) redisTemplate.opsForValue().set(key, value); /** * 缓存基本的对象,Integer、String、实体类等 * * @param key 缓存的键值 * @param value 缓存的值 * @param timeout 时间 * @param timeUnit 时间颗粒度 */ public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) redisTemplate.opsForValue().set(key, value, timeout, timeUnit); /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout) return expire(key, timeout, TimeUnit.SECONDS); /** * 设置有效时间 * * @param key Redis键 * @param timeout 超时时间 * @param unit 时间单位 * @return true=设置成功;false=设置失败 */ public boolean expire(final String key, final long timeout, final TimeUnit unit) return redisTemplate.expire(key, timeout, unit); /** * 获得缓存的基本对象。 * * @param key 缓存键值 * @return 缓存键值对应的数据 */ public <T> T getCacheObject(final String key) ValueOperations<String, T> operation = redisTemplate.opsForValue(); return operation.get(key); /** * 删除单个对象 * * @param key */ public boolean deleteObject(final String key) return redisTemplate.delete(key); /** * 删除集合对象 * * @param collection 多个对象 * @return */ public long deleteObject(final Collection collection) return redisTemplate.delete(collection); /** * 缓存List数据 * * @param key 缓存的键值 * @param dataList 待缓存的List数据 * @return 缓存的对象 */ public <T> long setCacheList(final String key, final List<T> dataList) Long count = redisTemplate.opsForList().rightPushAll(key, dataList); return count == null ? 0 : count; /** * 获得缓存的list对象 * * @param key 缓存的键值 * @return 缓存键值对应的数据 */ public <T> List<T> getCacheList(final String key) return redisTemplate.opsForList().range(key, 0, -1); /** * 缓存Set * * @param key 缓存键值 * @param dataSet 缓存的数据 * @return 缓存数据的对象 */ public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key); Iterator<T> it = dataSet.iterator(); while (it.hasNext()) setOperation.add(it.next()); return setOperation; /** * 获得缓存的set * * @param key * @return */ public <T> Set<T> getCacheSet(final String key) return redisTemplate.opsForSet().members(key); /** * 缓存Map * * @param key * @param dataMap */ public <T> void setCacheMap(final String key, final Map<String, T> dataMap) if (dataMap != null) redisTemplate.opsForHash().putAll(key, dataMap); /** * 获得缓存的Map * * @param key * @return */ public <T> Map<String, T> getCacheMap(final String key) return redisTemplate.opsForHash().entries(key); /** * 往Hash中存入数据 * * @param key Redis键 * @param hKey Hash键 * @param value 值 */ public <T> void setCacheMapValue(final String key, final String hKey, final T value) redisTemplate.opsForHash().put(key, hKey, value); /** * 获取Hash中的数据 * * @param key Redis键 * @param hKey Hash键 * @return Hash中的对象 */ public <T> T getCacheMapValue(final String key, final String hKey) HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash(); return opsForHash.get(key, hKey); /** * 删除Hash中的数据 * * @param key * @param hkey */ public void delCacheMapValue(final String key, final String hkey) HashOperations hashOperations = redisTemplate.opsForHash(); hashOperations.delete(key, hkey); /** * 获取多个Hash中的数据 * * @param key Redis键 * @param hKeys Hash键集合 * @return Hash对象集合 */ public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) return redisTemplate.opsForHash().multiGet(key, hKeys); /** * 获得缓存的基本对象列表 * * @param pattern 字符串前缀 * @return 对象列表 */ public Collection<String> keys(final String pattern) return redisTemplate.keys(pattern);
import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class WebUtils /** * 将字符串渲染到客户端 * * @param response 渲染对象 * @param string 待渲染的字符串 * @return null */ public static String renderString(HttpServletResponse response, String string) try response.setStatus(200); response.setContentType("application/json"); response.setCharacterEncoding("utf-8"); response.getWriter().print(string); catch (IOException e) e.printStackTrace(); return null;
5、实体类
import java.io.Serializable; import java.util.Date; @Data @AllArgsConstructor @NoArgsConstructor public class User implements Serializable private static final long serialVersionUID = -40356785423868312L; /** * 主键 */ private Long id; /** * 用户名 */ private String userName; /** * 昵称 */ private String nickName; /** * 密码 */ private String password; /** * 账号状态(0正常 1停用) */ private String status; /** * 邮箱 */ private String email; /** * 手机号 */ private String phonenumber; /** * 用户性别(0男,1女,2未知) */ private String sex; /** * 头像 */ private String avatar; /** * 用户类型(0管理员,1普通用户) */ private String userType; /** * 创建人的用户id */ private Long createBy; /** * 创建时间 */ private Date createTime; /** * 更新人 */ private Long updateBy; /** * 更新时间 */ private Date updateTime; /** * 删除标志(0代表未删除,1代表已删除) */ private Integer delFlag;
数据库校验用户准备工作
从之前的分析我们可以知道,我们可以自定一个UserDetailsService,让SpringSecurity使用我们的UserDetailsService。我们自己的UserDetailsService可以从数据库中查询用户名和密码。
准备工作
我们先创建一个用户表,建表语句如下:
CREATE TABLE `sys_user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT \'主键\', `user_name` VARCHAR(64) NOT NULL DEFAULT \'NULL\' COMMENT \'用户名\', `nick_name` VARCHAR(64) NOT NULL DEFAULT \'NULL\' COMMENT \'昵称\', `password` VARCHAR(64) NOT NULL DEFAULT \'NULL\' COMMENT \'密码\', `status` CHAR(1) DEFAULT \'0\' COMMENT \'账号状态(0正常 1停用)\', `email` VARCHAR(64) DEFAULT NULL COMMENT \'邮箱\', `phonenumber` VARCHAR(32) DEFAULT NULL COMMENT \'手机号\', `sex` CHAR(1) DEFAULT NULL COMMENT \'用户性别(0男,1女,2未知)\', `avatar` VARCHAR(128) DEFAULT NULL COMMENT \'头像\', `user_type` CHAR(1) NOT NULL DEFAULT \'1\' COMMENT \'用户类型(0管理员,1普通用户)\', `create_by` BIGINT(20) DEFAULT NULL COMMENT \'创建人的用户id\', `create_time` DATETIME DEFAULT NULL COMMENT \'创建时间\', `update_by` BIGINT(20) DEFAULT NULL COMMENT \'更新人\', `update_time` DATETIME DEFAULT NULL COMMENT \'更新时间\', `del_flag` INT(11) DEFAULT \'0\' COMMENT \'删除标志(0代表未删除,1代表已删除)\', PRIMARY KEY (`id`) ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COMMENT=\'用户表\'
引入MybatisPuls和mysql驱动的依赖
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency>
配置数据库信息
spring: datasource: url: jdbc:mysql://localhost:3306/sg_security?characterEncoding=utf-8&serverTimezone=UTC username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver
定义Mapper接口
public interface UserMapper extends BaseMapper<User>
修改User实体类
类名上加@TableName(value = "sys_user") ,id字段上加 @TableId
配置Mapper扫描
@SpringBootApplication @MapperScan("com.sangeng.mapper") public class SimpleSecurityApplication public static void main(String[] args) ConfigurableApplicationContext run = SpringApplication.run(SimpleSecurityApplication.class); System.out.println(run);
添加junit依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
测试MP是否能正常使用
@SpringBootTest public class MapperTest @Autowired private UserMapper userMapper; @Test public void testUserMapper() List<User> users = userMapper.selectList(null); System.out.println(users);
接口自动化测试流程和相关准备工作
第一步: 拿到需求文档、UI交互图(原型图)、数据库表设计文档、接口文档
1问:为什么要拿到这些文档资料呢?
1答:
①.《需求文档》,明确定义了:各个表单字段的限制条件;相关场景逻辑校验;
②.《UI交互图》,明确定义了:各单页面需展示的数据;页面之间的交互;
③.《数据表设计文档》,结合UI图和需求文档,明确定义了:表字段规则、表N多N关系(一对一、一对多、多对多);
④.《接口文档》,结合需求文档和UI和数据表,明确定义了:接口名,各个入参值,各个返回值,和其他相关信息;
2细节:
①.大多数公司,针对这四类文档资料都整理的不规范,或者没能及时更新;
②.这会导致接口测试用例的编写没有一个绝对可靠的需求来源;
③.因为,接口测试用例本质是针对各个表字段的校验;
④.所以需求文档里对各个表单字段的限制条件,产品人员务必都要写清楚,不要遗漏限制条件;
⑤.而接口文档里针对各个用例场景的返回值,文档里务必都要及时填写和更新;
⑥.针对接口文档返回值的字段:比如code=0表示登录成功,code=1表示密码错误;code=2表示无网络;code=3表示账号错误,等等类似code值有不同含义;
3注意:
①.所以,在写接口测试用例之前,务必要先核对需求文档和接口文档,以最正确的需求文档和接口文档,来编写接口用例,才能得到最正确的结果;
②.先实现单接口解耦;后续按照业务场景组合多个接口;
第二步: 拿到个人编写的接口用例模板,针对特定单接口编写接口用例
1重点:要及时更新接口用例模板.xlsx,保证用例模板的准确性;
2假设有一个后台系统banner广告位子模块,以特定单接口-createBanner-新增banner,来讲解编写单接口的接口用例的操作流程:
(1)新建一个xlsx后缀的excel文档,excel名改为:banner.xlsx
(2)打开banner.xlsx,第一个sheet页名称改为:创建banner
(3)打开接口用例模板.xlsx,把名称为【接口测试demo】的sheet页里的所有内容复制到banner.xlsx的名称为【创建banner】的空sheet页;
(4)在名称为【创建banner】的sheet页里,做如下步骤的操作:
(4-1)针对第一行各单元格数据(第一行各单元格是用来填写相关参数名和用于标识的参数名):
①按照实际需求来灵活配置:操作入参名所在的单元格;操作返回参数名所在的单元格;
入参名都用红色字体表示;返回参数名都用绿色字体表示;
比如:修改某个入参名,删除多余的一个入参名所在的一个列,新增一个列来填写一个新的入参名;
比如:修改某个返回参数名,删除多余的一个返回参数名所在的一个列,新增一个列来填写一个新的返回参数名;
②跟入参名跟返回参数名所在的单元格无关的单元格,都保持原位置和原单元格名称即可。
( 当然也支持任意移动位置和更改单元格名,但不建议更改单元格名因为更改了单元格名后代码也要跟着变动)
(4-2)针对第二行各单元格数据(第二行各单元格是用来填写默认值):
①单元格【验证字段】的值:不填,为空;
②单元格【接口地址】的默认值值是接口相对路径,结合实际情况比如更改为:/api/b.banner/creBanner,用于拼接该接口请求的绝对路径;
③单元格【请求方式】的默认值值是请求方式,结合实际情况比如更改为:post;
④单元格【编号】的值:不填,为空;
⑤单元格【返回值和断言结果的所在行下标】的值:不填,为空;
⑥单元格【用例类型】的值:不填,为空;
⑦单元格【用例目的】的值:不填,为空;
⑧单元格【用例名】的值:不填,为空;
⑨单元格【返回值】的值:不填,为空;
⑩单元格【断言结果】的值:不填,为空;
细节1:如果入参名所在的单元格和返回参数名所在的单元格,单元格值可以重复且出现频率高,那就可以填写默认值;
细节2:如果入参名所在的单元格和返回参数名所在的单元格,单元格值不可以重复只能是唯一值,那就单元格值为空;
(4-3)针对第三行及大于第三行的各单元格数据(用于填写具体值):
结合接口用例模板.xlsx的名为【接口测试用例模板】sheet页称为A,在banner.xlsx的名为【创建banner】sheet页称为B,进行灵活填写;
操作步骤:
①针对某个接口字段,复制A的三列数据【用例类型】【用例目的】【示范的用例名】,复制给B的三列数据【用例类型】【用例目的】【用例名】;
②然后删除不需要的用例名对应的行数据;
③更改各条剩下的用例名,比如更改字段名和相关数字等数据;(基本99%都要替换掉从模板复制过来的数据);
④B的这列数据【接口地址】,务必都填写DF;
⑤B的这列数据【请求方式】,务必都填写DF;
⑥B的这列数据【编号】,务必都填写文本形式的数字且数字递增,值从01开始(文本形式的数字可以再excel里设置);
⑦B的这列数据【返回值和断言结果的所在行下标】,务必都填写数字且数字递增,值从2开始;
⑧B的这列数据【验证字段】,灵活按照实际入参名来填写;
⑨B的两列数据【code】【data】都务必结合接口文档和需求文档,填写正确的值;(若哪些值觉得不合理,后期可以让产品或开发人员进行修改);
⑩B的两列数据【code】【msg】都务必结合接口文档和需求文档,填写正确的值;(若哪些值觉得不合理,后期可以让产品或开发人员进行修改);
细节1:DF表示默认值,取值于第二行各自的单元格值(在脚本里有做相关转化和校验);
细节2:&$,表示一个变量,由脚本内部赋值。比如: variable = "$count": 666 ;
细节3:B的两列数据【返回值】【断言结果】都默认不填写,这两列数据都是由相关脚本返回的值;
细节4: 相关颜色的标注,可结合实际来灵活填写;
细节5: 列数据【data】和列数据【msg】不可能同时有值;
细节6:待补充列表字段【预期值】,把【code】+【data】&【code】+【msg】这样组合为一个dict,回传写入【banner(包含接口返回值和断言结果).xlsx】;
第三步: 结合项目框架,做相关流程的脚本操作
(1)第一,执行必须操作的步骤(按项目二级目录的排序顺序来执行)
1. 在二级模块名【/data】内:编写脚本d_add_banner.py; (在子类D_add_banner的父类属性variable可结合banner.xlsx内的接口用例赋值情况,来重写该variable属性值;)
2. 在二级模块名【/excel】内:存放banner.xlsx;
3.在二级模块名【/expectedResult】内:编写脚本e_add_banner ; (第一次编写子类E_add_banner时,直接继承且不重写任何一个父类方法;调试期间,可按照实际重写父类相关方法;);
4. 在二级模块名【/model】内:编写脚本m_add_banner.py; (第一次编写子类M_add_banner.py时,直接继承且不重写任何一个父类方法;调试期间,可按照实际重写父类相关方法;)
5. 在二级模块名【/optimize】内:编写脚本o_add_banner.py; (第一次编写子类O_add_banner.py时,直接继承且不重写父类的sleep方法;调试期间,可按照实际重写父类的sleep方法;)
6. 在二级模块名【/validate】内:编写脚本v_add_banner.py;(第一次编写子类V_add_banner.py时,直接继承且不重写父类的compareResult方法;调试期间,可按照实际重写父类的compareResult方法;)
7. 在二级模块名【/writeCellValue】内:编写脚本w_add_banner.py;
细节:各步骤对应的类都相对解耦,数据源基本都来自同个上游接口--d_add_banner.py内的子类D_add_banner所调用的父类方法excel_data()的返回值;
(2)第二,再执行非必须操作的步骤(按项目二级目录的排序顺序来执行)
1. 在二级模块名【/fileAttribute】内:编写脚本f_add_banner.py;
2. 在二级模块名【/public】内:存放图片视频等相关测试数据;
(3)第三,针对单接口createBanner,调试接口请求
1. 在二级模块名【/controller】内:编写脚本c_add_banner.py; (第一次编写C_add_banner.py时,务必对某个特定的父类方法进行重写,比如:父类方法add,父类方法update;)
细节1: 相关入参值都采取参数化;
细节2:在【if __name__ == ‘__main__‘:】下方区域,进行单元测试的调试;
细节3:用于调试的数据源data,可以在【d_add_banner.py内的子类D_add_banner所调用的父类方法excel_data()的返回值】这边获取,获取符合要求的其中一条数据来当做数据源;
(4)第四,针对单接口createBanner,结合ddt,遍历执行所有的接口测试用例
1. 在二级模块名【/testcase】内:编写脚本test_001_add_banner_testcase.py;
细节1:可直接复制其余现成的脚本,用ctrl+R快捷键统一替换相关关键字;
细节2:针对不同接口,脚本需增加/减少特定的代码;(比如【修改】接口可能比【新增接口】需要多调用sleep方法,防止程序执行过快导致接口请求异常返回报错信息)
细节3:成功执行该脚本后,会在二级模块名【/excel】里生成对应的【banner(包含接口返回值和断言结果).xlsx】,会包含每条接口用例的返回值和断言结果;
(5)第五,核对生成的数据
1. 查看【banner(包含接口返回值和断言结果).xlsx-【创建banner】sheet页的 返回值和断言结果】,大概看一下返回值的数据是否正确;如果有错误的返回值,则继续排查和优化相关脚本;
(6)第六,执行单一入口函数
步骤:
1.配置根目录run.py相关参数信息;
2.执行run.py
3.生成html格式的测试报告;并,发送相关报告至指定邮箱;
后期拓展:
1.部署线上jenkins服务,部署本地/线上python环境,部署本地/线上wamp环境,CI持续集成;
2.熟悉相关linux命令;优化相关脚本逻辑;
3.部署线上禅道服务,实现主要功能:实时写入bug&获取bug清单&更改bug状态,下载最新包含符合筛选条件的bug的excel文档;
python相关核心脚本如下:
1 # coding:utf-8 2 ‘‘‘ 3 @file: test_002_update_banner_testcase.py 4 @author: jingsheng hong 5 @ide: PyCharm 6 @createTime: 2019年07月29日 10点21分 7 @contactInformation: 727803257@qq.com 8 ‘‘‘ 9 10 11 import unittest 12 import ddt 13 from data.b.banner.d_update_banner import D_update_banner 14 from controller.b.banner.c_update_banner import C_update_Banner 15 from expectedResult.b.banner.e_update_banner import E_update_banner 16 from validate.b.banner.v_update_banner import V_update_banner 17 from optimize.b.banner.o_update_banner import O_update_banner 18 from writeCellValue.b.banner.w_update_banner import W_update_banner 19 20 excel_data = D_update_banner().excel_data() 21 22 @ddt.ddt 23 class Test_update_banner(unittest.TestCase): 24 ‘‘‘【更新banner】接口的接口测试用例集合‘‘‘ 25 26 def setUp(self): 27 pass 28 29 def tearDown(self): 30 pass 31 32 @ddt.data(*excel_data) 33 def test_update_banner(self,data): 34 O_update_banner(data).sleep() 35 O_update_banner(data).printAllParamsLog() 36 expectResult = E_update_banner(data).update() 37 actualResult = C_update_Banner(data).update() 38 W_update_banner(data).writeReturnValue(actualResult) 39 O_update_banner(data).printLog(expectResult,actualResult) 40 compareResult = V_update_banner(expectResult,actualResult).compareResult() 41 assertResult = V_update_banner(expectResult,actualResult).assertResult() 42 W_update_banner(data).writeAssertResult(assertResult) 43 self.assertTrue(compareResult) 44 45 if __name__ =="__main__": 46 unittest.main()
php相关控制层脚本如下:
1 <?php 2 /** 3 * Created by PhpStorm. 4 * User: Administrator 5 * Date: 2019/03/25 6 * Time: 19:02 7 */ 8 9 namespace app\api\controller\b; 10 11 use app\api\controller\ApiCommon; 12 use app\api\controller\utils\File; 13 use app\api\model\ModelBanner; 14 use app\api\validate\V_Banner; 15 use think\Db; 16 use think\Response; 17 18 class Banner extends ApiCommon 19 20 21 /** 后台查询banner列表(可传id获取单个banner信息) 22 * @return \think\Response 23 */ 24 public function showBannerA() 25 $id = input(‘param.id‘); 26 $page = input(‘param.page‘); 27 $last = input(‘param.last‘); 28 //实例化content,查询all 29 $mBanner = new ModelBanner(); 30 $list = $mBanner->showBannerA($id,$page,$last); 31 $count = $mBanner->countBanner(); 32 list_upload_image_path_format($list,‘image‘); 33 if ($list) 34 $data = [ 35 ‘code‘=>0, 36 ‘count‘=>$count==0?‘‘:$count, 37 ‘data‘=>$list, 38 ]; 39 return response($data,0,array(),‘json‘); 40 else 41 return api_list_not_more(); 42 43 44 //Banner排序接口 45 public function sortBanner() 46 //获取到的id和sort值 47 $param = input(‘param.‘); 48 if (empty($param)) 49 return api_param_error(); 50 51 $all=[]; 52 for ($i = 0;$i<count($param[‘id‘]);$i++) 53 //验证参数 54 $validate = new V_Banner(); 55 if (!$validate->scene(‘sort‘)->check([‘sort‘=>$param[‘sort‘][$i]])) 56 return api_param_error($validate->getError()); 57 58 $info[‘id‘]=$param[‘id‘][$i]; 59 $info[‘sort‘]=$param[‘sort‘][$i]; 60 $all[] = $info; 61 62 $banner = new ModelBanner(); 63 $res = $banner->saveAll($all); 64 if ($res) 65 return api_success(‘操作成功‘); 66 else 67 return api_error(); 68 69 70 71 /** 删除banner(传id删除单个banner或传数组批量删除) 72 * @return \think\Response 73 */ 74 public function delBanner() 75 $id = input(‘param.id‘); 76 $mBanner = new ModelBanner(); 77 $banner =$mBanner->delBanner($id); 78 if ($banner) 79 return api_success(‘成功删除‘); 80 else 81 return api_error(); 82 83 84 /** 创建banner 85 * @return Response 86 */ 87 public function creBanner() 88 //获取内容信息 id title img_path status type 89 $info = input(‘param.‘); 90 //验证参数 91 $validate = new V_Banner(); 92 if (!$validate->scene(‘create‘)->check($info)) 93 return api_param_error($validate->getError()); 94 95 if (strpos($info[‘sort‘],‘.‘)) 96 return api_error(‘排序只能是整数‘); 97 98 $banner = new ModelBanner(); 99 $sort = $banner->where(‘sort‘,$info[‘sort‘])->find(); 100 if ($sort) 101 return api_error(‘此排序数字已经存在‘); 102 103 $file = new File(); 104 $files = $file->upload(‘Banner‘,‘image‘,true); 105 //判断是否上传文件 106 if ($files instanceof Response) 107 return api_error(‘没有上传图片‘); 108 109 //检查是否上传成功 110 if ($files[0][‘is_success‘]) 111 // 启动事务 112 Db::startTrans(); 113 try 114 $info[‘image‘] = $files[0][‘file_path‘]; 115 116 $res = $banner->save($info); 117 if ($res) 118 // 提交事务 119 Db::commit(); 120 return api_success(‘创建成功!‘); 121 122 catch (\Exception $e) 123 // 回滚事务 124 Db::rollback(); 125 return api_success(‘创建失败!‘); 126 127 else 128 return api_error($files[0][‘error_msg‘]); 129 130 131 132 /** 修改banner 133 * @return \think\Response 134 */ 135 public function upBanner() 136 137 //获取内容信息 id title img_path status type 138 $info = input(‘param.‘); 139 //验证参数 140 $validate = new V_Banner(); 141 if (!$validate->scene(‘update‘)->check(input(‘param.‘))) 142 return api_param_error($validate->getError()); 143 144 if (strpos($info[‘sort‘],‘.‘)) 145 return api_error(‘排序只能是整数‘); 146 147 $banner = new ModelBanner(); 148 $sort = $banner->where(‘sort‘,$info[‘sort‘])->find(); 149 if ($sort) 150 if (!$banner->where(‘sort‘,$info[‘sort‘])->find($info[‘id‘])) 151 return api_error(‘此排序数字已经存在‘); 152 153 154 $file = new File(); 155 $files = $file->upload(‘Banner‘,‘image‘,true); 156 157 //判断是否上传文件 158 if (!$files instanceof Response) 159 //判断是否符合大小和格式 160 if (!$files[0][‘is_success‘]) 161 return api_error($files[0][‘error_msg‘]); 162 163 $info[‘image‘] = $files[0][‘file_path‘]; 164 165 // 启动事务 166 Db::startTrans(); 167 try 168 $mBanner = new ModelBanner(); 169 $res = $mBanner->save($info,[‘id‘=>$info[‘id‘]]); 170 if ($res) 171 // 提交事务 172 Db::commit(); 173 return api_success(‘修改成功!‘); 174 175 catch (\Exception $e) 176 // 回滚事务 177 Db::rollback(); 178 return api_error(‘修改失败!‘); 179 180 181 182 183 184 /** banner上下线功能 185 * @return Response 186 */ 187 public function togStatus() 188 $id = input(‘param.id‘); 189 if (empty($id)) 190 return api_param_error(); 191 192 $mBanner = new ModelBanner(); 193 $result=$mBanner->togStatus($id); 194 if($result) 195 return api_success("操作成功!"); 196 else 197 return api_error(); 198 199 200 201 202 /** 测试 203 * @return array|false|\PDOStatement|string|\think\Model 204 */ 205 function f() 206 $id = input(‘param.‘); 207 $banner = new ModelBanner(); 208 $item = $banner->find($id); 209 return $item; 210 211 212 public function image() 213 // $arr = input(‘param.‘); 214 // dump($arr); 215 // return $arr; 216 return input(‘param.‘); 217 return $_POST[‘title‘]; 218 return $_FILES; 219 $files = request()->file(‘file‘); 220 return $files; 221 222 223
待更新 ...
以上是关于准备工作数据库校验用户准备工作的主要内容,如果未能解决你的问题,请参考以下文章