接口测试博客系统

一、报告概述

1.项目背景

博客系统是基于SSM框架精心构建,为用户提供了一个便捷,高效的交流与分享平台。通过强大的SSM架构,实现了系统的稳定运行和良好的扩展性。用户可以轻松注册并创建个性化的博客空间,自由撰写、编辑和发布文章。系统支持丰富的文本格式嵌入,让用户能够生动的展现自己的想法和创意。

系统包含以下核心模块:

. 用户登录

. 博客列表

. 发布博客

. 编辑博客

. 获取博客信息

2.测试目的

2.1 功能验证

. 确保所有核心功能符合要求

. 验证用户登录的正确性

. 确保编辑博客,发布博客的完整流程

. 检验获取博客列表信息和博客信息的完整性

2.2 系统稳定性保障

. 评估系统高并发下的稳定性

. 检查数据的一致性和完整性

2.3 用户体验优化

. 评估界面布局的合理性和易用性

. 测试系统逻辑是否符合用户习惯

. 验证错误信息的提示清晰度

. 检查响应在不同设备上的表现

2.4 兼容性确认

. 确保主流浏览器的兼容性

3.测试环境

. 操作系统:windows 11

. 浏览器:Chrome,Edge

. 测试工具:Selenium、JMeter、postman

二、测试用例

下图是测试用例脑图

三、手动测试

3.1用户登录

测试场景一:输入用户名和密码,确认成功后跳转到博客列表页

预期结果:登录成功后跳转到博客列表首页

测试场景二:不输入用户名或密码
预期结果:系统提示账号或密码不能为空

测试场景三:正确的用户名,错误的密码

预期结果:系统提示密码错误。

测试场景四:错误的用户名,正确的密码

预期结果:系统提示用户不存在。

3.2 编辑博客

测试场景一:输入博客标题和博客内容

预期结果:发布博客成功。

测试场景二:输入过短的博客标题和博客内容

预期结果:博客发布成功。

测试场景三:输入过长的博客标题

预期结果:发布博客成功。

测试场景四:输入博客标题,未输入博客内容

预期结果:博客发布失败。

测试场景五:未输入博客标题,输入博客内容。

预期结果:博客发布失败

3.3 获取博客信息

测试场景一:获取已存在的博客信息

预期结果:可以查看到指定博客的内容。

测试场景二:blogId不存在,获取不存在的博客信息

预期结果:系统提示内部错误。

测试场景三:blogId为负数

预期结果:博客信息获取失败。

测试场景四:blogId非数字

预期结果:系统提示内部错误。

测试场景五:blogId为浮点数

预期结果:系统提示内部错误。

四、兼容性测试

测试场景一:在Chrome浏览器下进入博客系统。

预期结果:无异常且一切布局显示功能与其等致。

测试场景二:在Edge浏览器下进入博客系统。

预期结果:无异常且一切布局显示功能与其等致。

五、自动化测试

自动化测试项目代码文件结构

5.1 用户登录测试

cpp 复制代码
import re

import pytest
from jsonschema import validate

from utils.request_util import host, Request
from utils.yaml_util import write_yaml

@pytest.mark.order(1)
class Testlogin:
    url = host + "user/login"
    schema = {
        "type": "object",
        #required 表示必须包含列表中的属性
        "required": ["code","errMsg","data"],
        # additionalProperties 表示不允许有额外的属性
        "additionalProperties": False,
        "properties": {
            "code": {
                "type": "string"
            },
            "errMsg": {
                "type": "string"
            },
            "data": {
                "type": ["string", "null"]
            }
        }
    }

    @pytest.mark.parametrize("login", [
        # 错误的账号,错误的密码
        {
            "username":"zhang",
            "password":"123",
            "errMsg":"用户不存在",
        },
        # 错误的账号,正确的密码
        {
            "username": "zhang",
            "password": "123456",
            "errMsg": "用户不存在",
        },
        # 正确的账号,错误的密码
        {
            "username": "zhangsan",
            "password": "123",
            "errMsg": "密码错误",
        },
        # 不存在的账号
        {
            "username": "wangwu",
            "password": "xxxxxx",
            "errMsg": "用户不存在",
        },
        # 账号和密码都为空
        {
            "username": "",
            "password": "",
            "errMsg": "账号或密码不能为空",
        },
        {
            "username": "",
            "password": "123456",
            "errMsg": "账号或密码不能为空",
        },
        {
            "username": "zhangsan",
            "password": "",
            "errMsg": "账号或密码不能为空",
        },
        {
            "username": "这是一个很长的账号这是一个很长的账号这是一个很长的账号这是一个很长的账号这是一个很长的账号",
            "password": "123456",
            "errMsg": "用户不存在",
        },
        {
            "username": "zhangsan",
            "password": "这是一个很长的密码这是一个很长的密码这是一个很长的密码这是一个很长的密码这是一个很长的密码",
            "errMsg": "密码错误",
        }
    ])
    def test_login_fail(self, login):
        data = {
            "username": login["username"],
            "password": login["password"]
        }
        # 发起post请求
        r = Request().post(url=self.url, data=data)
        # 校验响应数据格式是否符合预期
        validate(instance=r.json(), schema=self.schema)
        # 校验关键字段是否正确
        assert r.json()["code"] == "FAIL"
        assert r.json()["errMsg"] == login["errMsg"]


    @pytest.mark.parametrize("login", [
        {
            "username":"zhangsan",
            "password":"123456",
        },
        {
            "username": "lisi",
            "password": "123456",
        }
    ])
    def test_login_success(self, login):
        data = {
            "username":login["username"],
            "password":login["password"]
        }
        #发起post请求
        r = Request().post(url=self.url, data=data)
        #校验响应数据格式是否符合预期
        validate(instance=r.json(), schema=self.schema)
        #校验关键字段是否正确
        assert r.json()["code"] == "SUCCESS"
        # 利用正则表达式来进行校验
        assert re.match("\S{100,}", r.json()["data"])

        # 登录成功之后,返回的 data数据作为其它接口的登录凭证
        token = {
            "user_token_header":r.json()["data"]
        }
        write_yaml("data.yml", token)

5.2 博客列表页测试

cpp 复制代码
import pytest
from jsonschema import validate

from utils.request_util import host, Request
from utils.yaml_util import read_yaml, write_yaml

@pytest.mark.order(2)
class Testlist:
    url = host + "blog/getList"
    schema = {
        "type": "object",
        "additionalProperties": False,
        "required": ["code","errMsg","data"],
        "properties": {
            "code": {
                "type": "string"
            },
            "errMsg": {
                "type": "string"
            },
            "data": {
                "type": "array",
                "minItems": 1,
                "items": {
                    "type": "object",
                    "additionalProperties": False,
                    "required": ["id","title","content","userId","deleteFlag","createTime","updateTime","loginUser"],
                    "properties": {
                        "id": {
                            "type": "number"
                        },
                        "title": {
                            "type": "string"
                        },
                        "content": {
                            "type": "string"
                        },
                        "userId": {
                            "type": "number"
                        },
                        "deleteFlag": {
                            "type": "number"
                        },
                        "createTime": {
                            "type": "string"
                        },
                        "updateTime": {
                            "type": "string"
                        },
                        "loginUser": {
                            "type": "boolean"
                        }
                    }
                }
            }
        }
    }

    # 未登录场景下
    def test_list_nologin(self):
        r = Request().get(url=self.url)
        assert r.status_code == 401

    # 已登录场景下
    def test_list_login(self):
        token = read_yaml("data.yml", "user_token_header")
        header = {
            "user_token_header":token
        }
        # 发起请求
        r = Request().get(url=self.url, headers = header)
        validate(instance = r.json(), schema = self.schema)
        assert r.json()["code"] == "SUCCESS"

        blogId = {
            "blogId":r.json()["data"][0]["id"]
        }
        write_yaml("data.yml", blogId)

5.3 添加博客测试

cpp 复制代码
import pytest
from jsonschema import validate

from utils.request_util import host, Request
from utils.yaml_util import read_yaml


class Testadd:
    url = host + "blog/add"
    schema = {
        "type": "object",
        "additionalProperties": False,
        "required": ["code","errMsg","data"],
        "properties": {
            "code": {
                "type": "string"
            },
            "errMsg": {
                "type": "string"
            },
            "data": {
                "type": "boolean"
            }
        }
    }

    def test_add_nologin(self):
        r = Request().get(self.url)
        assert r.status_code == 401

    @pytest.mark.parametrize("add", [
        # 标题和内容都不为空
        {
            "title": "接口自动化测试",
            "content": "接口自动化测试内容",
            "data":True
        },
        # 标题为空
        {
            "title": "",
            "content": "接口自动化测试内容",
            "data":False
        },
        # 内容为空
        {
            "title": "接口自动化测试",
            "content": "",
            "data":False
        },
        # 标题和内容都为空
        {
            "title": "",
            "content": "",
            "data":False
        },
        # 添加带有图片的博客
        {
            "title": "接口自动化标题---带有图片",
            "content": "![](https://gips0.baidu.com/it/u=3959078674,1497137438&fm=3074&app=3074&f=PNG?w=1440&h=2560)",
            "data": True
        },
        # 添加带有链接的博客
        {
            "title":"接口自动化标题---带有链接",
            "content":"[百度首页](http://www.baidu.com \"百度首页\")",
            "data": True
        }
    ])
    def test_add_login(self,add):
        token = read_yaml("data.yml", "user_token_header")
        header = {
            "user_token_header":token
        }
        json = {
            "title":add["title"],
            "content":add["content"]
        }
        r = Request().post(url=self.url, headers=header, json=json)
        validate(instance=r.json(), schema=self.schema)
        assert r.json()["data"] == add["data"]

5.4 获取详情页测试

cpp 复制代码
import pytest
from jsonschema import validate

from utils.request_util import host, Request
from utils.yaml_util import read_yaml


class Testdetail:
    # url = host + "blog/getBlogDetail?blogId="
    url = host + "blog/getBlogDetail"
    schema = {
        "type": "object",
        "additionalProperties": False,
        "required": ["code","errMsg","data"],
        "properties": {
            "code": {
                "type": "string"
            },
            "errMsg": {
                "type": "string"
            },
            "data": {
                "type": "object",
                "additionalProperties": False,
                "required": ["id","title","content","userId","deleteFlag","createTime","updateTime","loginUser"],
                "properties": {
                    "id": {
                        "type": "number"
                    },
                    "title": {
                        "type": "string"
                    },
                    "content": {
                        "type": "string"
                    },
                    "userId": {
                        "type": "number"
                    },
                    "deleteFlag": {
                        "type": "number"
                    },
                    "createTime": {
                        "type": "string"
                    },
                    "updateTime": {
                        "type": "string"
                    },
                    "loginUser": {
                        "type": "boolean"
                    }
                }
            }
        }
    }

    def test_detail_nologin(self):
        # url = self.url + "1234"
        url = self.url + "?blogId=1234"
        r = Request().get(url = url)
        assert r.status_code == 401

    def test_detail_login(self):
        # url = self.url + str(read_yaml("data.yml", "blogId"))
        url = self.url + "?blogId=" + str(read_yaml("data.yml", "blogId"))
        token = read_yaml("data.yml", "user_token_header")
        header = {
            "user_token_header": token
        }
        # 发起请求
        r = Request().get(url=url, headers=header)
        validate(instance=r.json(), schema=self.schema)
        assert r.json()["code"] == "SUCCESS"

    @pytest.mark.parametrize("blogId", ["", 1234, "你好", -100, 9999999999999])
    def test_detail_fail(self, blogId):
        # url = self.url + str(blogId)
        url = self.url
        params = {
            "blogId":blogId
        }
        token = read_yaml("data.yml", "user_token_header")
        header = {
            "user_token_header": token
        }
        # 发起请求
        # r = Request().get(url=url, headers=header)

        r = Request().get(url=url, headers=header, params = params)
        expect_json = {
            "code": "FAIL",
            "errMsg": "内部错误, 请联系管理员",
            "data": None
        }
        assert r.json() == expect_json

5.5 获取用户信息测试

cpp 复制代码
from jsonschema import validate

from utils.request_util import host, Request
from utils.yaml_util import read_yaml


class TestgetUserInfo:
    url = host + "user/getUserInfo"
    schema = {
        "type": "object",
        "additionalProperties": False,
        "required": ["code","errMsg","data"],
        "properties": {
            "code": {
                "type": "string"
            },
            "errMsg": {
                "type": "string"
            },
            "data": {
                "type": "object",
                "additionalProperties": False,
                "required": ["id","userName","password","githubUrl","deleteFlag","createTime","updateTime"],
                "properties": {
                    "id": {
                        "type": "number"
                    },
                    "userName": {
                        "type": "string"
                    },
                    "password": {
                        "type": "string"
                    },
                    "githubUrl": {
                        "type": "string"
                    },
                    "deleteFlag": {
                        "type": "number"
                    },
                    "createTime": {
                        "type": "string"
                    },
                    "updateTime": {
                        "type": "string"
                    }
                }
            }
        }
    }
    def test_getUserInfo_nologin(self):
        r = Request().get(url=self.url)
        assert r.status_code == 401

    def test_getUserInfo_login(self):
        token = read_yaml("data.yml", "user_token_header")
        header = {
            "user_token_header":token
        }
        r = Request().get(url=self.url, headers=header)
        validate(instance=r.json(), schema=self.schema)
        assert r.json()["code"] == "SUCCESS"

5.5 获取作者信息测试

cpp 复制代码
import pytest
from jsonschema import validate

from utils.request_util import host, Request
from utils.yaml_util import read_yaml


class TestauthoInfo:
    url = host + "user/getAuthorInfo"
    schema = {
        "type": "object",
        "additionalProperties": False,
        "required": ["code","errMsg","data"],
        "properties": {
            "code": {
                "type": "string"
            },
            "errMsg": {
                "type": "string"
            },
            "data": {
                "type": ["object", "null"],
                "additionalProperties": False,
                "required": ["id","userName","password","githubUrl","deleteFlag","createTime","updateTime"],
                "properties": {
                    "id": {
                        "type": "number"
                    },
                    "userName": {
                        "type": "string"
                    },
                    "password": {
                        "type": "string"
                    },
                    "githubUrl": {
                        "type": "string"
                    },
                    "deleteFlag": {
                        "type": "number"
                    },
                    "createTime": {
                        "type": "string"
                    },
                    "updateTime": {
                        "type": "string"
                    }
                }
            }
        }
    }
    def test_getAuthorInfo_nologin(self):
        url = self.url + "?blogId=32279"
        r = Request().get(url)
        assert r.status_code == 401

    def test_getAuthorInfo_login(self):
        blogId = read_yaml("data.yml", "blogId")
        token = read_yaml("data.yml", "user_token_header")
        header = {
            "user_token_header":token
        }
        url = self.url + "?blogId=" + str(blogId)
        r = Request().get(url=self.url, headers=header)
        validate(instance=r.json(), schema=self.schema)

    @pytest.mark.parametrize("blogId, expected_code", [
        ("","FAIL"),
        (1234,"FAIL"),
        ("你好","FAIL"),
        (-100,"SUCCESS"),
        (9999999999999,"FAIL")
    ])
    def test_getAuthorInfo_fail(self, blogId, expected_code):
        url = self.url
        params = {
            "blogId":blogId
        }
        token = read_yaml("data.yml", "user_token_header")
        header = {
            "user_token_header":token
        }
        r = Request().get(url=url, params=params,headers=header)

        validate(instance=r.json(), schema=self.schema)
        assert r.json()["code"] == expected_code

5.6 日志模块

cpp 复制代码
import logging
import os
import time

class infoFilter(logging.Filter):
    def filter(self, record):
        return record.levelno == logging.INFO

class errFilter(logging.Filter):
    def filter(self, record):
        return record.levelno == logging.ERROR

class logger:
    #获取日志对象
    @classmethod #声明该方法为类方法,可以被类直接调用
    def getlog(cls):
        cls.logger = logging.getLogger(__name__)
        cls.logger.setLevel(logging.DEBUG)

        LOG_PATH = "./logs/"

        if not os.path.exists(LOG_PATH):
            os.mkdir(LOG_PATH)
        #将日志输出到日志文件中
        now = LOG_PATH + time.strftime("%Y-%m-%d")
        log_name = now + ".log"
        info_log_name = now + "-info.log"
        err_log_name = now + "-err.log"

        #创建文件处理器
        all_handler = logging.FileHandler(log_name, encoding = "utf-8")
        info_handler = logging.FileHandler(info_log_name, encoding = "utf-8")
        err_handler = logging.FileHandler(err_log_name, encoding = "utf-8")
        # 创建控制台处理器,将日志输出到控制台
        streamHandler = logging.StreamHandler()

        #设置日志的格式
        formatter = logging.Formatter(
            "%(asctime)s %(levelname)s [%(name)s] [%(filename)s (%(funcName)s:%(lineno)d)] - %(message)s"
        )
        all_handler.setFormatter(formatter)
        info_handler.setFormatter(formatter)
        err_handler.setFormatter(formatter)
        streamHandler.setFormatter(formatter)


        #添加日志过滤器
        info_handler.addFilter(infoFilter())
        err_handler.addFilter(errFilter())

        cls.logger.addHandler(all_handler)
        cls.logger.addHandler(info_handler)
        cls.logger.addHandler(err_handler)
        cls.logger.addHandler(streamHandler)
        return cls.logger

5.7 方法请求模块

cpp 复制代码
import requests

from utils.logger_util import logger

host = "http://49.235.61.184:19090/"
# http://49.235.61.184:19090/blog_list.html

class Request:
    log = logger.getlog()
    def get(self, url, **kwargs):
        self.log.info("准备发起get请求,url:"+url)
        self.log.info("接口信息:{}".format(kwargs)) #.format代表格式化数据

        r = requests.get(url, **kwargs)

        self.log.info("接口响应状态码:{}".format(r.status_code))
        self.log.info("接口响应内容:{}".format(r.text))
        return r


    def post(self, url, **kwargs):
        self.log.info("准备发起post请求,url:"+url)
        self.log.info("接口信息:{}".format(kwargs)) #.format代表格式化数据

        r = requests.post(url, **kwargs)

        self.log.info("接口响应状态码:{}".format(r.status_code))
        self.log.info("接口响应内容:{}".format(r.text))
        return r

5.8 yaml模块

cpp 复制代码
import os

import yaml

# 往文件中写入数据
def write_yaml(filename,data):
  with open(os.getcwd()+"/data/"+filename, mode="a+", encoding="utf-8") as f:
      yaml.safe_dump(data, stream=f)

# 从文件中读取数据
def read_yaml(filename, key):
    with open(os.getcwd()+"/data/"+filename, mode="r", encoding="utf-8") as f:
        data = yaml.safe_load(f)
        return data[key]

# 清空文件
def clear_yaml(filename):
    with open(os.getcwd()+"/data/"+filename, mode="w", encoding="utf-8") as f:
        f.truncate()

5.9 配置文件

cpp 复制代码
[pytest]
#addopts选项代表pytest在运行时会自动携带命令行参数-vs
addopts = -vs --alluredir allure-results

5.10 测试入口

cpp 复制代码
from utils.logger_util import logger

if __name__ == "__main__":
    log = logger.getlog()
    log.info("--- info message ---")
    log.error("--- err message ---")

六、性能测试

本次性能测试将采用业界广泛使用的两款工具:JMeter,postman。对博客系统进行性能评估。

postman 配置

创建专门的测试集合用于博客系统测试

为每个测试页面设置独立的请求,存储基础URL,用户凭证等共享信息

设置合理的请求头

JMeter配置

创建新的测试计划,添加线程组模拟并发用户

为每个测试页面配置HTTP请求采样器,添加监听器,生成结果树收集测试结果

1. 单一接口性能测试

6.1 用户登录
6.2 列表页
6.3 用户信息
6.4 详情页
6.5 添加博客

2. 聚合报告

3. TPS可视化

Transactions per Second(TPS)监听器是一个用于分析系统吞吐量的重要工具。TPS即每秒事务数,表示一个客户机向服务器发送请求后服务器做出反应的过程。这个指标反映了系统在同一时间内处理业务的最大能力。TPS值越高,说明系统的处理能力越强

4. 响应时间可视化

Response Times Over Time 主要用于监听整个事务运行期间的响应时间。在测试过程中,可以通过观察并分析响应时间的实时平均值以及整体的响应时间走向

5. 活跃线程可视化

Active Threads Over Time 监听器显示测试执行期间并发用户(线程)数量的实时变化,以折线图形式展示活跃线程数随时间变化的趋势帮助识别负载模式,峰值和低谷

6. 性能测试报告

七、测试报告