PPT
测试平台训练营二
源码地址
HogwartsTestPlatform202303: 测试平台项目,前端vue,后端flask
训练营目标
- 项目初始化
- api接口封装
- 布局实现
- 测试用例组件实现
- 测试计划组件实现
- 测试报告组件实现
项目初始化1-详情
项目初始化2-预设
- 进入预设界面
- 默认第一个是vue3,选择手动配置项目
- 点击下一步
项目初始化3-功能
- 进入功能界面
- 只勾选babel和router,其他全部取消
- 点击下一步
项目初始化4-配置
- 进入配置界面
- 切换vue2.x,默认是vue3.x
- 点击创建项目
项目初始化5-插件
- 点击左侧插件,进入插件页面
- 右上角输入vuetify,搜索后选择第一个进行安装
- 安装完成界面会展示该插件
项目初始化6-依赖
- 点击左侧依赖,进入依赖页面
- 右上角输入axios,搜索后选择第一个进行安装
- 安装完成界面会展示该依赖
启动项目
- 点击左侧任务,进入任务页面
- 选择serve
- 点击运行
- 点击启动app,即可打开页面
默认代码改造
api封装-http.js
// 完成 http 请求的基本配置
// 导入 axios
import axios from 'axios'
// 创建 axios 实例
var instance = axios.create({
// 请求头
headers: {
'Content-Type': 'application/json'
},
// 超时时间
timeout: 2500,
// 基础 url,接口服务地址
// baseURL: 'http://39.102.48.202:6099/'
baseURL: 'http://127.0.0.1:5001/'
})
export default instance
api封装-http.js
// 完成 http 请求的基本配置
// 导入 axios
import axios from 'axios'
// 创建 axios 实例
var instance = axios.create({
// 请求头
headers: {
'Content-Type': 'application/json'
},
// 超时时间
timeout: 2500,
// 基础 url,接口服务地址
// baseURL: 'http://39.102.48.202:6099/'
baseURL: 'http://127.0.0.1:5001/'
})
export default instance
api封装-testcase.js
// 测试用例增删改查接口管理
// 导入已经配置好的 axios 实例
import axios from './http'
const testcase = {
// 获取用例信息
getTestcase(params) {
return axios({
method: "GET",
url: "/testcase",
// 如果是传递拼接在 url 中的参数,要使用 params
params: params
})
},
// 添加用例
addTestcase(data) {
return axios({
method: "POST",
url: "/testcase",
// 如果是传递请求体,要使用 data
data: data
})
},
// 删除用例
deleteTestcase(data) {
return axios({
method: "DELETE",
url: "/testcase",
data: data
})
},
// 修改用例
updateTestcase(data) {
return axios({
method: "PUT",
url: "/testcase",
data: data
})
}
}
// 导出
export default testcase
api封装-plan.js
// 导入已经配置好的 axios 实例
import axios from './http'
const plan = {
// 获取测试计划
getPlan(params) {
return axios({
method: "GET",
url: "/plan",
// 如果是传递拼接在 url 中的参数,要使用 params
params: params
})
},
// 添加测试计划
addPlan(data) {
return axios({
method: "POST",
url: "/plan",
// 如果是传递请求体,要使用 data
data: data
})
},
// 删除测试计划
deletePlan(data) {
return axios({
method: "DELETE",
url: "/plan",
data: data
})
},
}
// 导出
export default plan
api封装-build.js
// 导入已经配置好的 axios 实例
import axios from './http'
const build = {
// 获取构建记录
getBuild(params) {
return axios({
method: "GET",
url: "/build",
// 如果是传递拼接在 url 中的参数,要使用 params
params: params
})
},
// 获取构建记录
addBuild(data) {
return axios({
method: "POST",
url: "/build",
// 如果是传递请求体,要使用 data
data: data
})
},
// 删除构建记录
deleteBuild(data) {
return axios({
method: "DELETE",
url: "/build",
data: data
})
},
}
// 导出
export default build
api封装-api.js
// 所有接口的入口,相当于目录
import testcase from './testcase'
import plan from './plan'
import build from './build'
const api = {
testcase,
plan,
build,
}
export default api
api封装-api.js
// 所有接口的入口,相当于目录
import testcase from './testcase'
import plan from './plan'
import build from './build'
const api = {
testcase,
plan,
build,
}
export default api
api封装-api.js
// 所有接口的入口,相当于目录
import testcase from './testcase'
import plan from './plan'
import build from './build'
const api = {
testcase,
plan,
build,
}
export default api
启动入口-main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import vuetify from './plugins/vuetify'
import api from './api/api'
Vue.prototype.$api = api
Vue.config.productionTip = false
new Vue({
router,
vuetify,
render: h => h(App)
}).$mount('#app')
最外层组件-App.vue
<template>
<router-view></router-view>
</template>
<script>
export default {
name: 'App',
data: () => ({
//
}),
};
</script>
布局组件-Index.vue
<template>
<v-app id="inspire">
<!-- 侧边栏 -->
<v-navigation-drawer v-model="drawer" app>
<!-- 左侧logo -->
<v-list-item>
<v-list-item-content>
<v-list-item-title class="text-h6">
测试平台
</v-list-item-title>
<v-list-item-subtitle>
霍格沃兹
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<!-- 左侧导航栏 -->
<v-list dense nav>
<v-list-item v-for="item in items" :key="item.title" link :href="item.link">
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
<v-list-item-content>
<v-list-item-title>{{ item.title }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</v-navigation-drawer>
<!-- 顶部栏 -->
<v-app-bar app>
<v-app-bar-nav-icon @click="drawer = !drawer"></v-app-bar-nav-icon>
<v-toolbar-title>霍格沃兹测试平台</v-toolbar-title>
</v-app-bar>
<!-- 主体内容 -->
<v-main>
<router-view></router-view>
</v-main>
</v-app>
</template>
<script>
export default {
data: () => ({
drawer: null,
items: [
{ title: '测试用例', icon: 'mdi-view-dashboard', link: '#/index/test_case' },
{ title: '测试计划', icon: 'mdi-image', link: '#/index/test_plan' },
{ title: '测试报告', icon: 'mdi-help-box', link: '#/index/test_report' },
],
right: null,
}),
}
</script>
路由封装-index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
import Index from '../views/Index.vue'
import TestCase from '../views/TestCase.vue'
import TestPlan from '../views/TestPlan.vue'
import TestReport from '../views/TestReport.vue'
Vue.use(VueRouter)
const routes = [
{
path: '/',
redirect: '/index/test_case'
},
{
path: '/index',
name: 'Index',
component: Index,
children: [
{
path: 'test_case',
name: 'TestCase',
component: TestCase
},
{
path: 'test_plan',
name: 'TestPlan',
component: TestPlan
},
{
path: 'test_report',
name: 'TestReport',
component: TestReport
},
]
}
]
const router = new VueRouter({
routes
})
export default router
测试用例组件-TestCase.vue
<template>
<div>
<!-- 测试用例数据表格 -->
<v-data-table :headers="headers" :items="desserts" sort-by="calories" class="elevation-1" v-model="selected"
item-key="id" show-select>
<!-- 顶部插槽 -->
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>测试用例</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
<!-- 生成测试计划弹框 -->
<v-dialog v-model="dialogPlan" max-width="500px">
<template v-slot:activator="{ on, attrs }">
<v-btn color="green" dark class="mb-2" v-bind="attrs" v-on="on">
生成计划
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="text-h5">生成计划</span>
</v-card-title>
<v-card-text>
<v-container>
<v-row>
<v-text-field v-model="planName" label="测试计划名称"></v-text-field>
</v-row>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="savePlan">
保存
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 新增用例弹框 -->
<v-dialog v-model="dialog" max-width="500px">
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" dark class="mb-2" v-bind="attrs" v-on="on">
新增用例
</v-btn>
</template>
<v-card>
<v-card-title>
<span class="text-h5">{{ formTitle }}</span>
</v-card-title>
<v-card-text>
<v-container>
<v-text-field v-if="editedItem.id" v-model="editedItem.id" label="用例id"
disabled></v-text-field>
<v-text-field v-model="editedItem.name" label="用例名称"></v-text-field>
<v-textarea v-model="editedItem.step" label="用例步骤"></v-textarea>
<v-text-field v-model="editedItem.method" label="用例方法"></v-text-field>
<v-text-field v-model="editedItem.remark" label="备注"></v-text-field>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="close">
取消
</v-btn>
<v-btn color="blue darken-1" text @click="save">
保存
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- 删除用例弹框 -->
<v-dialog v-model="dialogDelete" max-width="500px">
<v-card>
<v-card-title class="text-h5">确认删除用例?</v-card-title>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" text @click="closeDelete">取消</v-btn>
<v-btn color="blue darken-1" text @click="deleteItemConfirm">确认</v-btn>
<v-spacer></v-spacer>
</v-card-actions>
</v-card>
</v-dialog>
</v-toolbar>
</template>
<!-- 表格内容操作插槽 -->
<template v-slot:[`item.actions`]="{ item }">
<v-icon small class="mr-2" @click="editItem(item)">
mdi-pencil
</v-icon>
<v-icon small @click="deleteItem(item)">
mdi-delete
</v-icon>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
data: () => ({
dialog: false,
dialogDelete: false,
dialogPlan: false,
planName: '',
selected: [],
headers: [
{ text: '用例ID', align: 'start', sortable: false, value: 'id', },
{ text: '用例名称', value: 'name' },
{ text: '用例步骤', value: 'step' },
{ text: '用例方法', value: 'method' },
{ text: '备注', value: 'remark' },
{ text: '操作', value: 'actions', sortable: false },
],
desserts: [],
editedIndex: -1,
editedItem: {
id: '',
name: '',
step: '',
method: '',
remark: ''
},
defaultItem: {
id: '',
name: '',
step: '',
method: '',
remark: ''
},
}),
computed: {
formTitle() {
return this.editedIndex === -1 ? '新增用例' : '编辑用例'
},
},
watch: {
dialog(val) {
val || this.close()
},
dialogDelete(val) {
val || this.closeDelete()
},
},
created() {
this.initData()
},
methods: {
initData() {
this.$api.testcase.getTestcase().then((result) => {
console.log("getTestcase", result)
// 渲染测试用例表体的数据
this.desserts = result.data.data
}).catch((err) => {
console.log(err)
});
},
editItem(item) {
this.editedIndex = this.desserts.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialog = true
},
deleteItem(item) {
this.editedIndex = this.desserts.indexOf(item)
this.editedItem = Object.assign({}, item)
this.dialogDelete = true
},
deleteItemConfirm() {
// this.desserts.splice(this.editedIndex, 1)
// 获取要删除用例的 id
console.log(this.editedItem.id)
// 调用删除接口
this.$api.testcase.deleteTestcase({ 'id': this.editedItem.id }).then((result) => {
if (result.data.code === 0) {
this.initData()
} else {
console.log("用例删除失败")
console.log("deleteTestcase", result)
}
}).catch((err) => {
console.log(err)
});
this.closeDelete()
},
close() {
this.dialog = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
closeDelete() {
this.dialogDelete = false
this.$nextTick(() => {
this.editedItem = Object.assign({}, this.defaultItem)
this.editedIndex = -1
})
},
save() {
if (this.editedIndex > -1) {
// 编辑用例,调用更新用例的接口
console.log("this.editedIndex > -1")
// Object.assign(this.desserts[this.editedIndex], this.editedItem)
console.log("this.editedItem", this.editedItem)
this.$api.testcase.updateTestcase(this.editedItem).then((result) => {
if (result.data.code === 0) {
this.initData()
} else {
console.log("用例更新失败")
console.log("updateTestcase", result)
}
}).catch((err) => {
console.logO(err)
});
} else {
// 新增用例,调用新增用例的接口
console.log("else")
// this.desserts.push(this.editedItem)
const { id, ...newEditedItem } = this.editedItem
this.$api.testcase.addTestcase(newEditedItem).then((result) => {
if (result.data.code === 0) {
this.initData()
} else {
console.log("用例新增失败")
console.log("addTestcase", result)
}
}).catch((err) => {
console.log(err)
});
}
this.close()
},
// 生成测试计划的方法
savePlan() {
// 测试计划名称
console.log("测试计划名称", this.planName)
// 测试用例信息
console.log("测试用例信息", this.selected)
// this.selected 获取到的是一个对象,转换成数组
let idList = [];
this.selected.forEach(item => {
console.log("获取到的id为", item.id)
idList.push(item.id)
})
// 测试用例信息
console.log("测试用例数组信息", idList)
this.$api.plan.addPlan({ "name": this.planName, "testcase_ids": idList })
.then((result) => {
console.log("添加测试计划是否成功", result)
}).catch((err) => {
console.log(err)
});
// 执行完成初始选中状态并关闭弹窗
this.planName = ''
this.selected = []
this.dialogPlan = false
}
},
}
</script>
测试用例组件-效果
测试计划组件-TestPlan.vue
<template>
<div>
<!-- 测试任务数据表格 -->
<v-data-table :headers="headers" :items="desserts" :expanded.sync="expanded" item-key="name" class="elevation-1"
show-expand>
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>测试任务</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
</v-toolbar>
</template>
<!-- 测试用例插槽 -->
<template v-slot:[`item.testcases`]="{ item }">
<span v-for="i in item.testcases">{{ i.name }},</span>
</template>
<!-- 展开行插槽 -->
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length">
<h3 v-for="i in item.testcases">{{ JSON.stringify(i) }}</h3>
</td>
</template>
<!-- 操作插槽 -->
<template v-slot:[`item.actions`]="{ item }">
<v-btn color="success" @click="buildPlan(item)">
执行
</v-btn>
<v-btn color="warning" @click="deletePlan(item)">
删除
</v-btn>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
data: () => ({
expanded: [],
dialog: false,
headers: [
{ text: '计划ID', align: 'start', sortable: false, value: 'id' },
{ text: '计划名称', value: 'name' },
{ text: '关联用例', value: 'testcases' },
{ text: '操作', value: 'actions', sortable: false },
{ text: '展开查看', value: 'data-table-expand' },
],
desserts: [],
}),
created() {
this.initData()
},
methods: {
// 初始化数据
initData() {
this.$api.plan.getPlan().then((result) => {
console.log("获取到的测试计划为", result)
this.desserts = result.data.data
}).catch((err) => {
console.log(err)
})
},
// 执行计划
buildPlan(item) {
this.$api.build.addBuild({ 'id': item.id }).then((result) => {
console.log("执行计划", result)
}).catch((err) => {
console.log(err)
})
},
// 删除计划
deletePlan(item) {
this.$api.plan.deletePlan({ 'id': item.id }).then((result) => {
console.log("删除计划", result)
if (result.data.code === 0) {
this.initData()
} else {
console.log("删除计划失败")
console.log("addTestcase", result)
}
}).catch((err) => {
console.log(err)
})
}
},
}
</script>
测试计划组件-效果
测试报告组件-TestReport.vue
<template>
<div>
<!-- 测试任务数据表格 -->
<v-data-table :headers="headers" :items="desserts" sort-by="calories" class="elevation-1">
<template v-slot:top>
<v-toolbar flat>
<v-toolbar-title>测试报告</v-toolbar-title>
<v-divider class="mx-4" inset vertical></v-divider>
<v-spacer></v-spacer>
</v-toolbar>
</template>
<!-- 计划插槽 -->
<template v-slot:[`item.plan`]="{ item }">
<v-tooltip bottom>
<template v-slot:activator="{ on, attrs }">
<v-btn color="primary" dark v-bind="attrs" v-on="on">
查看详情
</v-btn>
</template>
<h3> 计划id:{{ item.plan.id }}</h3>
<h3> 计划名称:{{ item.plan.id }}</h3>
<h3> 用例详情:</h3>
<h3 v-for="i in item.plan.testcases">{{ JSON.stringify(i) }}</h3>
</v-tooltip>
</template>
<!-- 报告插槽 -->
<template v-slot:[`item.report`]="{ item }">
<a :href="item.report" target="_blank">{{ item.report }}</a>
</template>
<!-- 操作插槽 -->
<template v-slot:[`item.actions`]="{ item }">
<v-btn color="warning" @click="deleteBuild(item)">
删除
</v-btn>
</template>
</v-data-table>
</div>
</template>
<script>
export default {
data: () => ({
dialog: false,
headers: [
{ text: '构建ID', align: 'start', sortable: false, value: 'id' },
{ text: '计划ID', value: 'plan_id' },
{ text: '计划详情', value: 'plan' },
{ text: '报告地址', value: 'report' },
{ text: '创建时间', value: 'create_time' },
{ text: '操作', value: 'actions', sortable: false },
],
desserts: [],
}),
created() {
this.initData()
},
methods: {
initData() {
this.$api.build.getBuild().then((result) => {
console.log("获取到的测试计划为", result)
this.desserts = result.data.data
}).catch((err) => {
console.log(err)
});
},
// 删除报告
deleteBuild(item) {
console.log("item", item)
this.$api.build.deleteBuild({ 'id': item.id }).then((result) => {
if (result.data.code === 0) {
this.initData()
} else {
console.log("删除报告失败")
console.log("addTestcase", result)
}
}).catch((err) => {
console.log(err)
})
}
},
}
</script>
测试报告组件-效果