问题背景
在编写代码的过程中,常常会遇到资源重入的问题,测试自动化也是一样。造成重入的场景很多,这是比较常见的几种。
第一种是自动化用例本身存在多进程或者线程,进程或者线程之间存在资源重入的问题。对于多进程或者多线程而言,静态代码上下文内容是一样的,代码中引用的资源或者变量大多数时候也是相同的。当静态代码进入运行时状态,就会涉及资源被多个进程和线程重入和消费的情况。
第二种是不同用例使用了同样的资源
资源数量是有限的,测试的场景和内容是多样的,日益增长的测试场景和有限的资源是矛盾的,每个测试场景和内容都绑定独立的资源基本是无法实现的。
第三种是自动化用例被多种不同的方式启动。这个场景跟第一点提到的多进程或者线程的场景类似,因为是同一份用例的反复调用,他们内部的代码、变量和资源完全一样。当用例以不同的方式被启动的时候,同一份资源就陷入了被重入的漩涡当中。
对于第一种情况,多进程或者多线程是并发编程的基础,通常可以使用资源的调度或者锁的机制来解决本文就不再赘述。
第二、三种情况比较类似,都属于用例之间对于资源的重入,本文会会使用分布式锁的方式进行解决,后文也会着重对它们进行介绍。
举例说明
比如对于域名操作类的自动化用例,常常会伴随域名创建、域名更新、域名删除等操作。自动化代码本身是静态的,当自动化代码被运行时就变成动态的,会对域名真正进行以上的操作。
同时对于同一份自动化代码,里面使用的域名资源在某些情况下又是一致的。
如果自动化在多处被同时调用,就会产生同一套代码的执行和用例资源的重入。
出错场景
用例A被定时任务启动,并在执行过程中对域名做操作
用例A同时被流水线调用,并在执行过程中也对同一个域名做操作。但是因为这个域名正在被上面定时任务调用,所以本次调用会失败。虽然行为是正常的,但是造成的结果是自动化脚本的误报。
具体的实现
获取锁整个环节中最重的地方,主要说明里面的获取锁的逻辑。
def acquire_lock(self, lock_name, occupy_time=10, wait_time=600, over_time=15): """ Get nx lock with expire time """ end_time = time.time() wait_time while time.time() < end_time: try: # 尝试索取锁 if self._redis_client.setnx(lock_name, time.time() occupy_time): self._redis_client.expire(lock_name, occupy_time) logger.info('{lock_name} get the lock'.format(lock_name=lock_name)) return True # 给锁加上过期时间 elif self._redis_client.ttl(lock_name) == -1: self._redis_client.expire(lock_name, occupy_time) except Exception as err: logger.info(str(err)) self.release_lock(lock_name) raise Exception("{lock_name} lock failed".format(lock_name=lock_name)) time.sleep(1) return False |
---|
问题解决
也同样用例A被同时启动来说明,这种方法的效果。
用例A被调用执行,需要抢到这个锁才能执行。并且执行完成再释放。
用例A再次被调用,但是因为分布式锁的存在,被挂起直到上一个用例A对相同资源的执行完成才能执行。
最终结果是对于用例A的两次同时调用都成功了。
这样就解决了前文提到的问题