在实际的性能测试中,会遇到各种各样的问题,比如 TPS 压不上去等,导致这种现象的原因有很多,测试人员应配合开发人员进行分析,尽快找出瓶颈所在。
理想的性能测试指标结果可能不是很高,但一定是平缓的。
注意:性能测试调优并不是一次完成的过程,针对同一个性能问题,上述步骤可能要经过多次循环才能最终完成性能调优的目标,即:测试发现问题 -> 找原因 -> 调整 -> 验证 -> 分析 -> 再测试 …
60%:数据库瓶颈
数据库服务器 CPU 使用率高(慢查询、SQL 过多、连接数过多)
抛出连接数过多(连接池设置太小,导致连接排队)
数据库出现死锁
25%:应用瓶颈
10%:压测工具瓶颈
5%:Linux 机器出现异常
监控内容:CPU 使用率、CPU 使用类型(用户进程、内核进程)
瓶颈分析:CPU已压满(接近 100%),需要再看其他指标的拐点所出现的时刻是否与 CPU 压满的时刻基本一致。
监控内容:实际内存、虚拟内存
瓶颈分析:内存不足时,操作系统会使用虚拟内存,从虚拟内存读取数据,影响处理速度。
监控内容:I/O 速度、磁盘等待队列
瓶颈分析:磁盘 I/O 成为瓶颈时,会出现磁盘I/O繁忙,导致交易执行时在 I/O 处等待。
监控内容:网络流量(带宽使用率)、网络连接状态
瓶颈分析:如果接口传递的数据包过大,超过了带宽的传输能力,就会造成网络资源竞争, 导致 TPS 上不去。
发现了瓶颈后,只要对症下药就可以了。简单来说无论哪个地方出现瓶颈,只需要降低压力或者增加这部分瓶颈资源(应用软件没有瓶颈或优化空间之后),即可缓解症状。
后台服务的所有指令和数据处理都是由 CPU 负责,服务对 CPU 的利用率对服务的性能起着决定性的作用。
top 参数详解
下面以 top 命令的输出例,对 CPU 各项主要指标进行说明:
像 shell 程序、各种语言的编译器、数据库应用、web 服务器和各种桌面应用都算是运行在用户地址空间的进程。
这些程序如果不是处于 idle 状态,那么绝大多数的 CPU 时间都是运行在用户态。
所有进程要使用的系统资源都是由 Linux 内核处理的。当处于用户态(用户地址空间)的进程需要使用系统的资源时,比如需要分配一些内存、或是执行 I/O 操作、再或者是去创建一个子进程,此时就会进入内核态(内核地址空间)运行。事实上,决定进程在下一时刻是否会被运行的进程调度程序就运行在内核态。
对于操作系统的设计来说,消耗在内核态的时间应该是越少越好。通常 sy 比例过高意味着被测服务在用户态和系统态之间切换比较频繁,此时系统整体性能会有一定下降。
在实践中有一类典型的情况会使 sy 变大,那就是大量的 I/O 操作,因此在调查 I/O 相关的问题时需要着重关注它。
大部分后台服务使用的 CPU 时间片中 us 和 sy 的占用比例是最高的。同时这两个指标又是互相影响的,us 的比例高了,sy 的比例就低,反之亦然。
另外,在使用多核 CPU 的服务器上,CPU 0 负责 CPU 各核间的调度,CPU 0 上的使用率过高会导致其他 CPU 核心之间的调度效率变低。因此测试过程中需要重点关注 CPU 0。
每个 Linux 进程都有个优先级,优先级高的进程有优先执行的权利,这个叫做 pri。进程除了优先级外,还有个优先级的修正值。这个修正值就叫做进程的 nice 值。
这里显示的 ni 表示调整过 nice 值的进程消耗掉的 CPU 时间。如果系统中没有进程被调整过 nice 值,那么 ni 就显示为 0。
一般来说,被测服务和服务器整体的 ni 值不会很高。如果测试过程中 ni 的值比较高,需要从服务器 Linux 系统配置、被测服务运行参数查找原因。
一般情况下, us + ni + id 应该接近 100%。
线上服务运行过程中,需要保留一定的 id 冗余来应对突发的流量激增。
在性能测试过程中,如果 id 一直很低,吞吐量上不去,需要检查被测服务线程/进程配置、服务器系统配置等。
和 CPU 的处理速度相比,磁盘 I/O 操作是非常慢的。有很多这样的操作,比如:CPU 在启动一个磁盘读写操作后,需要等待磁盘读写操作的结果。在磁盘读写操作完成前,CPU 只能处于空闲状态。
Linux 系统在计算系统平均负载时会把 CPU 等待 I/O 操作的时间也计算进去,所以在我们看到系统平均负载过高时,可以通过 wa 来判断系统的性能瓶颈是不是过多的 I/O 操作造成的。
磁盘、网络等 I/O 操作会导致 CPU 的 wa 指标提高。通常情况下,网络 I/O 占用的 wa 资源不会很高,而频繁的磁盘读写会导致 wa 激增。
如果被测服务不是 I/O 密集型的服务,那需要检查被测服务的日志量、数据载入频率等。
如果 wa 高于 10% 则系统开始出现卡顿;若高于 20% 则系统几乎动不了;若高于 50% 则很可能磁盘出现故障。
硬中断是外设对 CPU 的中断,即外围硬件发给 CPU 或者内存的异步信号就是硬中断信号;软中断由软件本身发给操作系统内核的中断信号。
通常是由硬中断处理程序或进程调度程序对操作系统内核的中断,也就是我们常说的系统调用(System Call)。
在性能测试过程中,hi 会有一定的 CPU 占用率,但不会太高。对于 I/O 密集型的服务,si 的 CPU 占用率会高一些。
只有 Linux 在作为虚拟机运行时 st 才是有意义的。它表示虚机等待 CPU 资源的时间(虚机分到的是虚拟 CPU,当需要真实的 CPU 时,可能真实的 CPU 正在运行其它虚机的任务,所以需要等待)。
性能分析思路
wa(IO wait)的值过高,表示硬盘存在 I/O 瓶颈。
id(idle)值高,表示 CPU 较空闲。
如果 id 值高但系统响应慢时,有可能是 CPU 等待分配内存,此时应加大内存容量。
如果 id 值持续低于 10,那么系统的 CPU 处理能力相对较低,表明系统中最需要解决的资源是 CPU。
案例分析
现象:CPU 的 us 和 sy 不高,但 wa 很高。
如果被测服务是磁盘 I/O 密集型服务,wa 高属于正常现象。但如果不是此类服务,最可能导致 wa 高的原因有两个:
Linux 的系统负载指在特定时间间隔内(一个 CPU 周期)运行队列中的平均进程数。
(注意:Linux 中的 Load 体现的是整体系统负载,即 CPU 负载 + 磁盘负载 + 网络负载 + 其余外设负载,并不能完全等同于 CPU 使用率。而在其余系统如 Unix,Load 还是只代表 CPU 复杂。)
从服务器负载的定义可以看出,服务器运行最理想的状态是所有 CPU 核心的运行队列都为 1,即所有活动进程都在运行,没有等待。这种状态下服务器运行在负载阈值下。
通常情况下,按照经验值,服务器的负载应位于阈值的 70%~80%,这样既能利用服务器大部分性能,又留有一定的性能冗余应对流量增长。
Linux 提供了很多查看系统负载的命令,最常用的是 top 和 uptime。
top 和 uptime 针对负载的输出内容相同,都是系统最近 1 分钟、5 分钟、15 分钟的负载均值:
这三个数值的使用方法和 CPU 核数相关,首先确认 CPU 物理总核数:
示例:
[root@localhost home]# cat /proc/cpuinfo |grep "physical id"
physical id : 0
physical id : 0
[root@localhost home]# cat /proc/cpuinfo |grep "cpu cores"
cpu cores : 2
cpu cores : 2
物理 CPU 个数为 0+1=1 个,每个 CPU 的核数为 2 个,所以总的物理核数为 2x1=2。
计算结果说明该机器的在单位时间内可以处理的进程数是 2 个,如果单位时间内进程数超过 2 个,就会出现拥堵的情况,load 就会持续增高,增高到一定程度,就会出现系统崩溃等异常情况。
在性能测试过程中,系统负载是评价整个系统运行状况最重要的指标之一。通常情况下:
机器针对突发情况的处理
性能测试过程中对内存监控的主要目的是检查被测服务所占用内存的波动情况。
top 参数详解
在 Linux 系统中有多个命令可以获取指定进程的内存使用情况,最常用的是 top 命令,如下图所示:
VIRT:进程所使用的虚拟内存的总数。它包括所有的代码,数据和共享库,加上已换出的页面,所有已申请的总内存空间。
RES:进程正在使用的没有交换的物理内存(栈、堆)。申请内存后该内存段已被重新赋值。
SHR:进程使用共享内存的总数。该数值只是反映可能与其它进程共享的内存,不代表这段内存当前正被其他进程使用。
SWAP:进程使用的虚拟内存中被换出的大小。交换的是已经申请但没有使用的空间(包括栈、堆、共享内存)。
DATA:进程除可执行代码以外的物理内存总量,即进程栈、堆申请的总空间。
从上面的解释可以看出,测试过程中主要监控 RES 和 VIRT。对于使用了共享内存的多进程架构服务,还需要监控 SHR。
free 参数详解
free 命令显示系统内存的使用情况,包括物理内存、交换内存(swap)和内核缓冲区内存。如果加上 -h 选项(控制显示单位),输出的结果会友好很多:
有时我们需要持续的观察内存的状况,此时可以使用 -s 选项并指定间隔的秒数:如 free -h -s 3 表示每隔 3 秒输出一次内存的使用情况,直到按下 ctrl + c。
swap space 是磁盘上的一块区域,可以是一个分区,也可以是一个文件,所以具体的实现可以是 swap 分区也可以是 swap 文件。当系统物理内存吃紧时,Linux 会将内存中不常访问的数据保存到 swap 上,这样系统就有更多的物理内存为各个进程服务,而当系统需要访问 swap 上存储的内容时,再将 swap 上的数据加载到内存中,这就是常说的换出和换入。
交换空间可以在一定程度上缓解内存不足的情况,但是它需要读写磁盘数据,所以性能不是很高。因此当交换空间内存开始使用,则表明内存严重不足。
如果系统内存充足或是做性能压测的机器,可以使用 swapoff -a 关闭交换空间,或在 /etc/sysctl.conf 文件中设置 swappiness 值。如果系统内存不富余,则需要根据物理内存的大小来设置交换空间的大小,具体的策略网上有很丰富的资料。
在吞吐量固定的前提下,如果内存持续上涨,那么很有可能是被测服务存在明显的内存泄漏,需要使用 valgrind 等内存检查工具进行定位。
Linux 内核为了提升磁盘操作的性能,会消耗一部分空闲内存去缓存磁盘数据,就是 buffer 和 cache。
如果给所有应用分配足够内存后,物理内存还有剩余,linux 会尽量再利用这些空闲内存,以提高整体 I/O 效率,其方法是把这部分剩余内存再划分为 cache 及 buffer 两部分加以利用。
所以,空闲物理内存不多,不一定表示系统运行状态很差,因为内存的 cache 及 buffer 部分可以随时被重用,在某种意义上,这两部分内存也可以看作是额外的空闲内存。
从应用程序的角度来说,available = free + buffer + cache。请注意,这只是一个很理想的计算方式,实际中的数据往往有较大的误差。
释放缓存内存
方式一:手动释放缓存内存
snyc
echo 3 > /proc/sys/vm/drop_caches
free -m
方式二:修改 linux 配置自动释放
/proc/sys/vm/drop_caches 这个值的 0 改为 1
性能测试过程中,如果被测服务对磁盘读写过于频繁,会导致大量请求处于 I/O 等待的状态,系统负载升高,响应时间变长,吞吐量下降。
iostat 参数详解
Linux 下可以用 iostat 命令来监控磁盘状态。
iostat -d 2 10 表示每 2 秒统计一次基础数据,统计 10 次:
tps:该设备每秒的传输次数。“一次传输”意思是“一次 I/O 请求”。多个逻辑请求可能会被合并为“一次 I/O
请求”。“一次传输”请求的大小是未知的。
kB_read/s:每秒从设备(driveexpressed)读取的数据量,单位为 Kilobytes。
kB_wrtn/s:每秒向设备(driveexpressed)写入的数据量,单位为 Kilobytes。
kB_read:读取的总数据量,单位为 Kilobytes。
kB_wrtn:写入的总数量数据量,单位为 Kilobytes。
从 iostat -d 的输出中,能够获得系统运行最基本的统计数据。但对于性能测试来说,这些数据不能提供更多的信息。需要加上 -x 参数。
iostat -x 参数详解
如 iostat -x 2 10 表示每 2 秒统计一次更详细数据,统计 10 次:
当系统调用需要读取数据的时候,VFS 将请求发到各个 FS,如果 FS 发现不同的读取请求读取的是相同 Block 的数据,FS 会将这个请求合并 Merge。
await 的大小一般取决于服务时间(svtcm)以及 I/O 队列的长度和 I/O 请求的发出模式。假设 svtcm 比较接近 await,说明 I/O 差点没有等待时间。
假设 await 远大于 svctm(如大于 5),就要考虑 I/O 有压力瓶颈,说明 I/O 队列太长,应用得到的响应时间变慢。假设响应时间超过了用户能够容许的范围,这时可以考虑更换更快的磁盘。
例如,如果统计间隔 1 秒,该设备有 0.8 秒在处理 I/O,而 0.2 秒闲置,那么该设备的 %util = 0.8/1 = 80%,该参数暗示了设备的繁忙程度。
%util 接近100% 表明 I/O 请求太多,I/O 系统繁忙,磁盘可能存在瓶颈。
iostat -x 完整参数如下:
- rrqm/s: 每秒进行 merge 的读操作数目。即 delta(rerge)/s
- wrqm/s: 每秒进行 merge 的写操作数目。即 delta(wmerge)/s
- t/s: 每秒完成的读 I/O 设备次数。即 delta(rioVs
- w/s: 每秒完成的写 1/O 设备次数。即 delta(wio)/s
- rsec/s: 每秒读扇区数。即 delta(rsect)/s
- ws0c/s: 每秒写扇区数。即 deita(wsect)/s
- rkB/s: 每秒读 K 字节数。是 rsect/s 的一半,因为每扇区大小为 512 字节。(需要计算)
- wkB/s: 每秒写 K 字节数。是 wsect/s 的一半。(需要计算)
- avgrq+sz: 平均每次设备 I/O 操作的数据大小(扇区)。delta(rsect+wsect)/delta(rio+wio)
- avgqu-sz: 平均I/O队列长度,即delta(avea)/s/1000(因为 aveq 的单位为毫秒)。
- await: 平均每次设备 I/O 操作的等待时间(毫秒)。即 delta(ruse+wuse)/delta(rio+wio)
- svctm: 平均每次设备 I/O 操作的服务时间(毫秒)。即 delta(use)/delta(rio+wio)
- %util:一秒中有百分之多少的时间用于 I/O 操作,或者说一秒中有多少时间 I/O 队列是非空的。即 delta(use)/s/1000(因为 use 的单位为毫秒)
性能测试中网络监控主要包括网络流量、网络连接状态的监控。
网络流量监控
方法很多,网上有很多 shell 脚本。也可以使用 nethogs 命令。该命令与 top 类似,是一个实时交互的命令,运行界面如下:
在后台服务性能测试中,对于返回文本结果的服务,并不需要太多关注在流量方面。
理解带宽
针对一些特定的应用,比如直播或网盘(文件上传下载),带宽瓶颈也是一个出现频率较高的场景。
服务端的带宽分为上行(out)和下行(in)带宽(分别对应客户端的下载和上传)。
看视频看新闻使用带宽:客户端的下载、服务端的上行带宽。
服务端接收客户端的数据使用带宽:客户端的上传、服务端的下行带宽。
一个 Web 服务器如各类新闻网站通常需要更多的服务端上行(out)带宽;而邮件服务器、网盘服务器等则通常需要更多的服务端下行带宽(in)。
理解带宽速率公式
1 Mb/s 带宽速度为 128 KB/s(1024Kb / 8KB)
100 Mb/s 带宽速度为 12.5 Mb/s(考虑网络损耗通常按 10M/s 或 1280KB/s 算)
示例:5000 万像素手机拍一张照片,照片大小约 20MB,在下述带宽下需要耗时:
10M 带宽约 20 秒:耗时 = 流量 / 速率 = 20MB / (10Mb/8) = 20 / 1.25 = 16 秒(按 1MB/s=128KB/s 速度算即 20 秒)
100M 带宽约 2 秒:耗时 = 流量 / 速率 = 20MB / (100Mb/8) = 20 / 12.5 = 1.6 秒(按 10MB/s=128KB/s 速度算即 2 秒)
1000M 带宽约 0.2 秒:耗时 = 流量 / 速率 = 20MB / (1000Mb/8) = 20 / 125 = 0.16 秒(按 100MB/s=128KB/s 速度算即 0.2 秒)
案例分析
现象:从监控图表可以看出,当前的网络流量已经基本将网络带宽占满,因此网络存在瓶颈。
解决方案:
网络连接状态监控
性能测试中对网络的监控主要是监控网络连接状态的变化和异常。
对于使用 TCP 协议的服务,需要监控服务已建立连接的变化情况(即 ESTABLISHED 状态的 TCP 连接)。
对于 HTTP 协议的服务,需要监控被测服务对应进程的网络缓冲区的状态、TIME_WAIT 状态的连接数等。
Linux 自带的很多命令如 netstat、ss 都支持如上功能。
下图是 netstat 对指定 pid 进程的监控结果:
完整命令输出:
如 MySQL 资源出现瓶颈,首先找慢查询(超过自定义的执行时间阈值的 SQL)。
1)通过 SQL 语句定位到慢查询日志的所在目录,然后查看日志。
show variables like "slow%";
2)慢查询日志在查询结束以后才纪录,所以在应用反映执行效率出现问题时,查询慢查询日志并不能定位问题。这时可以使用show processlist命令查看当前 MySQL 正在进行的线程状态,可以实时地查看 SQL 的执行情况。
示例:
mysql -uroot -p123456 -h127.0.0.1 -p3307 -e "show full processlist" |grep dbname |grep -v NULL
3)找到慢查询 SQL 后可以用执行计划(explain)进行分析(或反馈给 DBA 和开发处理)。推荐最简单的排查方式,步骤如下:
数据库连接池的使用率
查看/设置最大连接数
-- 查看最大连接数
mysql> show variables like '%max_connection%';
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| extra_max_connections | |
| max_connections | 2512 |
+-----------------------+-------+
2 rows in set (0.00 sec)
-- 重新设置最大连接数
set global max_connections=1000;
在/etc/my.cnf 里面设置数据库的最大连接数
[mysqld]
max_connections = 1000
查看当前连接数
mysql> show status like 'Threads%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| Threads_cached | 32 |
| Threads_connected | 10 |
| Threads_created | 50 |
| Threads_rejected | 0 |
| Threads_running | 1 |
+-------------------+-------+
5 rows in set (0.00 sec)
Threads_connected:表示当前连接数。跟 show processlist 结果相同。准确的来说,Threads_running 代表的是当前并发数。
Threads_running:表示激活的连接数。一般远低于 connected 数值。
Threads_created:表示创建过的线程数。
如果我们在 MySQL 服务器配置文件中设置了 thread_cache_size,那么当客户端断开之后,服务器处理此客户的线程将会缓存起来以响应下一个客户而不是销毁(前提是缓存数未达上限)。
如果发现 Threads_created 值过大的话,表明 MySQL 服务器一直在创建线程,这也是比较耗资源,因此可以适当增加配置文件中 thread_cache_size 值。
查询服务器 thread_cache_size 的值
mysql> show variables like 'thread_cache_size';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| thread_cache_size | 100 |
+-------------------+-------+
1 row in set (0.00 sec)
详见《MySQL 事务和锁》。
通常,SQL 查询是从磁盘中的数据库文件中读取数据。
若当某一个 SQL 查询语句之前执行过,则该 SQL 语句及查询结果都会被缓存下来,下次再查询相同的 SQL 语句时,就会直接从数据库缓存中读取。(注意,MySQL 8 开始已废弃查询缓存功能。)
监控点
业务执行过程中 SQL 查询时的缓存命中率(查询语句读取缓存的次数占总查询次数的比例)。
如果缓存命中率过低,需要优化对应的代码和 SQL 查询语句,以提高缓存命中率。
测试结果分析
结论:从目前的测试结果来看(如下图所示),性能存在问题。
现象:并发数达到 50 时的 TPS 为 52,此时虽然响应时间为 4.4s(小于需求的 5s),但是数据库服务器的 CPU 使用率非常高(接近 100%),因此需要重点关注数据库的调优分析。
排查过程
1、使用 top 命令观察,确定是 mysqld 导致还是其他原因。
CPU 分为用户 CPU 和内核 CPU。综合其他的各项资源指标来分析,发现内存、磁盘IO、网络等指标无任何异常,因此判断此处不是内核 CPU 占用高,主要原因是用户进程占用的 CPU 高。
确认目前 CPU 占用高的为 mysqld 进程。
2、分析数据库服务器 CPU 高的可能原因:慢 SQL、SQL 语句过多、连接数过多等。
确认是否存在慢 SQL:
查看慢查询日志,看看是否有超过预期指标的 SQL 语句,并分析排查:看看执行计划是否准确、索引是否缺失、数据量是否太大等。
目前案例经过慢查询日志的分析,未存在慢查询。
确认是否 SQL 语句过多或连接数过多:
使用show full processlist查看当前数据库中正在执行的 SQL 语句及连接池的状态,发现大量 SQL 在等待执行。
再结合操作过程中的系统日志进行分析,发现每进入一次商城首页,就需要在数据库中执行 19 条查询 SQL。
解决方案
硬件解决:增加 CPU。
软件解决:为减少一次性加载过多 SQL,可考虑使用分批次、异步加载的方式(展示到什么位置,就查询什么位置的数据)。
JVM 简介
JVM(JAVA Virtual Machine):虚拟出来的空间,专门供 JAVA 程序运行。
JVM 内存
重点关注:堆区(动态变化)
所有的对象在初始化都会申请堆区的空间,如果已申请的空间在使用结束后没有及时地释放,那么该空间就会被占用,即内存泄漏。
监控点:因此在测试时,需要关注堆区的空间是否持续上升而没有下降。
案例分析
现象:堆内存使用是持续升高,无法降低到之前的水平。
解决方案:找到内存泄漏的代码,并优化代码。
什么是垃圾回收机制
垃圾回收指将内存中已申请并使用完成的那部分内存空间回收,供新申请使用。
垃圾回收机制都是针对堆区的内存进行的。
监控点
系统在做垃圾回收时,不能够处理任何用户业务的。如果垃圾回收过于频繁,导致系统业务处理能力下降。
由于 Full GC 内存比较大,垃圾回收一次时间比较长,那么这段时间内都不能处理业务,对系统影响比较大,因此我们需要关注Full GC 频率。
垃圾回收机制的运行步骤如下:
1、新程序执行时需要先申请内存空间,会先从年轻代中申请。
2、在年轻代满了以后,就会进行垃圾回收Young GC。
3、回收时检查年轻代中的内存,是否还在使用。还在使用的部分会移存到生存区 2 中;不使用的部分则释放,此时年轻代内存空间被清空。
4、新程序执行申请内存空间,再从年轻代申请。
5、年轻代又满了,就会进行垃圾回收Young GC。还在使用的内存移存到生存区 1 中,并把生存区 2 中的内存也都存到生存区 1 中。此时就会清空年轻代和生存区 2。
6、循环上述 1-5 步。
7、如果部分内存在生存区中存活很久(内存在生存区中移动了 10 次左右),则将这部分内存放入到老年代中。
8、循环上述 1-7 步,直到老年代内存空间全部占满,此时就要进行垃圾回收Full GC。
最后: 可以加入群号:914172719 ! 进去有许多资料共享!资料都是面试时面试官必问的知识点,也包括了很多测试行业常见知识,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。
如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦!
转行面试,跳槽面试,软件测试人员都必须知道的这几种面试技巧!
面试经:一线城市搬砖!又面软件测试岗,5000就知足了…
面试官:工作三年,还来面初级测试?恐怕你的软件测试工程师的头衔要加双引号…
什么样的人适合从事软件测试工作?
那个准点下班的人,比我先升职了…
测试岗反复跳槽,跳着跳着就跳没了…