在自动化测试中测试数据的管理是绕不开的一个话题,不过我在前面加了一个修饰词:静态,用于对测试数据做一个分类。我所定义的静态测试数据有几个特征:

  • 一般预先保存在测试框架/平台内,无需在测试用例执行中从数据源动态获取
  • 测试数据在运行前、运行中、运行后状态不变更,是不可变数据

与之对应的则是动态测试数据,是在测试过程中临时生成的或是动态获取的。

静态测试数据的圈定比较简单,一般就是去数据源看下有没有业务完整度比较高的数据,把这样的数据找出来,然后跟开发、测试约定好,这种数据是专门跑自动化用例的,平时不要去修改它。看上去是个小问题,

如果你只维护了几十个、上百个测试用例,你可能察觉不到静态测试数据的管理会有什么样的问题,而如果你维护了上千、上万用例,我相信你应该多多少少有些痛点。

我计划用两篇或者三篇文章完整的阐述下在测试数据管理存在的问题以及我个人的一些实践办法。本篇是第一篇,目的是对当前普遍做法的吐槽以及我个人观念的植入。

怪像:为什么都在用Excel管理数据?

在我经历过的候选人有自动化测试能力的面试中,我听到的绝大部分用例、框架在管理测试数据时使用了Excel,形如:

mobile

password

deviceId

15015015015

123456

554c20e

15015015015

234567

554c20f

我一直不太明白为什么,是不是有双无形的大手在控制在从业人员都使用这种Excel表格管理测试数据?我估摸着有几个原因:

  • 早期的QTP的datatable也是张表格,从业人员把这种使用惯性传递到了现代社会
  • 微软买通了测试人员,就等着贵司上市前发律师函了
  • 人云亦云,缺乏思考

就上面这份数据来看,你完全可以使用一个轻量级的文本编辑器以jsonyamltoml再不济用xml的格式存放,如:

# json格式
[
    {
        "mobile": "15015015015", 
        "password": "123456"
    }
]
# yaml格式
accounts:
- mobile: 15015015015
  password: 123456
# toml格式
[[accounts]]
mobile = "15015015015"
password = "123456"

以这种现代化的格式来存储静态数据怎么不比Excel方便呢,香不?

我这边也把目前常用格式的定义附上,帮助各位系统性去了解:

  • JSON: https://www.json.org/json-zh.html
  • YAML: https://yaml.org/spec/1.1/current.html
  • TOML: https://github.com/toml-lang/toml

真正的问题:语义的丢失

用excel存放数据真的比json、yaml等格式差吗?看过我Python接口测试实战6-序列化与反序列化 可能会提出疑问:用什么格式并不重要,只是对应的序列化、反序列化方式产生了差异而已。

是的,没错,用json/yaml/xml不重要,你完全可以这么去兼容所有格式:

import json
import yaml


class Account:

    def __init__(self, mobile, password):
        self.mobile = mobile
        self.password = password

    @classmethod
    def from_json(cls, data: bytes):
        d = json.loads(data)
        return cls(d['mobile'], d['password'])

    @classmethod
    def from_yaml(cls, data: bytes):
        d = yaml.safe_load(data)
        return cls(d['mobile'], d['password'])
    
    @classmethod
    def from_excel(cls, data: bytes):
        ...

对于程序而言,Excel实际是一个二维数组,你只能够以data[i][j]的方式去访问其中的值,相比使用account.mobile这种方式,这里最大的问题是缺失了语义信息

什么是语义?我们看下百度百科的解答:

语言所蕴含的意义就是语义(semantic)。简单的说,符号是语言的载体。符号本身没有任何意义,只有被赋予含义的符号才能够被使用,这时候语言就转化为了信息,而语言的含义就是语义。

语义可以简单地看作是数据所对应的现实世界中的事物所代表的概念的含义,以及这些含义之间的关系,是数据在某个领域上的解释和逻辑表示。

我再给出一段代码具象的讲下这里的差别:

# 无语义的测试数据
login(data[3][0], data[3][1])
login(data[4][0], data[4][1])

# 语义化的测试数据
login(normal_account.mobile, normal_account.password)
login(abnormal_account.mobile, abnormal_account.password)

很明显的,当使用第一种方法,你调用login接口时,根本不知道传入的两个参数是什么,以及两个同类型的数据有什么差别,而第二种写法则非常的清晰明了。

简言之,语义提供给使用者『望文生义』的能力,降低了理解的成本、负担。

业务实体(Entity)的抽取

仅仅赋予每个属性语义是不够的,我们把测试场景扩充下:现在有两个接口,一个是登录接口,使用手机号码、密码登录,另外个则是登录用户信息查询接口,校验用户各个属性值是否正确。

传统地使用Excel的做法里,对于第二个接口,又会把用户有关的属性字段,比如姓名、出生日期等放在另外几行,然后在用例里去使用。哪怕你使用json格式存储,你也很可能设计下面这个结构体:

class Account:
    mobile = None
    password = None

class AccountInfo:
    name = None
    birthday = None

我们假设这些属性跟上文提到的手机号码、密码实际在一张表中,而测试人员这种做法显然是错的,他们只是在面向单个用例/接口归纳整理数据,而没有基于整个业务来考虑。

好的做法应该充分考虑业务,借鉴开发对业务实体(Entity)的定义,把这些属性归集至一个对象之下,并且保证同一个对象在全局有且仅有一份:

class Account:
    mobile = None
    password = None
    name = None
    birthday = None

# 使用数据对象的过程
login(normal_account.mobile, normal_account.password)
ret = get_account_info()
assert ret.name == normal_account.name
assert ret.birthday = normal_account.birthday

结束语

这篇先简单介绍下现在常用的数据格式,以及灌输下测试数据也要『面向对象』的理念。

下一篇我将阐述下如何去表达多个对象实体的关联关系,以及相互之间如何引用方法。有时间的话会进一步讲下如何通过技术来落地这些能力。