测试脚本把页面搞崩了

本文涉及的产品
云原生大数据计算服务MaxCompute,500CU*H 100GB 3个月
云原生大数据计算服务 MaxCompute,5000CU*H 100GB 3个月
简介: 测试脚本把页面搞崩了

写一个栗子看看ivew table承载的数据边界是多少


笔者写了一个简单的栗子来,测试页面卡顿的情况,新建一个index.html,贴上关键代码

<html>
   ...
   <link
      rel="stylesheet"
      type="text/css"
      href="http://unpkg.com/view-design/dist/styles/iview.css"
    />
    <script type="text/javascript" src="./js/vue.min.js"></script>
    <script type="text/javascript" src="./js/iview.min.js"></script>
    <script type="text/javascript" src="./js/mock-min.js"></script>
    <script type="text/javascript" src="./js/axios.min.js"></script>
    <script type="text/javascript" src="./mockserver.js"></script>
    <style>
      #app {
        margin: 10px;
      }
    </style>
  <body>
    <div id="app">
      <Row align="middle" type="flex" gutter="10">
        <i-col span="24"><h2>iview-table性能优化测试</h2></i-col>
        <i-col span="3">
          pageNum<i-input v-model.number="pageParams.pageNum"></i-input>
        </i-col>
        <i-col span="3">
          pageSize<i-input v-model.number="pageParams.pageSize"></i-input>
        </i-col>
        <i-col span="3">
          total<i-input v-model.number="pageParams.total"></i-input>
        </i-col>
        <i-col span="3">
          <i-button type="primary" @click="handleReflush">刷新</i-button>
        </i-col>
      </Row>
      <i-table
        row-key="id"
        :loading="loading"
        :columns="columns"
        :data="tableData"
        border
      ></i-table>
      <Page
        :total="pageParams.total"
        @on-change="handleChangePage"
        show-sizer
      ></Page>
    </div>
  </body>
</html>

新建一个index.js,引入页面中

  <html>
    ....
    <body>
      ...
      <div id="app">
        ...
        <Page
        :total="pageParams.total"
        @on-change="handleChangePage"
        show-sizer
      ></Page>
      </div>
      <script src="./index.js"></script>
    </body>
  </html>

我本地新建一个模拟接口数据的操作,这里笔者用了一个`mockjs`[1]造数据,使用axios这个库做ajax请求


具体看下index.js这个主页面的js

// index.js
var vm = new Vue({
    el: "#app",
    data: {
        loading: false,
        tableData: [],
        pageParams: {
            pageNum: 1,
            pageSize: 10,
            total: 10,
        },
        columns: [
            {
                title: "序号",
                type: "index",
            },
            {
                title: "Name",
                key: "name",
                tree: true,
            },
            {
                title: "age",
                key: "age",
            },
            {
                title: "address",
                key: "adress",
            },
        ],
    },
    methods: {
        // todo 请求数据
        featchData() {
            const { pageParams } = this;
            this.loading = true;
            this.tableData = [];
            let timer;
            mockServer("http://test.com", pageParams).then((res) => {
                const {
                    data: { result },
                } = res;
                console.log(res, "=res");
                this.tableData = result;
                if (timer) {
                    clearTimeout(timer);
                }
                // todo 模拟数据延时loading
                timer = setTimeout(() => {
                    this.loading = false;
                }, 2000);
            });
        },
        // todo 点击按钮刷新操作
        handleReflush() {
            this.featchData();
        },
        // 分页参数请求
        handleChangePage(pageNum) {
            this.pageParams = {
                ...this.pageParams,
                pageNum,
            };
            this.featchData();
        },
    },
    mounted() {
        this.featchData();
    },
});

以上代码片段有些长,但是核心思想非常简单,我模拟了一个页面列表需要的数据以及入参请求的分页参数,列表会根据我设置的分页参数,请求拿到数据,渲染到页面中。


接下来看下mockserver.js这个是一个模拟接口的一个工具库,可以看下片段

// 生成mock数据
const mockServer = (path, { pageNum, pageSize, total }) => {
    // 生成随机长度的数组
    const createMapRandom = (len) => {
        const data = new Array(len);
        return data.fill('Maic')
    }
    const childrenData = Mock.mock({
        [`data|${Math.floor(total / pageSize)}`]: [
            {
                "name|1": createMapRandom(100).map(() => Mock.mock("@cname")),
                "age|1": createMapRandom(100).map(() => Mock.mock("@integer(0,100)")),
                "adress|1": createMapRandom(100).map(() => Mock.mock("@city")),
                "id|1": createMapRandom(100).map(() => Mock.mock("@id")),
            },
        ],
    });
    Mock.mock(path, {
        code: 0,
        message: "成功",
        [`result|${pageNum * pageSize}`]: [
            {
                "name|+1": createMapRandom(10).map(() => Mock.mock("@cname")),
                "age|1": createMapRandom(10).map(() => Mock.mock("@integer(0,100)")),
                "adress|1": createMapRandom(10).map(() => Mock.mock("@city")),
                "id|1": createMapRandom(10).map(() => Mock.mock("@id")),
                children: childrenData.data,
            },
        ],
    });
    return axios.get(path)
}

mock数据已经准备ok,我们看下页面就是这样的

b015803302b17daa042da143ee79ecf1.jpg


打开控制台netWorkperfomance monitor可以看到js heap size右侧非常平稳(这里可以看到页面内存溢出情况,如果是平稳的,说明内存溢出的可能性很小),在10条数据时候,页面也非常流畅


当我把总条数调至100

68385ddb929e1026bf070241e9765841.jpg

cpu在我修改总条数,然后点击刷新按钮操作,cpu内存都有往上飙升了,但是内存溢出依然不是很明显,点击页面也并无卡顿。


当我把页面总数调至500时,此时页面内存溢出和cpu又是怎么样

80fbd4170d2125690e9fb4888d757b4a.jpg

当我点击页面刷新按钮操,然后点击列表的树操作时,页面已经有明显的卡顿了,但页面没有卡死,当我直接把总条数修改1000时,整个页面已经卡死。


500条数据就已经感受到页面卡顿了,当为1000条时,页面直接卡死,因此在测试同学极限测试的情况下,生产环境页面直接崩了,这时候,你不可能跟测试说,你为啥要造那么多数据?


在极端情况下,也许就是有测试的这种情况,看了官方文档,临时做了一个补救方案,就是点击那个tree的时候,再异步加载children数据,但是...,第二天测试告诉我,页面又崩了,于是,这种方式是不行了,那么加个页面吧,把树的子集页面用一个弹框页面展示,这样首页只加载第一级数据,二级数据让后端做了个分页查询,再给前端渲染。


终于这样页面不卡顿了,测试添加1000条数据,页面不卡顿了,但是为啥ivew的table渲染数据,会造成页面内存溢出如此严重,去官方github上看了一下table组件的源码

ivewtable组件,是用render,根据columns生成colgroup,根据data生成trtd,具体可以看下table-body[2]

...
render(h) {
  let $tableTrs = [];
  this.data.forEach((row, index) => {
  let $tds = [];
  const $tableTr = h(TableTr, {
      props: {
          draggable: this.draggable,
          row: row,
          'prefix-cls': this.prefixCls
      },
      key: this.rowKey ? row._rowKey : index,
      nativeOn: {
          mouseenter: (e) => this.handleMouseIn(row._index, e),
          mouseleave: (e) => this.handleMouseOut(row._index, e),
          click: (e) => this.clickCurrentRow(row._index, e),
          dblclick: (e) => this.dblclickCurrentRow(row._index, e),
          contextmenu: (e) => this.contextmenuCurrentRow(row._index, e),
          selectstart: (e) => this.selectStartCurrentRow(row._index, e)
      }
  }, $tds);
  // 子数据
  if (row.children && row.children.length) {
      const $childNodes = this.getChildNode(h, row, []);
      $childNodes.forEach(item => {
          $tableTrs.push(item);
      });
  }
  ...
  })
  ...
}

在循环data中创建tr,而且还有递归寻找getChildNode操作,tr上还绑定了许多事件,当我们点击tree时,会触发tr的mouseenter,click等事件,如此多的事件绑定在tr上,在数据量很大的时候,绑定的事件越多,造成内存泄漏越是严重,而且是每个tr上都是直接绑定nativeOn等这些事件。所以ivew的table造成内存的泄漏直接让页面卡死。


ivew的table既然这么不经打,那么我测试下elementUItable是否比ivew更好。


笔者糊了一个一模一样的测试页面

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>element-table</title>
    <style>
      #app {
        margin: 10px;
      }
    </style>
    <link
      rel="stylesheet"
      href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
    />
    <script type="text/javascript" src="./js/vue.min.js"></script>
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <script type="text/javascript" src="./js/mock-min.js"></script>
    <script type="text/javascript" src="./js/axios.min.js"></script>
    <script type="text/javascript" src="./mockserver.js"></script>
  </head>
  <body>
    <div id="app">
      <el-row type="flex">
        <el-col span="5"><h2>element-table性能优化测试</h2></el-col>
        <el-col span="3">
          pageNum<el-input v-model.number="pageParams.pageNum"></el-input>
        </el-col>
        <el-col span="3">
          pageSize<el-input v-model.number="pageParams.pageSize"></el-input>
        </el-col>
        <el-col span="3">
          total<el-input v-model.number="pageParams.total"></el-input>
        </el-col>
        <el-col span="3">
          <el-button type="primary" @click="handleReflush">刷新</el-button>
        </el-col>
      </el-row>
      <el-table
        row-key="id"
        :data="tableData"
        :tree-props="{children: 'children', hasChildren: 'hasChildren'}"
        border
      >
      <el-table-column
        type="index"
        label="序号"
        width="50">
        </el-table-column>
        <el-table-column v-for="(item) in columns" :prop="item.key" :label="item.title">
        </el-table-column>
        </el-table-column>
    </el-table>
    <el-pagination
    :total="pageParams.total"
    :page-size="pageParams.pageSize"
    :page-sizes="[10, 20, 30, 40]"
    @current-change="handleChangePage"
  ></el-pagination>
    </div>
    <script>
        var vm = new Vue({
    el: "#app",
    data: {
        loading: false,
        tableData: [],
        pageParams: {
            pageNum: 1,
            pageSize: 10,
            total: 10,
        },
        columns: [
            {
                title: "Name",
                key: "name",
                tree: true,
            },
            {
                title: "age",
                key: "age",
            },
            {
                title: "address",
                key: "adress",
            },
        ],
    },
    methods: {
        // todo 请求数据
        featchData() {
            const { pageParams } = this;
            this.loading = true;
            this.tableData = [];
            let timer;
            mockServer("http://test.com", pageParams).then((res) => {
                const {
                    data: { result },
                } = res;
                console.log(res, "=res");
                this.tableData = result;
                if (timer) {
                    clearTimeout(timer);
                }
                // todo 模拟数据延时loading
                timer = setTimeout(() => {
                    this.loading = false;
                }, 2000);
            });
        },
        // todo 点击按钮刷新操作
        handleReflush() {
            this.featchData();
        },
         // 分页参数请求
        handleChangePage(pageNum) {
            this.pageParams = {
                ...this.pageParams,
                pageNum,
            };
            this.featchData();
        },
    },
    mounted() {
        this.featchData();
    },
});
    </script>
  </body>
</html>

打开浏览器,直接设置1000elementUItable真的吊打ivew几条街

d9dc126a162ae104955b5895981c8abb.png

cpu几乎没有变化多少,内存泄漏也是几乎没有,在一段时间内,几乎是保持不变的。


5000调试,页面有稍微卡顿了,10000条数据测试,终于把页面搞崩了。点击tree页面明显卡顿,但即使是这样也比ivew1000条的测试数据页面要好得多。

28ea0a6bc603c39b63dbdc6326fa31f7.jpg

由此可见笔者已经把ivewtable最大的问题踩了一个坑。 关于elementUI的table可以去官方库看下,比ivew处理要优雅得多,具体参考ele-body[3]


看到这里,如果table大数据渲染,有没有比较好的实践方案。因为1w条数据的情况,即使是elementUI这么能扛也显得力不从心。


虚拟长列表方案优化


虚拟列表优化,这是大数据量优化的一种手段,大数据渲染dom导致页面卡顿,我们尝试用虚拟长列表方案去实践下


为快速实现业务table性能,我们采用第三方虚拟列表`umy-ui`[4],专门解决table卡顿问题


新建一个index-vitual.html

...
 <u-table
        ref="table"
        :data="tableData"
        :height="height"
        use-virtual
        :row-height="rowHeight"
        :treeConfig="{
            children: 'children',
            expandAll: false,
            lazy: true
          }"
        row-id="id"
        border
      >
        <u-table-column
          type="index"
          width="100"
          label="序号"
          fixed
        ></u-table-column>
        <u-table-column
          v-for="item in columns"
          :key="item.key"
          :prop="item.key"
          :tree-node="item.hasChildren"
          :label="item.title"
        >
        </u-table-column>
      </u-table>
    ...
// js
// 引入UMYUI 组件
const { UTable, UTableColumn } = UMYUI;
  var vm = new Vue({
        el: "#app",
        components: {
          UTable,
          UTableColumn,
        },
        ...
  })

就是引入umy-ui的两个组件即可,主要注意u-table的几个props


1、use-virtual主要是打开虚拟列表


2、height设置一个固定的高度,或者设置一个max-height,如果不需要设置高度,内容需根据内容滚动,则关闭虚拟列表use-virtual这个参数不设置即可


3、treeConfig这个参数设置是否有tree,当设置树时u-table上必须设置row-id="id",否则树不会出来,并且cloumns上设置hasChildren标识


4、u-table-column设置:tree-node属性,指定列中哪个props展开


更多API可以参考官网[5]


当我们将参数调节至首页1000条时,其实table的tr始终中16条左右

d593afb1fa7307c9900e3bbc843829de.jpg

用该方案极大的减少了列表dom的渲染,避免了一次性渲染1000个tr,td。因此极大的提升了table的渲染,页面的性能也会提升不少。


最后,如果你将总条数调至10000,你最后还是会发现页面cpu直接上升至100%,页面明显的卡顿了几秒钟,这也表明,此时无论页面是否虚拟列表方案,造成页面卡顿与js声明数据量也有一定关系,当定义的数据过大,在内存没有释放的这段过程中,如果造成页面内存溢出或者堆栈过大,那么也会造成页面的卡死。


总结


1、ivewtable性能很差,比较elementUI1000数据ivew就能让你浏览器崩掉,所以慎用ivewtable的大数据量,有坑


2、elementUI的table组件很优秀,1000条能扛得住,但上了5000后,就明显扛不住了,所以采用umy-ui虚拟列表渲染


3、umy-ui方案可以极大的优化大数据table渲染,但是数据量超过1w+,甚至更多,那么虚拟列表也是没得救了,页面依然会卡顿。


因此造成页面卡顿的因素很多,我们减少事件操作、闭包、全局变量等等这些尽量减少内存的消耗,以及页面的GUI渲染,这样就可以极大提高页面的访问性能。


关于虚拟长列表方案,后续我会写一篇深究虚拟长列表的技术文章,除了这种方案优化table,笔者想到,另外两种方案


一种是假分页,如果后端一次性返回了1000条数据,那么我在前端按照上拉滚动的方式,每次加载100条的方式去渲染,这样分10页就可以加载完毕了,比起一次性加载1w+应该会有明显的提升,后续会写个测试demo验证一下。


二种是增加二级页面,将大数据做本地indexDB存储,然后从indexDB中做前端分页查询。

相关实践学习
基于MaxCompute的热门话题分析
本实验围绕社交用户发布的文章做了详尽的分析,通过分析能得到用户群体年龄分布,性别分布,地理位置分布,以及热门话题的热度。
SaaS 模式云数据仓库必修课
本课程由阿里云开发者社区和阿里云大数据团队共同出品,是SaaS模式云原生数据仓库领导者MaxCompute核心课程。本课程由阿里云资深产品和技术专家们从概念到方法,从场景到实践,体系化的将阿里巴巴飞天大数据平台10多年的经过验证的方法与实践深入浅出的讲给开发者们。帮助大数据开发者快速了解并掌握SaaS模式的云原生的数据仓库,助力开发者学习了解先进的技术栈,并能在实际业务中敏捷的进行大数据分析,赋能企业业务。 通过本课程可以了解SaaS模式云原生数据仓库领导者MaxCompute核心功能及典型适用场景,可应用MaxCompute实现数仓搭建,快速进行大数据分析。适合大数据工程师、大数据分析师 大量数据需要处理、存储和管理,需要搭建数据仓库?学它! 没有足够人员和经验来运维大数据平台,不想自建IDC买机器,需要免运维的大数据平台?会SQL就等于会大数据?学它! 想知道大数据用得对不对,想用更少的钱得到持续演进的数仓能力?获得极致弹性的计算资源和更好的性能,以及持续保护数据安全的生产环境?学它! 想要获得灵活的分析能力,快速洞察数据规律特征?想要兼得数据湖的灵活性与数据仓库的成长性?学它! 出品人:阿里云大数据产品及研发团队专家 产品 MaxCompute 官网 https://www.aliyun.com/product/odps&nbsp;
相关文章
|
3月前
ruoyi-nbcio增加websocket与测试页面
ruoyi-nbcio增加websocket与测试页面
30 0
|
3月前
|
JSON 监控 测试技术
Groovy脚本编写员工上网行为监控自动化测试
本文介绍了如何使用Groovy脚本创建一个自动化工具来监控员工的网络活动。通过编写简单脚本记录员工访问的网站并打印信息,可进一步扩展为将数据保存至数据库。此外,通过设定定时任务,实现了每30分钟自动监控一次的功能。最后,展示了如何将监控数据转换为JSON格式并使用HTTP POST请求提交到网站,以实现数据的自动化上报,有助于企业保障网络安全、保护数据并提升工作效率。
144 5
|
1月前
|
测试技术 API Android开发
《手把手教你》系列基础篇(九十七)-java+ selenium自动化测试-框架设计篇-Selenium方法的二次封装和页面基类(详解教程)
【7月更文挑战第15天】这是关于自动化测试框架中Selenium API二次封装的教程总结。教程中介绍了如何设计一个支持不同浏览器测试的页面基类(BasePage),该基类包含了对Selenium方法的二次封装,如元素的输入、点击、清除等常用操作,以减少重复代码。此外,页面基类还提供了获取页面标题和URL的方法。
44 2
|
13天前
|
消息中间件 Kafka
使用kafka自带脚本进行压力测试
使用kafka自带脚本进行压力测试
|
1月前
|
机器学习/深度学习 数据采集 人工智能
探索自动化测试的边界:从脚本到智能
在软件开发领域,自动化测试已成为确保产品质量和提升开发效率的关键因素。随着人工智能和机器学习技术的飞速发展,传统的自动化测试方法正面临重大的变革。本文将从多个角度分析自动化测试的现状与未来趋势,探讨如何通过集成先进的技术手段优化测试流程,并预测自动化测试领域的发展方向。
35 1
|
18天前
|
机器学习/深度学习 人工智能 数据挖掘
探索自动化测试的边界:从脚本到智能
在软件测试领域,自动化测试一直是提高测试效率与质量的重要工具。随着技术的发展,自动化测试也在不断进化,从简单的脚本录制回放,到现在结合人工智能的智能测试。本文将探讨自动化测试的发展路径,以及如何通过智能化提升测试的深度和广度,同时分析面临的挑战和未来的可能性。
|
21天前
|
机器学习/深度学习 人工智能 供应链
探索自动化测试的边界:从脚本到智能
在软件质量保障的竞技场上,自动化测试以其高效、一致性和可重复性的特点,成为提升开发流程效率的关键工具。本文将深入自动化测试的核心,探讨它如何从简单的脚本化任务,演变为一个集成了机器学习、人工智能和持续集成/持续部署(CI/CD)的复合型解决方案。我们将一起见证自动化测试如何突破传统边界,实现从线性脚本到自适应智能体的飞跃,并在此过程中重塑软件开发的未来。
|
2月前
|
数据管理 测试技术 持续交付
自动化测试的进阶之路:从脚本到框架
【6月更文挑战第28天】在软件开发的生命周期中,自动化测试是确保产品质量和提升开发效率的关键步骤。本文深入探讨了自动化测试的演变历程,从简单的脚本编写到构建复杂的测试框架,揭示了如何通过持续集成和持续部署(CI/CD)实现自动化测试的高效执行。文章不仅介绍了自动化测试的基本概念和工具,还提供了实用的策略和技巧,帮助读者理解如何在现代软件工程实践中有效地应用自动化测试,以及如何克服常见的挑战。
|
1月前
|
敏捷开发 测试技术
探索式测试与脚本化测试的融合之道
在软件测试领域,探索式测试和脚本化测试常被视为对立面。然而,随着敏捷开发的普及和测试自动化需求的增加,两者之间的界限逐渐模糊。本文将探讨如何有效地结合这两种测试方法,以实现更高效、更全面的软件测试策略。我们将通过实际案例分析,展示融合探索式与脚本化测试的优势,并提供实施建议。
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
自动化测试的演进之路:从脚本到智能
【6月更文挑战第18天】自动化测试作为软件质量保证的重要手段,其发展历程映射了技术进步和行业需求的变化。本文旨在探讨自动化测试技术从简单的脚本编写逐步演变为集成化、智能化的测试解决方案的过程。文章将分析自动化测试面临的挑战,介绍当前流行的框架和工具,并展望自动化测试的未来趋势,特别是人工智能如何重塑测试实践,提升测试效率和有效性。
86 2