44

自动化用例开发过程中的常见技巧:如何让用例支持多环境?

 4 years ago
source link: https://mp.weixin.qq.com/s?__biz=MzA4NTYwOTE3MQ%3D%3D&%3Bmid=2452533032&%3Bidx=1&%3Bsn=cea4d6695e08505ef1d7409e95aac1c9&%3Bchksm=880f5094bf78d98243c458256a9ab50ff87f5c3f84dbed245278ea618f725c16f41faa524f2d&%3Btoken=95782995&%3Blang=zh_CN
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

今天起我开了《自动化用例开发过程中的常见技巧》系列文章,来务实地讲下在开发自动化用例时会遇到的一些 开发技巧 ,我本来想把范围框定在 设计模式 上,但严格的讲有些 技巧 并不算设计模式,而且这样来可谈的内容也被限制了,所以就换成了 技巧 俩字。

本系列的文章不会干讲设计模式、高级语法特性,如果是这样子,你完全可以自己去看一些高级编程的书籍。我会每次抛出一个在开发用例中会出现的问题,看看如何结合这些开发技巧来解决。另外请注意: 解决一个问题的方法不会只有一个,不要让自己的思维被我限定住了

这系列文章适合以下读者:

  • 测试框架、平台开发者

  • 自动化测试用例编写者

本系列文章中主要使用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,分别访问 nameno_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!@#!"})

曾经也有人告诉我怎么解决:这几个环境用同一个账号、密码呗

VFn2UvR.jpg!web

来认真分析下,要实现测试用例在多环境下执行,要解决哪些问题:

  • 不同环境的服务入口地址不同,一般还会有http/https的差别

  • 不同环境需要使用不同的测试数据

  • 一些中间件,比如数据库、消息队列、缓存服务的访问地址、账号、配置有差别

  • 不同环境的第三方回调地址有差别

  • 不同环境的配置需要整体切换,不要出现在开发环境里用了生产环境的数据的问题

以上都是些常见的问题,甚至有些业务功能在实现上都还存在差异,不过这种就不在我们讨论的范围内了。

如何解题

要在用例层对测试环境无感,需要把环境所用的数据抽象出来,如下图:

7jQBZ3F.png!web

其实这是一个典型的桥接模式(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 就可以实现:

VfQfq2u.png!web

写在最后

这是本系列的第一篇文章,因为是写给测试人员看的,我不确定文章内容是否合适,有想法的童鞋可以回复告知我下感受,我好在今后的文章里调整。

另外,这系列的文章中的代码样例我会以jupyter notebook的形式push到github repo: https://github.com/jacexh/automation-testing-patterns ,感兴趣的可以点下star

EjE3E3R.png!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK