ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

使用子进程执行关键字的Robot Framework库。可以使用多个子进程同时执行相同或者不同的关键字,从而使得robot具有并发执行关键字的能力。

2026/9/25 8:04:23 拓冰建站 浏览量
使用子进程执行关键字的Robot Framework库。可以使用多个子进程同时执行相同或者不同的关键字,从而使得robot具有并发执行关键字的能力。

使用子进程执行关键字的Robot Framework库。可以使用多个子进程同时执行相同或者不同的关键字,从而使得robot具有并发执行关键字的能力。

    使用multiprocessing.Process创建子进程。使用multiprocessing.Manager创建多进程安全的字典存储子进程的返回值。

    为了减少学习成本,这个库的API和robot标准库Process的API类似。Robot标准库Process用于在子进程中执行shell命令。

    与robot标准库Process的差异:

    1、创建进程时要指定process_name,process_name用于存储和获取process的返回值,所以不能重复。

    2、没有active process的概念。操作已有的process时要指名start process返回的Process实例。

    The library has following main usages:

    Running processes in system and waiting for their completion using `Run Process` keyword.

    Starting processes on background using `Start Process`. 

    Waiting started process to complete using `Wait For Process` or stopping them with `Terminate Process`.

    局限:

    1、子进程中执行的关键字,没有详细的log记录。因为多个进程并发读写xml文件会导致文件内容混乱。所以建议子进程执行的关键字已经经过充分的测试

    2、建议子进程执行的关键字中不要写断言。而是将相关信息[RETURN]出来,在主进程中进行断言。因为案例是在主进程中执行的,子进程关键字的断言的结果无法直接影响案例是否通过。

    注:如果子进程中断言失败了,`Get Process Exit Code`获取到的子进程的exit code将不是0。这说明可能是子进程中的断言失败了,但是不能得到肯定的结论。因为其它异常也会导致子进程的exit code不是0。

代码如下:

# author: Liu Yifan
from multiprocessing import Process,Managerfrom robot.libraries.BuiltIn import BuiltIn
from robot.output.logger import LOGGER
from robot.api import loggerclass MultiProcess(object):'''使用子进程执行关键字的robot库。可以使用多个子进程同时执行相同或者不同的关键字,从而使得robot具有关键字并发执行的能力。使用multiprocessing.Process创建子进程。使用multiprocessing.Manager创建多进程安全的字典存储子进程的返回值。为了减少学习成本,这个库的API和标准库Process的API类似。Robot标准库Process用于在子进程中执行shell命令。与Process的差异:1、创建进程时要指定process_name,process_name用于存储和获取process的返回值,所以不能重复。2、没有active process的概念。操作已有的process时要指名start process返回的Process实例。The library has following main usages:Running processes in system and waiting for their completion using `Run Process` keyword.\nStarting processes on background using `Start Process`. Waiting started process to complete using `Wait For Process` or stopping them with `Terminate Process`.局限:\n1、子进程中执行的关键字,没有详细的log记录。因为多个进程并发读写xml文件会导致文件内容混乱。所以建议子进程执行的关键字已经经过充分的测试\n2、建议子进程执行的关键字中不要写断言。而是将相关信息[RETURN]出来,在主进程中进行断言。因为案例是在主进程中执行的,子进程关键字的断言的结果无法直接影响案例是否通过。注:如果子进程中断言失败了,`Get Process Exit Code`获取到的子进程的exit code将不是0。这说明可能是子进程中的断言失败了,但是不能得到肯定的结论。因为其它异常也会导致子进程的exit code不是0。\n'''ROBOT_LIBRARY_SCOPE = 'GLOBAL'def __init__(self):# 存储process的返回值。key是process的name,value是其返回值。不用PID做key,是因为已经结束的process的PID可能被新的process使用,不能保证唯一。self.results = Manager().dict()# 封装_mutiprocess_run_keyword是为了# 1、子进程不写xml文件。多进程并发写xml文件,robot会出错,无法生成log日志和report。# 2、将关键字的返回值存储在字典self.results中。process_name用于结果存储。keyword运行本身不需要process_namedef _multiprocess_run_keyword(self, *args):# 多进程并发操作,robot会出错,无法生成log日志和report。# [ ERROR ] Reading XML source '/output.xml' failed: ParseError: mismatched tag: line 4794, column 72LOGGER.unregister_xml_logger()process_name = args[0]keyword = args[1]true_args = args[2:]result = BuiltIn().run_keyword(keyword, *true_args)self.results[process_name]=resultdef run_process(self, keyword, process_name, *args, timeout=None, terminate_on_timeout=True):"""Runs a process and waits for it to complete. 等待进程结束,返回子进程的返回值。第一个参数keyword是要执行的关键字。第二个参数process_name是进程的名字,代码中用于存储和获取process的返回值,名字可以是任意值,但是要唯一。如果process名字重复了,会抛出AssertionError(f"Error: process_name {process_name} is duplicate!"),test会fail。从第三个位置参数开始是关键字的参数。最后还有两个名字参数timeout和terminate_on_timeout。它们的的含义见`Wait For Process` keyword。但是考虑到`Run Process`同步调用的本质,这里terminate_on_timeout的缺省值是True,即如果timeout了,则结束进程。"""process = self.start_process(keyword, process_name, *args)return self.wait_for_process(process, timeout=timeout, terminate_on_timeout=terminate_on_timeout)def start_process(self, keyword, process_name, *args):"""Starts a new process on background. 返回Process实例。第一个参数keyword是要执行的关键字。第二个参数process_name是进程的名字,代码中用于存储和获取process的返回值,名字可以是任意值,但是要唯一。如果process名字重复了,会抛出AssertionError(f"Error: process_name {process_name} is duplicate!"),test会fail。从第三个位置参数开始是关键字的参数。"""if process_name in self.results:raise AssertionError(f"Error: process_name {process_name} is duplicate!")self.results[process_name]=Noneprocess = Process(target=self._multiprocess_run_keyword, name=process_name, args=(process_name, keyword, *args))process.start()return processdef is_process_running(self, process):"""Checks is the process running or not.Returns ``True`` if the process is still running and ``False`` otherwise.可以用来轮询检查process是否已经完成。"""return process.is_alive()def process_should_be_running(self, process, error_message='Process is not running.'):"""Verifies that the process is running.Fails if the process has stopped."""if not self.is_process_running(process):raise AssertionError(error_message)def process_should_be_stopped(self, process, error_message='Process is running.'):"""Verifies that the process is not running.Fails if the process is still running."""if self.is_process_running(process):raise AssertionError(error_message)def get_process_result(self, process):"""获取进程的返回值。如果进程没有返回值、或者进程还没有结束、或者进程被`Terminate Process`提前结束了,这个关键字的返回值都是None。"""return self.results.get(process.name)def get_process_id(self, process):"""获取操作系统的进程PID。"""return process.piddef terminate_process(self, process):"""提前结束进程。"""process.terminate()def wait_for_process(self, process, timeout=None, terminate_on_timeout=False):"""Waits for the process to complete or to reach the given timeout. Return the return value of process.如果执行该关键字时进程已经结束,则直接返回进程的返回值。如果进程没有返回值、或者进程还没有结束、或者进程被`Terminate Process`提前结束了,这个关键字的返回值都是None。The process to wait for must have been started earlier with `Start Process`.如果不指定timeout,则会一直等待到进程结束。如果指定了timeout,则最多等待到timeout指定的时间。timeout的单位是秒。如果timeout了进程还没有结束,会打印日志 "Warnning: process {process.name} has not finished after wait for process!",用以提示用户。缺省timeout时,并不terminate进程。用户还可以选择再次调用`Wait For Process`。如果terminate_on_timeout设置为True,则timeout时terminate进程。并且会打印日志"Process {process.name} has been terminated because parameter terminate_on_timeout is True.",用以提示用户。通常正常结束的子进程exit code是0。如果exit code的值不是0,则说明子进程可能出现了异常。并且会打印日志 "Process {process.name} may has an exception because it's exit code is {exit_code}.",用以提示用户。比如说等待通过ssh会话完成命令执行的时间不够。"""wait_timeout = float(timeout) if timeout is not None else Noneprocess.join(wait_timeout)# 如果get_process_exit_code返回值是None,则说明子进程还没有结束exit_code = self.get_process_exit_code(process)logger.info(f"exit code of process {process.name}: {exit_code}")if exit_code is None:logger.info(f"Process {process.name} has not finished after wait {timeout} seconds!")if terminate_on_timeout:self.terminate_process(process)logger.info(f"Process {process.name} has been terminated because parameter terminate_on_timeout is True.")elif exit_code != 0:logger.info(f"Process {process.name} may has an exception because it's exit code is {exit_code}.")return self.get_process_result(process)def get_process_exit_code(self, process):"""获取进程的exit code。通常正常结束的子进程exit code是0。如果exit code的值不是0,则说明子进程可能出现了异常。如果子进程还没有结束,返回值为None。"""return process.exitcode