文章系转载,方便整理和归纳,源文地址 https://developer.aliyun.com/article/62845

简介: 本文将对Redis内核单元测试框架进行基本的解析,并对如何编写测试用例进行基本的讲解。

在修改Redis内核之后,第一步我们需要做的就是添加或者对应的单元测试用例来进行基本的单元测试。本文将对Redis内核单元测试框架进行基本的解析,并对如何编写测试用例进行基本的讲解。

单元测试框架流程

Redis单元测试框架是基于tcl sh脚本实现的,其启动的方式为runtest [options]。
每一类的测试case写在单独的测试文件中,测试文件列表写入到test_server中all_tests列表中。
在启动测试时,会以server模式启动一个测试服务器,再启动多个测试客户端与之通信。由测试服务器会给空闲的测试服务端发送测试任务,参数为测试用例所在脚本文件名,由测试客户端执行对应的测试用例。详细的流程图如下:

1. processOptions

对选项进行解析,其中默认的模式是server模式,进入test_server_main函数; 若带了client选项则进入test_client_main函数.

2. test_server_main

  1. 内部维护了一系列当前的测试客户端状态列表;
  2. accept_test_clients 创建一个socket fd,侦听来自测试客户端的消息;
  3. 按照传入的参数,以runtest --client的方式启动n个测试client;

3. test_client_main

启动测试客户端,往测试服务器的fd上发送ready消息,开启客户端与服务端的交互流程;

4. 客户端与服务端的交互

  1. 客户端启动后,往测试服务器fd上发送ready消息;
  2. 服务端收到客户端ready或done事件后,检查所有的测试集,若还有测试任务未完成,则使用signal_idle_client方法往测试客户端发送测试任务,即"run 测试用例脚本文件名"消息;
  3. 客户端收到run消息后,调用execute_tests $data方法执行测试用例脚本文件;
  4. 客户端执行完测试case脚本后,往服务端发送done事件。再次进入第2步;

测试用例编写

1. 增加测试用例

新建一个测试用例文件,比如dummy.tcl,将之加入到test_helper.tcl的all_tests列表里

set ::all_tests {unit/auth...unit/dummy...
}

这样启动测试的时候,会自动执行unit/dummy.tcl里面的测试用例;

2. 测试用例文件

每个测试用例文件里面可以包含多个start_server的部分,每个start_server都会启动一个redis实例。
每个start_server内部包含多个test函数模块,每个test函数对应一个测试用例。
例子:auth.tcl

start_server {tags {"auth"}} {test {AUTH fails if there is no password configured server side} {catch {r auth foo} errset _ $err} {ERR*no password*}
}start_server {tags {"auth"} overrides {requirepass foobar}} {test {AUTH fails when a wrong password is given} {catch {r auth wrong!} errset _ $err} {ERR*invalid password}test {Arbitrary command gives an error when AUTH is required} {catch {r set foo bar} errset _ $err} {NOAUTH*}test {AUTH succeeds when the right password is given} {r auth foobar} {OK}test {Once AUTH succeeded we can actually send commands to the server} {r set foo 100r incr foo} {101}
}

3. 启动redis实例

启动单个实例

使用start_server可以启动一个redis实例. 启动的时候接受三种类型的参数:

  1. config: redis server的配置文件名,文件放到tests/assets目录下;
  2. override: 覆盖配置文件中的某个具体配置;
  3. tags: 该server的标示,一般用于log输出;
    启动一个redis实例的例子可以见上一节的auth.tcl.

启动多个实例

在进行主从同步测试,集群测试的时候,需要同时起多个redis实例,直接在一个test_server内部,再执行test_server即可。
例子:

start_server {tags {"repl"}} {start_server {} {test {First server should have role slave after SLAVEOF} {r -1 slaveof [srv 0 host] [srv 0 port]after 1000s -1 role} {slave}}
}

4. 执行redis命令

测试case中执行redis命令用r函数.(s函数与r函数类似,只是s函数会从info中提取返回值)

proc r {args} {set level 0if {[string is integer [lindex $args 0]]} {set level [lindex $args 0]set args [lrange $args 1 end]}[srv $level "client"] {*}$args
}

当同时启动多个redis实例时,使用r函数的第一个参数,标示具体在哪个实例上执行对应的命令。0为当前redis实例,-1为上一个启动的redis实例,以此类推。例如:

start_server {tags {"repl"}} {r set mykey foostart_server {} {test {Second server should have role master at first} {s role} {master}test {SLAVEOF should start with link status "down"} {r slaveof [srv -1 host] [srv -1 port]s master_link_status} {down}}
}

5. 结果判断

结果判断有几种方式:

  • assert类:详见support/test.tcl
  • fail “comment”: 失败
  • test函数最后一个参数,支持正则表达式。其匹配的对象是最后一条redis命令返回的结果。例如:
    test {AUTH fails when a wrong password is given} {catch {r auth wrong!} errset _ $err} {ERR*invalid password}test {Arbitrary command gives an error when AUTH is required} {catch {r set foo bar} errset _ $err} {NOAUTH*}

6. 同步等待函数

wait_for_condition {maxtries delay e else elsescript}函数可以同步等待指定条件被满足。例:

        test "Fixed AOF: Keyspace should contain values that were parseable" {set client [redis [dict get $srv host] [dict get $srv port]]wait_for_condition 50 100 {[catch {$client ping} e] == 0} else {fail "Loading DB is taking too much time."}assert_equal "hello" [$client get foo]assert_equal "" [$client get bar]}

7. 随机生成数据

start_write_load {host port seconds}函数可以不停的往实例中写入数据。

8. 一个稍复杂的例子

下面是一个主从同步的例子,作为这一节的结束和测试。

foreach dl {no yes} {start_server {tags {"repl"}} {set master [srv 0 client]$master config set repl-diskless-sync $dlset master_host [srv 0 host]set master_port [srv 0 port]set slaves {}set load_handle0 [start_write_load $master_host $master_port 3]set load_handle1 [start_write_load $master_host $master_port 5]set load_handle2 [start_write_load $master_host $master_port 20]set load_handle3 [start_write_load $master_host $master_port 8]set load_handle4 [start_write_load $master_host $master_port 4]start_server {} {lappend slaves [srv 0 client]start_server {} {lappend slaves [srv 0 client]start_server {} {lappend slaves [srv 0 client]test "Connect multiple slaves at the same time (issue #141), diskless=$dl" {# Send SALVEOF commands to slaves[lindex $slaves 0] slaveof $master_host $master_port[lindex $slaves 1] slaveof $master_host $master_port[lindex $slaves 2] slaveof $master_host $master_port# Wait for all the three slaves to reach the "online"# state from the POV of the master.set retry 500while {$retry} {set info [r -3 info]if {[string match {*slave0:*state=online*slave1:*state=online*slave2:*state=online*} $info]} {break} else {incr retry -1after 100}}if {$retry == 0} {error "assertion:Slaves not correctly synchronized"}# Wait that slaves acknowledge they are online so# we are sure that DBSIZE and DEBUG DIGEST will not# fail because of timing issues.wait_for_condition 500 100 {[lindex [[lindex $slaves 0] role] 3] eq {connected} &&[lindex [[lindex $slaves 1] role] 3] eq {connected} &&[lindex [[lindex $slaves 2] role] 3] eq {connected}} else {fail "Slaves still not connected after some time"}# Stop the write loadstop_write_load $load_handle0stop_write_load $load_handle1stop_write_load $load_handle2stop_write_load $load_handle3stop_write_load $load_handle4# Make sure that slaves and master have same# number of keyswait_for_condition 500 100 {[$master dbsize] == [[lindex $slaves 0] dbsize] &&[$master dbsize] == [[lindex $slaves 1] dbsize] &&[$master dbsize] == [[lindex $slaves 2] dbsize]} else {fail "Different number of keys between masted and slave after too long time."}# Check digestsset digest [$master debug digest]set digest0 [[lindex $slaves 0] debug digest]set digest1 [[lindex $slaves 1] debug digest]set digest2 [[lindex $slaves 2] debug digest]assert {$digest ne 0000000000000000000000000000000000000000}assert {$digest eq $digest0}assert {$digest eq $digest1}assert {$digest eq $digest2}}}}}}
}

总结

Redis内核自动化测试框架可以同时启动多个测试客户端进行测试,其测试用例编写简便,测试效率高,使用起来非常方便。
该测试框架也可以很方便的改造成其它基于socket通信的服务的自动化测试框架。
简约,高效,这就是我对它的印象。

redis内核单元测试框架相关推荐

  1. Zephyr单元测试框架:ztest/twister的使用和介绍

    目录 简介 Ztest 简介 注意事项 宏函数 ZTEST ZTEST_USER ZTEST_RULE 常用宏函数封装 ztest_test_suite ztest_unit_test ztest_r ...

  2. Java单元测试框架与实践(Junit5 + Mockito)

    Java单元测试框架与实践 本文首先在理论上归纳了单元测试在宏观和微观层面要遵循的基本原则,以及测试覆盖率的要求和评价维度.然后具体阐述了笔者实战中总结的基于Junit + Mockito 的单元测试 ...

  3. python 单元测试_聊聊 Python 的单元测试框架(一):unittest

    本文首发于 HelloGitHub 公众号,并发表于 Prodesire 博客. 前言 说到 Python 的单元测试框架,想必接触过 Python 的朋友脑袋里第一个想到的就是 unittest. ...

  4. python装饰器执行顺序_python unittest单元测试框架-3用例执行顺序、多级目录、装饰器、fixtures...

    1.用例执行顺序 unittest默认会按照ascii码的顺序,依次执行.类名--方法名排序,使用discover也是默认排序.如果不想使用默认排序,就使用testsuite测试集的方式. impor ...

  5. pytest测试框架_聊聊 Python 的单元测试框架(三):最火的 pytest

    本文首发于 HelloGitHub 公众号,并发表于 Prodesire 博客. 一.介绍 本篇文章是<聊聊 Python 的单元测试框架>的第三篇,前两篇分别介绍了标准库 unittes ...

  6. Selenium+Python ---- 免登录、等待、unittest单元测试框架、PO模型

    1.免登录在进行测试的过程中难免会遇到登录的情况,给测试工作添加了工作量,本文仅提供一些思路供参考解决方式:手动请求中添加cookies.火狐的profile文件记录信息实现.人工介入.万能验证码.去 ...

  7. [测试]单元测试框架NUnit

    说到测试,相信大家都或多或少了解. 按照各自分类,就自己知道包括 A.单元测试.集成测试.系统测试 B.白盒测试.黑盒测试 C.压力测试.性能测试.安全测试 ...... 反正是太多太多.就做开发以来 ...

  8. Python单元测试框架Pyunit 的使用

    Python单元测试框架Pyunit 使用示例: 1 import unittest 2 3 class Person: 4 def age(self): 5 return 34 6 def name ...

  9. 单元测试框架之unittest(一)

    一.单元测试的含义 unittest单元测试框架的设计灵感来源于Junit(Java语言的单元测试框架),它与其他语言的单元测试框架风格相类似,支持自动化测试.为测试共享setUp和shutDown. ...

  10. μCUnit,微控制器的单元测试框架

    在MCU on Eclipse网站上看到Erich Styger在8月26日发布的博文,一篇关于微控制器单元测试的文章,有很高的参考价值,特将其翻译过来以备学习.原文网址:https://mcuone ...

最新文章

  1. php在线备忘录,一个会话备忘录小程序的实现方法
  2. 12.04 深圳站 | Serverless Developer Meetup 开放报名啦
  3. 第二届战神杯线上编程挑战赛月赛第一题:回文数
  4. library的英语怎么读音_如何提高英语听力
  5. Spring 框架之 AOP 原理深度剖析!|CSDN 博文精选
  6. java scanner类成员_Java Scanner类的使用示例
  7. VPP 助你创新更高效、更灵活的报文处理方案
  8. SQL数据库连接超时时间已到
  9. 苍穹影视双端千月app源码,全新后台
  10. JSP——在JSP中嵌入java代码
  11. fid fopen MATLAB
  12. C# 切割超级大图(.bmp)[1G以上超大图片分块加载代码]
  13. 计算机文件恢复快捷键,文件变成快捷方式怎么恢复
  14. linux下计算时间,linux 日期时间计算
  15. 「咕咕网校 - 基础省选」树上问题的进阶 by Drench
  16. X006---交叉表(Cross Tab)和转置(Transpose)
  17. 学会放松和享受当下极其重要
  18. Android开发学习——记单词APP安卓注册登录跳转
  19. 毕业论文关键字HTML5,毕业论文关键词的选择
  20. TPM1.2到TPM 2.0的变化

热门文章

  1. Android8.1系统Led的控制从底层到上层的实现
  2. linux驱动中使用定时器的设置
  3. Android系统中用C语言来编写服务程序并且开机自启动运行服务
  4. 闫墨杰415知识点总结
  5. memcached(十)动态扩容
  6. Effective C++ 学习笔记(24)
  7. 下载silverlight官网的全部视频教程
  8. Linux安装Python3详解
  9. C++ 从入门到入土(English Version)Section 7 : Classes, Objects and Pointers
  10. windows10快速搭建和部署docker、kubernetes开发环境