自动化用例开发过程中的常见技巧-如何让用例支持多环境?
今天起我开了《自动化用例开发过程中的常见技巧》系列文章,来务实地讲下在开发自动化用例时会遇到的一些开发技巧,我本来想把范围框定在 设计模式上,但严格的讲有些技巧并不算设计模式,而且这样来可谈的内容也被限制了,所以就换成了技巧俩字。
本系列的文章不会干讲设计模式、高级语法特性,如果是这样子,你完全可以自己去看一些高级编程的书籍。我会每次抛出一个在开发用例中会出现的问题,看看如何结合这些开发技巧来解决。另外请注意:解决一个问题的方法不会只有一个,不要让自己的思维被我限定住了。
这系列文章适合以下读者:
测试框架、平台开发者 自动化测试用例编写者
本系列文章中主要使用Python来展示这类技巧,也可能会使用Golang,如果你对这两门语言不熟悉,不用担心,这些技巧也可以使用其他编程语言应用,没有编程语言的限制。
初识property装饰器
了解设计模式的人肯定知道『装饰模式』,不过这与Python下的装饰器并不完全是等价的,Python装饰器可以提供更加强大的能力。
这里先介绍下property
装饰器,看下面这个简单的例子:
class People:
def __init__(self, name: str):
self._name = name
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, nn: str)
self._name = nn
def no_property(self):
return self._name
我实例化该People,分别访问name
、no_property
两个属性,看下有什么区别:
>>> mike = Person("mike")
>>> mike.no_property
<bound method Person.no_property of <__main__.Person object at 0x10def4790>>
>>> mike.name
'mike'
no_property
的打印结果应该是更加容易理解的:它是Person的一个方法,当你访问该方法,不是调用时,自然是返回方法对象本身;而访问name
的时候结果就有点让人意外,它的打印结果等同于name()
,而非访问方法对象本身。
@property
装饰器是为了解决Python下没有真正意义的私有变量,导致对象的attribute会被外部调用、修改、删除等问题而产生的。在使用上,你可以理解为加上了@property
装饰器后,访问该属性obj.method
即为调用该方法obj.method()
语法讲解点到即止,想更加深入的理解property,可以参考官方文档:https://docs.python.org/3/library/functions.html#property
自动化用例的多环境执行需求
一个常规的研发项目,一般都存在开发、测试、生产环境:
开发环境用于开发用途,以及各模块联调,一般测试人员不介入 测试环境用于测试目的,测试人员的主要工作环境 生产环境,上线验证过程中测试人员会使用到
公司规模越大,对各类环境的定义会更加清晰、明确,环境种类也会进一步的细分,比如很多互联网公司都会有预发布环境、鄙司的『特性环境』等。
这样来就对自动化测试框架提出了一个需求:自动化用例应当支持在不同环境里执行,并且对用例逻辑层透明无感。
前面一句话好懂,后面一句话我考虑了下,还是得稍作解释:不能在用例里出现类似的代码
def test_login(self):
if env = "dev":
requests.post("http://biz.dev.company.net/login", data={"username":"admin", "password":"123456"})
elif env = "test"
requests.post("http://biz.test.company.net/login", data={"username":"admin", "password":"88888888"})
else:
requests.post("https://biz.company.com/login", data={"username":"superadmin", "password":"123sxcdas!@#!"})
曾经也有人告诉我怎么解决:这几个环境用同一个账号、密码呗
来认真分析下,要实现测试用例在多环境下执行,要解决哪些问题:
不同环境的服务入口地址不同,一般还会有http/https的差别 不同环境需要使用不同的测试数据 一些中间件,比如数据库、消息队列、缓存服务的访问地址、账号、配置有差别 不同环境的第三方回调地址有差别 不同环境的配置需要整体切换,不要出现在开发环境里用了生产环境的数据的问题
以上都是些常见的问题,甚至有些业务功能在实现上都还存在差异,不过这种就不在我们讨论的范围内了。
如何解题
要在用例层对测试环境无感,需要把环境所用的数据抽象出来,如下图:
其实这是一个典型的桥接模式(Bridge Pattern):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
拿上面出现的测试用例代码具象一点地讲:用例开发者需要抽象出服务地址
、账号
两个对象,用例逻辑层只允许使用这些抽象的对象,而不能直接访问具体的数据,框架层面提供对这些抽象对象的具体实现。这样来上面的测试代码就可以改成:
def test_login(self):
requests.post(entrypoint.biz+"/login", data={"username":data.account.username, "password":data.account.password})
我这边使用property装饰器
来简易的实现桥接模式,框架层的代码示例如下:
from enum import Enum
class Environment(Enum):
DEV = 0
BETA = 1
PROD= 2
env = Environment.BETA # 作为全局的环境变量
class EntryPoint:
_biz = {
Environment.DEV: "http://biz.dev.company.net",
Environment.BETA: "http://biz.beta.company.net",
Environment.PROD: "https://biz.company.com"
}
@property
def biz(self):
return self._biz[env]
样例代码中我省略了测试数据部分,测试数据是个很大的话题,我之后会完整地去讲述
如果需要切换环境去执行,只要更新全局变量env
就可以实现:
写在最后
这是本系列的第一篇文章,因为是写给测试人员看的,我不确定文章内容是否合适,有想法的童鞋可以回复告知我下感受,我好在今后的文章里调整。
另外,这系列的文章中的代码样例我会以jupyter notebook的形式push到github repo: https://github.com/jacexh/automation-testing-patterns ,感兴趣的可以点下star