【ZStack】16.自动化测试系统3——基于模型的测试

模型测试系统是zstack-woodpecker中的一个子项目。通过有限状态机和行为选择策略,它可以产生随机的API操作,一直运行下去,直到遇到一个缺陷或者预定义的退出条件。ZStack依赖模型测试去测试真实世界中难以遇到的边界用例,在测试覆盖度方面补充集成测试和系统测试。

概述

测试覆盖率是一个判断一个测试系统品质的重要指示器。常规测试方法论,例如单元测试,集成测试,系统测试,都是由人类逻辑思考构建的,难以覆盖软件中的边界场景。这个问题在IaaS软件中变得更加明显,因为管理不同的子系统会导致极为复杂的场景。
ZStack通过引入基于模型的测试来解决这个问题。它可以产生由随机API组合构成的场景,会持续运行知道遇到预定义的退出条件或者找到一个缺陷。作为机器驱动的测试,基于模型的测试可以克服人类逻辑思考的缺陷来执行一些,看起来反人类逻辑,但是API完全正确的测试,帮助发现难以被人类主导的测试发现的边界问题。

一个例子可以帮助理解这个思想。基于模型的测试系统通常在执行大约200个API后暴露一个bug,在调试后,我们找到最小重现这个问题的序列是:

  1. 创建一个VM
  2. 关闭这个VM
  3. 为这个VM的根云盘创建一个云盘快照
  4. 从这个VM的根云盘创建一个新的数据云盘快照
  5. 销毁这个VM
  6. 创建一个新的数据云盘,使用4中的模板
  7. 从6中的数据云盘创建一个新的云盘快照

这个操作序列显然是反逻辑的,我们相信没有测试者会写一个集成测试用例或者系统测试用例这么做。这就是机器思考闪光的地方,因为它没有人类的感情,会做人类感觉不合理的事情。在找到这个bug之后,我们生成了一个回归测试为以后保障这个问题。

基于模型的测试系统

基于模型的测试系统,因为由机器驱动,也被称为机器人测试。当这个系统运行时,它从一个模型(在下面几节中也被称为阶段)移动到另一个模型,通过执行被动作选择策略选出的动作(也被称为操作)。在每一个模型完成之后,检查器将会验证测试结果,测试退出条件。如果任何失败被发现,或者退出条件被满足,系统将会退出。否则,它将会移动到下一个模型,然后重复。

有限状态机

在基于模型的测试的理论中,有许许多多生成测试操作的方式。例如:有限状态机,自动推导,模型检验。我们选择使用有限状态机,因为它自然地适合IaaS软件,其中每一个资源都由状态驱动。例如,从用户角度看,VM的状态像这样:

在基于模型的测试系统中,每一个资源的每一个状态都预先定义在test_state.py中,看起来像:

vm_state_dict = {
            Any: 1 ,
            vm_header.RUNNING: 2,
            vm_header.STOPPED: 3,
            vm_header.DESTROYED: 4
            }

    vm_volume_state_dict = {
            Any: 10,
            vm_no_volume_att: 20,
            vm_volume_att_not_full: 30,
            vm_volume_att_full: 40
            }

    volume_state_dict = {
            Any: 100,
            free_volume: 200,
            no_free_volume:300
            }

    image_state_dict = {
            Any: 1000,
            no_new_template_image: 2000,
            new_template_image: 3000
            }

系统中所有资源的所有状态构成一个阶段(模型),系统可以从一个阶段转移到下一个阶段,通过执行维护在转换表中操作。一个阶段被定义成类似这样:

class TestStage(object):
    '''
        Test states definition and Test state transition matrix.
    '''
    def __init__(self):
        self.vm_current_state = 0
        self.vm_volume_current_state = 0
        self.volume_current_state = 0
        self.image_current_state = 0
        self.sg_current_state = 0
        self.vip_current_state = 0
        self.sp_current_state = 0
        self.snapshot_live_cap = 0
        self.volume_vm_current_state = 0
...

一个阶段可以被表示成一个整数,即由这个阶段的所有状态的和。通过这个整数,我们可以在转换表中查找到下一个后选的操作。转换表的一个例子如下:

#state transition table for vm_state, volume_state and image_state
    normal_action_transition_table = {
        Any: [ta.create_vm, ta.create_volume, ta.idel],
        2: [ta.stop_vm, ta.reboot_vm, ta.destroy_vm, ta.migrate_vm],
        3: [ta.start_vm, ta.destroy_vm, ta.create_image_from_volume, ta.create_data_vol_template_from_volume],
        4: [],
      211: [ta.delete_volume],
      222: [ta.attach_volume, ta.delete_volume],
      223: [ta.attach_volume, ta.delete_volume],
      224: [ta.delete_volume],
      232: [ta.attach_volume, ta.detach_volume, ta.delete_volume],
      233: [ta.attach_volume, ta.detach_volume, ta.delete_volume],
      234: [ta.delete_volume], 244: [ta.delete_volume], 321: [],
      332: [ta.detach_volume, ta.delete_volume],
      333: [ta.detach_volume, ta.delete_volume], 334: [],
      342: [ta.detach_volume, ta.delete_volume],
      343: [ta.detach_volume, ta.delete_volume], 344: [],
     3000: [ta.delete_image, ta.create_data_volume_from_image]
    }

通过这种方式,基于模型的测试系统可以保持运行,从一个阶段到另一个阶段,直到遇到预先定义的退出条件或者发现一些缺陷,它可以持续地跑很多天,数以万次地调用API。

动作选择策略

当在阶段间移动时,基于模型的测试系统需要决定下一个需要执行的操作是什么。决定制定器被称为动作选择策略,一个可扩展插件的引擎,不同的选择算法可以以不同的目的被实现。
当前系统有三种策略:

  • 随机调度器:最简单的策略,为当前的阶段,从候选动作中随机地选择下一个操作。作为一种很直接的算法,随机调度器可能会重复一项操作,而使得其他操作等待。为了缓解这个问题,我们为每一个操作都增加了一个权重,这样测试人员可以为他们想多测试的操作赋予更高的权重。
  • 公平调度器:一种对待每个操作都完全平等的策略,以这样一种方式补充随机调度器:每个操作都有平等的机会被执行,保证只要测试周期足够长,每个操作都会被测试到。
  • 路径覆盖调度器:通过历史数据决定下一步操作的策略。这个策略会记住已经被执行过的所有操作路径,然后尝试选择一个可以形成新的操作路径的操作。例如,给定候选操作A,B,C,D,如果前一个操作时B且路径BA,BB,BC都已经被执行,策略将会选取D作为下一个操作,这样路径BD将会被测试到。

如上面提及到的,动作选择策略是一个可扩展插件的引擎,每一个策略实际上由类ActionSelector派生来:
一个随机调度器的实现例子像这样:

class ActionSelector(object):
    def __init__(self, action_list, history_actions, priority_actions):
        self.history_actions = history_actions
        self.action_list = action_list
        self.priority_actions = priority_actions

    def select(self):
        '''
        New Action Selector need to implement own select() function.
        '''
        pass

    def get_action_list(self):
        return self.action_list

    def get_priority_actions(self):
        return self.priority_actions

    def get_history_actions(self):
        return self.history_actions

退出条件

在启动基于模型的测试系统之前,退出条件必须被设定好,否则系统将会保持运行,直到一个缺陷被发现,或者日志文件撑爆了测试机器的硬盘。退出条件可以是任何形式的,例如,在运行24小时后退出,在系统有100个EIP被创建后退出,在有2个停止的VM、8个运行中的VM时退出。一切都取决于测试者去定义条件,尽可能地增加发现缺陷的机会。

失败回放

调试一个被基于模型的测试系统发现的失败是很难而且令人沮丧的,大多数的失败都由大量的操作序列暴露,而且它们通常缺乏逻辑并有着大量的日志。我们通常手动重现失败,在痛苦地依照大约500,000行日志,使用zstack-cli调用API 200次后,我们最终意识到这个悲惨的任务不是人类可以做到的。然后我们发明了一个工具用于重现一个失败,通过回放动作日志(纯粹只记录了关于API的测试信息)。
一个动作日志像这样:

Robot Action: create_vm  
 Robot Action Result: create_vm; new VM: fc2c0221be72423ea303a522fd6570e9
 Robot Action: stop_vm; on VM: fc2c0221be72423ea303a522fd6570e9
 Robot Action: create_volume_snapshot; on Root Volume: fe839dcb305f471a852a1f5e21d4feda; on VM: fc2c0221be72423ea303a522fd6570e9
 Robot Action Result: create_volume_snapshot; new SP: 497ac6abaf984f5a825ae4fb2c585a88
 Robot Action: create_data_volume_template_from_volume; on Volume: fe839dcb305f471a852a1f5e21d4feda;  on VM: fc2c0221be72423ea303a522fd6570e9
 Robot Action Result: create_data_volume_template_from_volume; new DataVolume Image: fb23cdfce4b54072847a3cfe8ae45d35
 Robot Action: destroy_vm; on VM: fc2c0221be72423ea303a522fd6570e9
 Robot Action: create_data_volume_from_image; on Image: fb23cdfce4b54072847a3cfe8ae45d35
 Robot Action Result: create_data_volume_from_image; new Volume: 20dee895d68b428a88e5ec3d3ef634d8
 Robot Action: create_volume_snapshot; on Volume: 20dee895d68b428a88e5ec3d3ef634d8

测试人员可以通过调用回放工具重建失败的环境:

robot_replay.py -f path_to_action_log

总结

在这篇文章中,我们引入了基于模型的测试系统。由于善于暴露边界用例中的问题,基于模型的测试系统和集成测试系统、系统测试系统共同作为保卫ZStack质量的基础,使得我们可以以骄傲的自信发布产品。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容