测试平台系列(76) 编写测试计划前端部分.md

2022/6/13 测试平台接口测试FastApiPythonReact

大家好~我是米洛
我正在从0到1打造一个开源的接口测试平台, 也在编写一套与之对应的完整教程,希望大家多多支持。
欢迎关注我的公众号米洛的测开日记,获取最新文章教程!

# 回顾

上一节我们编写了测试计划APScheduler相结合的方法,定时任务也能够正常跑起来了

这一节我们就简单聊聊前端怎么去做,最近天气变冷了,博主也感冒了。自己写的过程中发现许多错误,如果有出错的地方希望大家帮忙指出下 =。=

# 先谈设计

如果像以往的模式,放一个表格,里面展示每个测试计划的相关信息。

添加和删除呢,就放到modal对话框里面。表单的内容用户填写就好了,那这个curd的页面就可以很快完成

但想一下,我们的测试计划是一个比较复杂的数据。在我的想法中,他应该具备这些条件:

  • 多条件搜索

    可以根据项目,测试计划名称,优先级,创建人等条件进行搜索。后续还可能根据创建时间搜索啥的。

  • 步骤清晰

    我想把这块分为3块:

      1. 基本信息
      1. 用例数据
      1. 通知配置

顺着这个想法,所以我找到了antd的steps(步骤条),让用户录入数据的时候也有一定的条理,提升体验。

类似于图中这块部分

# 编写TestPlan.jsx组件

import {PageContainer} from "@ant-design/pro-layout";
import {connect} from 'umi';
import {Badge, Button, Card, Col, Divider, Form, Input, Row, Select, Table, Tag, Tooltip} from "antd";
import React, {useEffect} from "react";
import {CONFIG} from "@/consts/config";
import {PlusOutlined} from "@ant-design/icons";
import TestPlanForm from "@/components/TestCase/TestPlanForm";

const {Option} = Select;

const TestPlan = ({testplan, dispatch, loading, gconfig, user, project}) => {

  const {planData} = testplan;
  const {userList, userMap} = user;
  const {projectsMap, projects} = project;
  // const {envList} = gconfig;

  const getStatus = record => {
    if (record.state === 2) {
      return <Tooltip title="定时任务可能添加失败, 请尝试重新添加"><Badge status="error" text="出错"/></Tooltip>
    }
    if (record.state === 3) {
      return <Tooltip title="任务已暂停"><Badge status="warning" text="已暂停"/></Tooltip>
    }
    if (record.state === 1) {
      return <Tooltip title="任务正在执行中"><Badge status="processing" text="执行中"/></Tooltip>
    }
    return <Tooltip title={`下次运行时间: ${record.next_run}`}>
      <Badge status="success" text="等待中"/>
    </Tooltip>
  }

  const columns = [
    {
      title: '项目',
      key: 'project_id',
      dataIndex: 'project_id',
      render: projectId => <a href={`/#/apiTest/project/${projectId}`}
                              target="_blank" rel="noreferrer">{projectsMap[projectId] || 'loading'}</a>
    },
    {
      title: '测试计划',
      key: 'name',
      dataIndex: 'name'
    },
    {
      title: '优先级',
      key: 'priority',
      dataIndex: 'priority',
      render: priority => <Tag color={CONFIG.CASE_TAG[priority]}>{priority}</Tag>
    },
    {
      title: 'cron表达式',
      key: 'cron',
      dataIndex: 'cron',
    },
    {
      title: '顺序执行',
      key: 'ordered',
      dataIndex: 'ordered',
      render: bool => bool ? <Tag color="blue"></Tag> : <Tag></Tag>
    },
    {
      title: '用例数量',
      key: 'case_list',
      dataIndex: 'case_list',
      render: caseList => caseList.split(",").length,
    },
    {
      title: '状态',
      key: 'next_run',
      dataIndex: 'next_run',
      render: (_, record) => getStatus(record)
    },
    {
      title: '创建人',
      key: 'create_user',
      dataIndex: 'create_user',
      render: create_user => userMap[create_user] !== undefined ? userMap[create_user].name : '加载中...'
    },
    {
      title: '操作',
      key: 'ops',
      render: () => <>
        <a>编辑</a>
        <Divider type="vertical"/>
        <a>删除</a>
      </>
    },


  ]

  // form查询条件
  const [form] = Form.useForm();

  const spin = loading.effects['testplan/listTestPlan'] || loading.effects['project/listProject']

  const fetchTestPlan = () => {
    const values = form.getFieldsValue();
    dispatch({
      type: 'testplan/listTestPlan',
      payload: {
        page: 1,
        size: 10,
        ...values,
      }
    })
  }

  const fetchProjectList = () => {
    dispatch({
      type: 'project/listProject',
    })
  }

  const fetchUsers = () => {
    if (userList.length === 0) {
      dispatch({
        type: 'user/fetchUserList',
      })
    }
  }

  const fetchEnvList = () => {
    dispatch({
      type: 'gconfig/fetchEnvList',
      payload: {
        page: 1,
        size: 1000,
        exactly: true // 全部获取
      }
    })
  }

  const onSave = data => {
    dispatch({
      type: 'testplan/save',
      payload: data
    })
  }

  useEffect(() => {
    fetchEnvList()
    fetchUsers()
    fetchProjectList()
    fetchTestPlan()
  }, [])

  return (
    <PageContainer title={false}>
      <Card>
        <TestPlanForm/>
        <Form form={form} {...CONFIG.LAYOUT} onValuesChange={() => {
          fetchTestPlan();
        }}>
          <Row gutter={[12, 12]}>
            <Col span={6}>
              <Form.Item label="项目" name="project_id">
                <Select allowClear showSearch placeholder="选择项目">
                  {projects.map(item => <Option value={item.id} key={item.id}>{item.name}</Option>)}
                </Select>
              </Form.Item>
            </Col>
            <Col span={6}>
              <Form.Item label="名称" name="name">
                <Input placeholder="输入测试计划名称"/>
              </Form.Item>
            </Col>
            <Col span={6}>
              <Form.Item label="优先级" name="priority">
                <Select placeholder="选择优先级" allowClear>
                  {CONFIG.PRIORITY.map(v => <Option key={v} value={v}>{v}</Option>)}
                </Select>
              </Form.Item>
            </Col>
            <Col span={6}>
              <Form.Item label="创建人" name="state">
                <Select placeholder="选择创建人" showSearch allowClear>
                  {userList.map(item => <Option key={item.id} value={item.id}>{item.name}</Option>)}
                </Select>
              </Form.Item>
            </Col>
          </Row>
        </Form>
        <Row style={{marginBottom: 12}}>
          <Button type="primary" onClick={() => {
            onSave({visible: true, title: '新增测试计划'})
          }}><PlusOutlined/> 添加计划</Button>
        </Row>
        <Table columns={columns} dataSource={planData} rowKey={row => row.id} loading={spin}/>
      </Card>
    </PageContainer>
  )
}


export default connect(({testplan, project, user, loading, gconfig}) => ({
  testplan,
  project,
  loading,
  user,
  gconfig,
}))(TestPlan);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205

讲解一下代码,核心还是通过dva的model-service这种类redux管理状态。

结合最新的useEffect,获取到测试计划信息。

把搜索选项放到表单中,并给form设置onValuesChange方法,当数据有变动的时候自动重新查询测试计划。这样避免了 人肉再点击一次搜索按钮

效果图如下:

需要注意的是,这里展示了大部分数据,如果想看更多内容,需要点击编辑按钮查看了。

# 编写创建测试计划表单组件

该组件还是一个Modal,按照我们刚才说的3个字段去分配对应的板块,大概样式如下:

由于测试用例的选择,还没有确定,所以暂时还需要慢慢设计。个人感觉自己前端部分写起来还是偏慢,前面还有好多坑没有填。

今天的内容就先介绍到这里吧,下一节我们演示下做好的测试计划页面以及解决多workers下的APScheduler单个任务重复执行的问题。

在线体验地址: http://test.pity.fun (opens new window)