通常,在分工明确的测试团队中功能测试用例设计和测试用例脚本实现会分开实施,即测试用例设计人员要分析被测试需求内容,设计测试场景及编写测试用例,而自动化测试人员根据功能测试用例实现测试用例脚本代码。测试团队各类成员之间相互协作来完成不同测试任务,那么Agent之间是否也可以进行协作来完成测试任务呢?现在就给大家介绍一款使用简单、方便的多Agent协作工具crewAI。
9.7.1 crewAI简介
crewAI是一款开源的项目,正如其首页面的描述“crewAI:用于编排角色扮演、自主AI智能体的尖端框架。通过培养协作智能,crewAI使智能体能够无缝协作,处理复杂的任务。”,如图9-53所示。
图9-53 crewAI项目
crewAI 主要由Agent(智能体)、Task(任务)、Tool(工具)和Crew(团队)模块组成。crewAI的结构图,如图9-54所示。在编写代码时,Agent需要指定角色和描述背景故事、目标等信息;Task是Agent需要完成的任务,通常要填写描述(description)、期望输出(expected_output)和指定该任务由那个Agent去执行;Tool是Agent使用的工具,用于辅助完成任务,在后续例子中笔者创建了一个用于保存Python代码的工具;Crew则是Agent和Task相结合的容器,用于协调各Agent合作完成任务,默认情况下这些任务顺序执行。
图9-54 crewAI的结构图
9.7.2 多智能体协作构建测试脚本生成器
要实现一个基于DeepSeek和crewAI多智能体,构建“测试脚本生成器”非常简单。
首先,确保“deepseek-r1:14b”模型正常运行。
然后,安装crewAI库,请使用“pip install crewai[tools]”命令安装即可。
接下来,编写相关代码。“ReqtoScript.py”为利用crewAI多智能体实现构建“测试脚本生成器”的源代码。
“ReqtoScript.py”源代码
from crewai import Agent, Task, Crew, Process
from langchain_community.llms import Ollama
from tools.getCode import getCode
qllm = Ollama(model='deepseek-r1:14b')
analyst = Agent(
role="软件测试分析师",
goal="从需求文档中提取测试场景,并基于这些场景生成测试用例。",
backstory="从需求文档中提取关键信息,形成测试场景,并基于这些场景生成相应测试用例。",
llm=qllm,
temperature=0,
allow_delegation=False,
verbose=True
)
expert = Agent(
role="自动化测试专家",
goal="生成基于selenium和unittest测试框架的自动化测试脚本,确保脚本能够覆盖关键功能点,并且易于维护和扩展。",
backstory="从给定的输出内容中提取测试用例,并且仅输出基于Selenium和unittest测试框架的测试脚本。",
temperature=0,
allow_delegation=False,
llm=qllm,
verbose=True
)
coder = Agent(
role="代码专家",
goal="仅提取Python代码,不要任何文字描述。",
backstory="从给定的输出内容中提取Python脚本代码,并且存入指定的Python文件。",
temperature=0,
allow_delegation=False,
llm=qllm,
verbose=True,
tools=[getCode]
)
testcases = Task(
description=(
"""
输出内容包括两部分,测试场景和测试用例,分别显示,可参考示例。测试用例需有详细的测试步骤。
1. 认真阅读和理解需求文档{requirement}
2. 提取关键功能点和业务规则,覆盖需求文档内容。
3. 设计测试场景,包括正常流程和异常流程。
4. 必须按照示例格式为每个测试场景编写详细的测试用例。
输出测试场景及测试用例。
Examples:
需求:用户注册功能
测试场景示例:
- 正常注册流程:用户输入有效邮箱和密码,单击注册。
- 邮箱格式错误:用户输入格式错误的邮箱地址。
- 密码强度不足:用户输入的密码不符合安全要求。
测试用例示例:
场景:正常注册流程
前提条件:用户访问注册页面。
测试步骤:
1. 输入有效的邮箱地址。
2. 输入符合要求的密码。
3. 单击注册按钮。
预期结果:系统显示注册成功消息
"""
),
expected_output="输出测试场景和测试用例,分别显示。",
agent=analyst,
)
scripts = Task(
description=(
"""
输出基于Selenium和unittest测试框架的测试脚本代码。
1. 根据测试场景及生成的测试用例。
2. 编写符合基于Selenium和unittest测试框架规范的测试脚本,应给出具体的操作步骤,不做
PageObject模式设计。
3. 需要保留具体的操作步骤代码,必须使用find_element(By.ID,*)方法,使用Firefox驱动。
4. 用例需要分离,放置到不同函数。
5. 必须保证代码的正确性和完整性,不存在语法错误。
"""
),
expected_output="输出基于Selenium和unittest测试框架的测试脚本代码。",
agent=expert
)
writefile = Task(
description=(
"仅提取Python代码,使用提供的外部工具Tool将提取的Python代码保存到testcases.py文件。"
),
expected_output="仅提取Python代码,使用提供的外部工具Tool将提取的Python代码保存到testcases.py文件。" ,
agent=coder
)
crew = Crew(
agents=[analyst, expert, coder],
tasks=[testcases, scripts, writefile],
verbose=True,
process=Process.sequential
)
inputs = {"requirement": """
一个在线购物系统的登录功能。
需求描述:
用户可以使用邮箱和密码登录;
邮箱格式必须有效;
密码长度为8-20个字符;
登录失败超过3次,账户将被锁定1小时。
"""}
result = crew.kickoff(inputs=inputs)
笔者简单介绍一下代码。我创建了3个Agent,因为从需求到测试脚本任务相对复杂,单个Agent很难实现,需要多个Agent相互协作完成。“软件测试分析师”Agent主要用于分析需求,设计测试场景和测试用例;“自动化测试专家”Agent用于根据测试用例实现基于Python语言Selenium、unittest测试框架脚本的编写。结合应用LLM在输出测试脚本时有可能会产生解释性内容的问题,又创建了“代码专家”Agent,它仅提取Python脚本代码并将其保存到指定的文件中,因此创建了“getCode”自定义的工具。如果使用自定义的工具,必须在定义Agent时指定tools参数列表。“getCode”工具的具体实现代码参见“getCode.py”源代码。
“getCode.py”源代码
from crewai_tools import tool
@tool("getCode")
def getCode(text: str) -> str:
"""
使用这个工具来保存任务提取的Python代码到testcases.py文件。
:param text: 要保存的Python代码
:return: 保存状态消息
"""
try:
with open('testcases.py', 'w', encoding='utf-8') as file:
file.write(text)
return f"代码已成功写入到testcases.py文件中。"
except Exception as e:
return f"写入文件时发生错误: {e}"
t 最后,运行“ReqtoScript.py”输出内容如下。
运行“ReqtoScript.py”输出内容
[2025-02-22 09:10:36][DEBUG]: == Working Agent: 软件测试分析师
[2025-02-22 09:10:36][INFO]: == Starting Task:
输出内容包括两部分,测试场景和测试用例,分别显示,可参考示例。测试用例需有详细的测试步骤。
1. 认真阅读和理解需求文档
一个在线购物系统的登录功能。
需求描述:
用户可以使用邮箱和密码登录;
邮箱格式必须有效;
密码长度为8-20个字符;
登录失败超过3次,账户将被锁定1小时。
2. 提取关键功能点和业务规则,覆盖需求文档内容。
3. 设计测试场景,包括正常流程和异常流程。
4. 必须按照示例格式为每个测试场景编写详细的测试用例。
输出测试场景及测试用例。
Examples:
需求:用户注册功能
测试场景示例:
- 正常注册流程:用户输入有效邮箱和密码,单击注册。
- 邮箱格式错误:用户输入格式错误的邮箱地址。
- 密码强度不足:用户输入的密码不符合安全要求。
测试用例示例:
场景:正常注册流程
前提条件:用户访问注册页面。
测试步骤:
1. 输入有效的邮箱地址。
2. 输入符合要求的密码。
3. 单击注册按钮。
预期结果:系统显示注册成功消息
> Entering new CrewAgentExecutor chain...
<think>
好的,我现在需要帮助用户完成一个软件测试分析师的任务。用户的要求是从需求文档中提取关键信息,形成测试场景,并基于这些场景生成相应的测试用例。我必须严格按照指定的格式输出结果。
首先,我要仔细阅读并理解需求文档。需求文档描述的是一个在线购物系统的登录功能,具体包括以下几点:
1. 用户可以使用邮箱和密码进行登录。
2. 邮箱格式必须有效。
3. 密码长度必须在8到20个字符之间。
4. 登录失败超过3次后,账户会被锁定1小时。
接下来,我需要提取关键的功能点和业务规则。这些包括:
- 用户登录的正常流程:使用正确的邮箱和密码成功登录。
- 邮箱格式的有效性验证。
- 密码长度的有效性检查。
- 登录失败次数限制以及账户被锁定的情况。
然后,设计测试场景。我需要考虑正常的登录流程和各种异常情况,确保覆盖所有可能的用户操作。具体来说:
1. 正常登录流程:用户提供正确的邮箱和密码。
2. 邮箱格式错误:使用无效的邮箱地址进行登录。
3. 密码长度不足或过长:输入少于8个字符或超过20个字符的密码。
4. 多次失败登录后账户被锁定:尝试登录5次,超过三次限制。
接下来是为每个测试场景编写详细的测试用例。测试用例需要包括前提条件、测试步骤和预期结果。确保每一步骤都清晰明了,并且能够准确反映用户操作及系统响应。
最后,我必须按照示例格式输出结果,确保结构正确,不遗漏任何部分。
</think>
Thought: I now can give a great answer
Final Answer:
测试场景
1. 正常登录流程:用户输入有效邮箱和密码,单击登录。
2. 邮箱格式错误:用户输入格式错误的邮箱地址。
3. 密码长度不足或过长:用户输入少于8个字符或超过20个字符的密码。
4. 多次失败登录后账户被锁定:用户尝试登录5次,触发账户锁定机制。
测试用例
场景 1:正常登录流程
前提条件:用户访问登录页面。
测试步骤:
1. 输入有效的邮箱地址(例如:test@example.com)。
2. 输入符合要求的密码(长度为8-20个字符,例如:TestPass123)。
3. 单击登录按钮。
预期结果:系统显示欢迎消息,用户成功登录。
场景 2:邮箱格式错误
前提条件:用户访问登录页面。
测试步骤:
1. 输入无效的邮箱地址(例如:test@invalid)。
2. 输入符合要求的密码(长度为8-20个字符,例如:TestPass123)。
3. 单击登录按钮。
预期结果:系统显示错误提示,指出邮箱格式无效。
场景 3:密码长度不足或过长
前提条件:用户访问登录页面。
测试步骤:
1. 输入有效的邮箱地址(例如:test@example.com)。
2. 输入少于8个字符的密码(例如:Pass1)或超过20个字符的密码(例如:TestPass12345678901234567890)。
3. 单击登录按钮。
预期结果:系统显示错误提示,指出密码长度不符合要求。
场景 4:多次失败登录后账户被锁定
前提条件:用户访问登录页面。
测试步骤:
1. 输入无效的邮箱地址(例如:test@invalid.com)。
2. 单击登录按钮,重复此操作5次(每次尝试均使用不同的无效密码或邮箱)。
预期结果:系统显示账户被锁定提示,并在1小时内拒绝所有登录请求。
> Finished chain.
[2025-02-22 09:19:45][DEBUG]: == [软件测试分析师] Task output: 测试场景
1. 正常登录流程:用户输入有效邮箱和密码,单击登录。
2. 邮箱格式错误:用户输入格式错误的邮箱地址。
3. 密码长度不足或过长:用户输入少于8个字符或超过20个字符的密码。
4. 多次失败登录后账户被锁定:用户尝试登录5次,触发账户锁定机制。
测试用例
场景 1:正常登录流程
前提条件:用户访问登录页面。
测试步骤:
1. 输入有效的邮箱地址(例如:test@example.com)。
2. 输入符合要求的密码(长度为8-20个字符,例如:TestPass123)。
3. 单击登录按钮。
预期结果:系统显示欢迎消息,用户成功登录。
场景 2:邮箱格式错误
前提条件:用户访问登录页面。
测试步骤:
1. 输入无效的邮箱地址(例如:test@invalid)。
2. 输入符合要求的密码(长度为8-20个字符,例如:TestPass123)。
3. 单击登录按钮。
预期结果:系统显示错误提示,指出邮箱格式无效。
场景 3:密码长度不足或过长
前提条件:用户访问登录页面。
测试步骤:
1. 输入有效的邮箱地址(例如:test@example.com)。
2. 输入少于8个字符的密码(例如:Pass1)或超过20个字符的密码(例如:TestPass12345678901234567890)。
3. 单击登录按钮。
预期结果:系统显示错误提示,指出密码长度不符合要求。
场景 4:多次失败登录后账户被锁定
前提条件:用户访问登录页面。
测试步骤:
1. 输入无效的邮箱地址(例如:test@invalid.com)。
2. 单击登录按钮,重复此操作5次(每次尝试均使用不同的无效密码或邮箱)。
预期结果:系统显示账户被锁定提示,并在1小时内拒绝所有登录请求。
[2025-02-22 09:19:45][DEBUG]: == Working Agent: 自动化测试专家
[2025-02-22 09:19:45][INFO]: == Starting Task: 输出基于selenium和unittest测试框架的测试脚本代码。
1. 根据测试场景及生成的测试用例。
2. 编写符合基于selenium和unittest测试框架规范的测试脚本,应给出具体的操作步骤,不做
PageObject模式设计。
3. 需要保留具体的操作步骤代码,必须使用find_element(By.ID,*)方法,使用Firefox驱动。
4. 用例需要分离,放置到不同函数。
5. 必须保证代码的正确性和完整性,不存在语法错误。
> Entering new CrewAgentExecutor chain...
<think>
好的,我现在需要根据给定的测试场景和用例,编写基于selenium和unittest框架的自动化测试脚本。首先,我应该理解每个测试场景的需求,并将其转化为具体的测试用例。
对于场景1:正常登录流程。用户输入有效邮箱和密码后应成功登录。这一步相对简单,直接按照步骤操作即可。需要注意的是,使用find_element方法时要确保元素定位正确,比如用户名框、密码框和登录按钮的ID是否正确。
接下来是场景2:邮箱格式错误的情况。这里需要模拟输入一个无效的邮箱地址,然后检查系统是否显示错误提示。同样,我需要确认错误提示的元素位置,以便在代码中进行断言。
场景3涉及密码长度的问题。测试用例要求分别测试短于8字符和长于20字符的密码。这意味着我需要编写两个不同的测试函数,一个用于密码过短的情况,另一个用于过长的情况。这可能涉及到在同一个测试方法内处理两种情况,或者分开为两个独立的方法。
场景4是关于多次失败登录导致账户锁定。这个测试较为复杂,因为需要模拟五次错误的登录尝试,并检查是否触发锁定机制。这里需要注意的是,每次登录之间是否有延迟,以避免被系统视为同一请求。另外,锁定后可能需要等待一段时间才能继续后续操作,因此可能需要用到time模块。
在编写代码时,我会使用unittest框架的TestCase类,并为每个测试场景创建独立的方法。每个方法中会初始化浏览器、执行步骤,然后断言结果是否符合预期。此外,所有的元素定位都将使用By.ID,确保稳定性。
关于驱动部分,明确要求是使用Firefox驱动,所以我需要在代码中正确设置Firefoedriver的路径,并启动相应的服务。另外,测试完成后记得调用driver.quit()以释放资源。
最后,我会检查每个测试方法是否独立且易于维护,同时确保没有语法错误和逻辑漏洞。通过这种方式,整个测试脚本将能够有效地覆盖所有关键功能点,并保证其可维护性和扩展性。
</think>
Thought: I now can give a great answer
Final Answer:
```python
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
class TestLogin(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get("http://example.com/login")
def test_normal_login(self):
......
从输出日志,“软件测试分析师”、“自动化测试专家”、“代码专家”这3个Agent按照代码任务的执行顺序,先后执行了生成测试用例(testcases)、生成自动化测试脚本(scripts)和提取Python脚本代码写入到“testcases.py”文件(writefile)这3个任务。在每次执行任务时DeepSeek都会认真思考该如何执行任务,给出合理的执行计划,3个Agent各司其职,最终输出“testcases.py”文件。从文件中的测试用例来看,输出质量较好,基本覆盖了需求内容,包括:正常登录流程、邮箱格式错误、密码强度过短或过长和账户被锁定情况。将3个Agent的“allow_delegation”参数均设置为False时,它们将各自执行自己负责的任务。“allow_delegation”参数为True时,代表允许该Agent将任务委托给其他Agent,它们之间相互协作来完成最终任务。由于各Agent间要进行多轮会话,通常耗费更长的时间,在任务完成效果不佳的情况下建议尝试将“allow_delegation”参数设为True。
项目相关结构,如图9-55所示。
图9-55 项目相关结构
“测试脚本生成器”生成的“testcases.py”文件内容如下。
“testcases.py”文件内容
import unittest
from selenium import webdriver
from selenium.webdriver.common.by import By
import time
class TestLogin(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get('http://example.com/login')
def test_normal_login(self):
# 测试步骤1:输入有效邮箱和密码
self.driver.find_element(By.ID, 'email').send_keys('test@example.com')
self.driver.find_element(By.ID, 'password').send_keys('TestPass123')
time.sleep(1)
# 单击登录按钮
self.driver.find_element(By.ID, 'login_button').click()
time.sleep(2)
# 验证欢迎消息存在
assert '欢迎回来,test@example.com!' in self.driver.page_source
def test_invalid_email_format(self):
# 测试步骤:输入无效邮箱
self.driver.find_element(By.ID, 'email').send_keys('test@invalid')
self.driver.find_element(By.ID, 'password').send_keys('TestPass123')
time.sleep(1)
# 单击登录按钮
self.driver.find_element(By.ID, 'login_button').click()
time.sleep(2)
# 验证错误提示存在
error_msg = self.driver.find_element(By.ID, 'error_email').text
assert '邮箱格式无效' in error_msg
def test_password_length(self):
# 测试密码过短的情况
self.driver.find_element(By.ID, 'email').send_keys('test@example.com')
self.driver.find_element(By.ID, 'password').send_keys('Pass1')
time.sleep(1)
self.driver.find_element(By.ID, 'login_button').click()
time.sleep(2)
error_msg = self.driver.find_element(By.ID, 'error_password').text
assert '密码长度不符合要求' in error_msg
# 测试密码过长的情况
long_password = 'TestPass12345678901234567890'
self.driver.find_element(By.ID, 'email').clear()
self.driver.find_element(By.ID, 'password').clear()
self.driver.find_element(By.ID, 'email').send_keys('test@example.com')
self.driver.find_element(By.ID, 'password').send_keys(long_password)
time.sleep(1)
self.driver.find_element(By.ID, 'login_button').click()
time.sleep(2)
error_msg = self.driver.find_element(By.ID, 'error_password').text
assert '密码长度不符合要求' in error_msg
def test_locked_account(self):
# 测试账户被锁定的情况
for _ in range(3):
self.driver.find_element(By.ID, 'email').send_keys('locked@example.com')
self.driver.find_element(By.ID, 'password').send_keys('IncorrectPass123!')
self.driver.find_element(By.ID, 'login_button').click()
time.sleep(2)
# 验证锁定提示
error_msg = self.driver.find_element(By.ID, 'error_account').text
assert '账户已被锁定' in error_msg
# 等待1小时(这里简化处理,仅测试触发机制)
time.sleep(3600) # 实际应处理更复杂的逻辑
def tearDown(self):
self.driver.quit()
if __name__ == '__main__':
unittest.main()
“testcases.py”文件不存在语法错误且覆盖了测试需求内容。但是,由于LLM并不知道真实被测试系统具体页面操作元素的相关属性(如ID、XPATH等)信息,所以需要结合实际情况来修改脚本。当然,如果有相应的知识库则可以借助RAG技术补充这部分信息或者针对性对模型进行微调。经过处理后,“testcases.py”文件很有可能一步到位,直接使用。