😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。
📡主页地址:【Austin_zhai】
🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能,分享行业相关最新信息。
💎声明:博主日常工作较为繁忙,文章会不定期更新,各类行业或职场问题欢迎大家私信,有空必回。
阅读目录
- 1. 目的
- 2. 策略模式
- 3. 优势
- 4. 实操
- 4.1 拆解
- 4.2 举一反三
- 5. 后话
1. 目的
web自动化测试作为软件自动化测试领域中绕不过去的一个“香饽饽”,通常都会作为广大测试从业者的首选学习对象,相较于C/S架构的自动化来说,B/S有着其无法忽视的诸多优势,从行业发展趋、研发模式特点、测试工具支持,其整体的完整生态已经远远超过了C/S架构方面的测试价值。
经过前几次的大致讲解,我们已经将web自动化测试相关的基础知识介绍完了,相信大家如果一路学习过来的话,基本已经可以自己编写一个完整的UI自动化测试脚本与大致的框架了。
接下来博主将会与大家讨论一些我们在设计与编写过程中会用到的进阶知识与技巧,通过这些相关的知识和技巧,就可以让我们的脚本与框架变得更为灵活与高效,那么我们就来看看今天要介绍的内容吧。
2. 策略模式
看到这里,许多同学都会问,策略模式是什么?其实策略模式在自动化测试中是一种行为设计模式,说人话就是我们设计一套规则,来告诉程序什么时候或者什么条件下该执行什么样的操作。听到这里,估计又有同学会问,那这个不就是我们语法中的if-else吗?其实不单单是这样,具体的博主后面会详细介绍,这里大家只需要了解这个概念与我们测试计划中的测试策略大致相同,什么样的测试场景用什么样的测试数据,什么样的被测对象形态用什么样的测试用例,基本都是这样的概念了。
3. 优势
策略模式与if-else能实现的效果是一致的吗?其实不然,光这样想就太片面了。要说优势之前,我们先来介绍一下策略模式的实现步骤:
- 我们先需要定义一个抽象类,当然也可以是接口,里面一般含有执行动作或测试方法
- 在类中编写具体的测试实现方法,实现类的功能
- 创建策略对象,传递到对应的测试方法内
- 最后实例化并调用策略,执行对应的测试方法
相较于if-else来说,我们先将对应的规则设计好,再封装到独立的类中去,达到规则的实现与使用分离的效果。那么在执行的时候,测试同学可以灵活的使用各类规则来应对测试执行中会出现的多种测试场景。这是其一,其二,我们这样的设计理念,可以省去业务代码中的大量判断与分支,无论是从脚本框架的维护和扩展性上来说,策略模式有着绝对的优势。
4. 实操
为了让大家更好的理解策略模式的实现,这里我们举个例子,来实际看看其中的基础概念。
from abc import ABC, abstractmethod
class TestStrategy(ABC):
@abstractmethod
def execute_test(self, url):
pass
class ChromeTestStrategy(TestStrategy):
def execute_test(self, url):
# 执行Chrome浏览器测试
pass
class FirefoxTestStrategy(TestStrategy):
def execute_test(self, url):
# 执行Firefox浏览器测试
pass
class SafariTestStrategy(TestStrategy):
def execute_test(self, url):
# 执行Safari浏览器测试
pass
class EdgeTestStrategy(TestStrategy):
def execute_test(self, url):
# 执行Edge浏览器测试
pass
class TestRunner:
def run_test(self, url, test_strategy):
test_strategy.execute_test(url)
从上面的代码来看,我们定义了一个TestStrategy抽象类,它用来执行测试用例。接下来封装了ChromeTestStrategy、FirefoxTestStrategy、SafariTestStrategy和EdgeTestStrategy类,其作用就是根据不同的测试要求与场景来切换不同的浏览器。TestRunner类用来接收一个URL和一个测试策略对象,并在方法内部调用测试策略对象的execute_test()方法来执行测试。
相信大家都已经看懂了上面的一个小例子了,同时也对策略模式的有了一个大概的认知。那么在我们的日常工作中如何把策略模式融入其中呢,别急,接下来博主就来仔细的来进行拆解说明。
4.1 拆解
我们日常写UI自动化测试用例,一般都会是以下的这种方式:
import unittest
from selenium import webdriver
class TestSearch(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.driver.get("https://www.test.com/")
def test_search(self):
search_box = self.driver.find_element_by_name("input")
search_box.send_keys("hello")
search_box.submit()
self.assertIn("hello", self.driver.title)
def tearDown(self):
self.driver.quit()
这个就不具体介绍了,懂的自然懂,那么我们就把策略模式的理念加入到具体的脚本中来。
class SelectByBrowser(Strategy):
def __init__(self, browser):
self.browser = browser
def select(self, suite):
loader = unittest.TestLoader()
browser_suite = loader.loadTestsFromTestCase(TestSearch)
if self.browser == 'chrome':
return browser_suite
elif self.browser == 'firefox':
return unittest.TestSuite()
else:
raise ValueError("无效的浏览器类性")
上述的代码是一个名为SelectByBrowser的策略类,它的作用是接受一个浏览器参数(指定浏览器的类型),并返回一个包含测试用例的测试套件,该测试套件会根据所提供的浏览器参数选择测试用例。我们可以看到这里的选择方法中会进行对应的浏览器类型判断,如果是谷歌浏览器则会使用TestSearch的测试用例,而选择火狐浏览器则返回一个空的测试用例套件。
class SelectByEnvironment(Strategy):
def __init__(self, environment):
self.environment = environment
def select(self, suite):
loader = unittest.TestLoader()
environment_suite = loader.loadTestsFromTestCase(TestSearch)
if self.environment == 'dev':
return environment_suite
elif self.environment == 'prod':
return unittest.TestSuite()
else:
raise ValueError("无效的环境类型")
这个策略类会根据我们给出的环境参数(environment),返回对应的测试用例套件,以便我们可以针对不同的测试环境来灵活切换对应的测试用例,同样的选择开发环境的话返回TestSearch测试用例,选择生产环境的话依旧是返回空的用例套件。
class TestExecutor:
def __init__(self, suite):
self.suite = suite
def execute(self):
runner = unittest.TextTestRunner()
return runner.run(self.suite)
这里定义了个TestExecutor类,它的作用为接受一个测试套件并提供一个execute()方法来执行测试。通过这个类,我们可以根据SelectByBrowser和SelectByEnvironment的选择,让他们选择要执行的测试用例。
这样大致就将策略模式的相关接口与测试用例主备好了,之后我们可以直接开始调用其进行实例化和使用了,这里为了方便演示与讲解,博主直接使用主程序的写法来进行调用,就不跨脚本了。
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(TestSearch('test_search'))
executor = TestExecutor(suite)
# 在谷歌浏览器内执行的用例
browser_strategy = SelectByBrowser('chrome')
browser_suite = browser_strategy.select(suite)
browser_executor = TestExecutor(browser_suite)
browser_executor.execute()
# 在开发环境中执行的用例
env_strategy = SelectByEnvironment('dev')
env_suite = env_strategy.select(suite)
env_executor = TestExecutor(env_suite)
env_executor.execute()
在这个主程序中,先创建了一个包含测试用例的测试套件,然后将TestExecutor实例化,并将该测试套件传递给它。接下来,创建SelectByBrowser策略实例,并使用它选择Chrome浏览器的测试用例,而另一个TestExecutor实例来执行这些测试用例。接着创建一个SelectByEnvironment策略实例,并指定使用开发环境的测试用例,最后使用第三个TestExecutor实例来执行这些测试用例。
4.2 举一反三
有了上面的例子,我们在日常的脚本设计中就可以进行有效的练习与运用,其实该设计行为的主旨很明确,你根据预先的测试设计与场景条件进行规则的创建,然后封装类或接口,调用。就是这么的简单,真正要说有点难度的则是在设计时期受否真正的将各种场景与策略考虑完全的情况,这个需要各位同学反复的去进行实践和总结。
就拿博主自身来说,如果在测试项目的前期,我要对已经迭代后的自动化测试框架进行策略模式的优化,那么我就需要先将被测对象的相关业务场景和业务路线进行整理,这里需要注意的是尽量将异常场景进行排除,本就异常的结果会令自动化测试的结果发生不可预测的偏差。那么整理完上述的这些因素之后,将已知的业务场景与业务线路结果转化为具体的抽象类或接口,进行编写。
假设这里的业务场景和测试需求为:私人基金公司管理着多个基金,每个基金都有不同的投资策略和风险水平。我们需要编写一套自动化测试来验证每个基金的投资策略是否符合预期,并且能够自动化地选择和执行不同的测试用例套件,以确保每个基金的投资策略都能够得到充分的测试和验证。
class SelectByRiskLevel:
def __init__(self, risk_level):
self.risk_level = risk_level
def should_select(self, test_case):
if test_case.risk_level == self.risk_level:
return True
else:
return False
class FundInvestmentTestCase:
def __init__(self, fund_id, investment_strategy, risk_level):
self.fund_id = fund_id
self.investment_strategy = investment_strategy
self.risk_level = risk_level
class FundInvestmentTestExecutor:
def __init__(self, test_cases):
self.test_cases = test_cases
def select_tests(self, selector):
selected_tests = []
for test_case in self.test_cases:
if selector.should_select(test_case):
selected_tests.append(test_case)
return selected_tests
def run_tests(self, test_cases):
for test_case in test_cases:
# Run test case
pass
# 为不同的基金创建测试用例
test_cases = [
FundInvestmentTestCase(fund_id=1, investment_strategy='Aggressive Growth', risk_level='High'),
FundInvestmentTestCase(fund_id=2, investment_strategy='Balanced Portfolio', risk_level='Medium'),
FundInvestmentTestCase(fund_id=3, investment_strategy='Conservative Income', risk_level='Low'),
FundInvestmentTestCase(fund_id=4, investment_strategy='Value Investing', risk_level='High')
]
# 创建测试执行对象FundInvestmentTestExecutor
executor = FundInvestmentTestExecutor(test_cases)
# 选择测试用例来执行
risk_level_selector = SelectByRiskLevel('High')
selected_suite = executor.select_tests(risk_level_selector)
executor.run_tests(selected_suite)
在上面的代码中,先定义一个SelectByRiskLevel策略类,用于根据风险水平选择测试用例套件。而FundInvestmentTestCase类,则表示不同的基金投资策略,包括基金ID、投资策略和风险水平。FundInvestmentTestExecutor类,用于选择和执行测试用例。
因为测试业务与需求的要求,所以得创建一组不同的测试用例,表示不同的基金投资策略和风险水平。接着将FundInvestmentTestExecutor实例化,并将这些测试用例作为参数传递给它,其次使用SelectByRiskLevel策略类选择高风险水平的测试用例,而FundInvestmentTestExecutor的select_tests方法则会选择测试用例套件。最后由FundInvestmentTestExecutor的run_tests来执行具体的测试用例。
5. 后话
在我们的日常工作中,策略模式因其特有的灵活特性,经常会让测试同学忘记了其原有的一些限制,其实代码中的设计也好,实现也罢,还是需要遵循UI自动化测试的一些基本规则。如果被测对象功能原本就不太稳定或经常发生业务、设计变更,依然是不推荐使用该模式。另外对于各类策略类的封装,远远没有博主上面演示的那么简单,需要好好考虑业务上下衔接的连贯性和稳定性,良好的上下文封装会让测试脚本的复用性大大提升,从而真正的达到提升测试与运行效率的目的。