Day924.自动化测试 -系统重构实战
Posted 阿昌喜欢吃黄桃
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Day924.自动化测试 -系统重构实战相关的知识,希望对你有一定的参考价值。
自动化测试
Hi,我是阿昌
,今天学习记录的是关于自动化测试
的内容。
自动化测试是一个很容易产生“争议”的话题,也经常会有一些很有意思的问题。
- 自动化测试不是应该由测试同学来编写吗,开发是不是没有必要学吧?
- 之前一个自动化测试都没写过,怎么开始落地呢?
- 编写自动化测试代码意味着要写更多的代码,这能带来什么好处呢?
在这个过程中的代码”
一、示例介绍
这个示例是一个登录的场景。
当用户在登录页面输入正确的账户和密码时,能正常跳转到登录界面,否则提示登录失败的信息。
下面是关键的代码。
- 登录页面代码
public class LoginActivity extends AppCompatActivity
private LoginLogic loginLogic = new LoginLogic();
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
final EditText usernameEditText = findViewById(R.id.username);
final EditText passwordEditText = findViewById(R.id.password);
final Button loginButton = findViewById(R.id.login);
loginButton.setOnClickListener(new View.OnClickListener()
@Override
public void onClick(View v)
boolean success = loginLogic.login(LoginActivity.this,usernameEditText.getText().toString(),
passwordEditText.getText().toString());
if (success)
//登录成功跳转主界面
startActivity(new Intent(LoginActivity.this, MainActivity.class));
else
//登录失败进行提示
Toast.makeText(LoginActivity.this, "login failed", Toast.LENGTH_LONG).show();
);
- 登录逻辑代码
public class LoginLogic
public boolean login(Context context,String username, String password)
if (!isUserNameValid(username) || !isPasswordValid(password))
return false;
else
//通过服务器判断账户及密码的有效性
boolean result = checkFromServer(username, password);
if (result)
//登录成功保持本地的信息
SharedPreferencesUtils.put(context, username, password);
return result;
// 为了进行演示,去除通过服务器鉴定的逻辑,当用户输入特定账号及密码为时则验证成功
private static boolean checkFromServer(String username, String password)
if (username.equals("123@163.com") && password.equals("123456"))
return true;
return false;
private boolean isUserNameValid(String username)
if (username == null)
return false;
if (username.contains("@"))
return Patterns.EMAIL_ADDRESS.matcher(username).matches();
else
return !username.trim().isEmpty();
private boolean isPasswordValid(String password)
return password != null && password.trim().length() > 5;
注意,这里为了简化演示,将验证的逻辑写死在本地了。另外,账户密码有两个核心的验证规则。
- 账户不能为空,需要符合邮箱规则。
- 密码不能为空,长度需要超过 5 个字符。
二、搭建测试环境
当通过默认的编辑器创建新的项目工程时,编辑器会自动创建好测试的运行配置,一般无需修改。
如果要增加测试框架,就把测试框架的 Maven 坐标添加到对应的 dependencies 中即可。
Gradle 中的测试相关配置代码是后面这样。
android
defaultConfig
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
dependencies
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
接下来,在默认的 /src/test 或 /src/androidTest 目录下编写用例。
注意,test 目录的用例运行不依赖于设备,androidTest 目录下的用例运行需要依赖设备。
一般来说,自动化测试分为小型、中型和大型三种,逐个看看这三种测试怎么落地。
三、小型自动化测试实践
小型测试是指单元测试
,用于验证应用的行为,一次验证一个类。
在这个示例中,LoginLogic 主要承担的是登录逻辑,这里就以账户密码的验证逻辑为例,演示一下小型测试的编写,这两个逻辑的主要规则是这样。
- 账户不能为空,需要符合邮箱规则。
- 密码不能为空,长度需要超过 5 个字符。
接着,设计对应的测试用例。
注意,用例的设计应该包含正常和异常的验证场景,具体的测试场景是后面这样。
- 输入大于 6 个字符长度的密码,验证成功。
- 输入为 Null 的字符,验证失败。
- 输入小于 5 个字符长度的密码,验证失败。
- 输入等于 5 个字符长度的密码,验证失败。
下面新建一个 LoginLogicTest 的测试类,按照上述的测试场景编写用例。
这里我会采用 given(输入)、when(执行)、then(结果)的形式,让用例更加结构化,便于理解和维护。
具体的测试用例代码如下:
public class LoginLogicTest
@Test
public void should_return_false_when_password_is_null()
LoginLogic loginLogic = new LoginLogic();
String password = null;
boolean result = loginLogic.isPasswordValid(password);
Assert.assertFalse(result);
@Test
public void should_return_false_when_password_length_is_less_than_5()
LoginLogic loginLogic = new LoginLogic();
String password = "1234";
boolean result = loginLogic.isPasswordValid(password);
Assert.assertFalse(result);
@Test
public void should_return_false_when_password_length_is_equal_5()
LoginLogic loginLogic = new LoginLogic();
String password = "12345";
boolean result = loginLogic.isPasswordValid(password);
Assert.assertFalse(result);
@Test
public void should_return_true_when_password_length_greater_than_5()
LoginLogic loginLogic = new LoginLogic();
String password = "123456";
boolean result = loginLogic.isPasswordValid(password);
Assert.assertTrue(result);
通过点击用例旁的运行箭头可以执行用例,如下图所示。
运行完就可以直接查看运行结果了,如下图所示。
可以看出小型测试的执行时间还是比较快的,4 个用例总共用了 7 ms。
此外,还可以用另一种方式执行测试用例:使用命令行./gradlew test,运行 test 目录下的测试用例,如下图所示。
执行完测试后,在 /build/reports/tests 下可以查看到对应的测试报告,报告截图如下所示,从中能得到每个用例具体的执行情况和执行时间。
四、中型自动化测试实践
中型测试是指集成测试
,用于验证模块内堆栈级别之间的互动或相关模块之间的互动。
常用的测试框架有两种:Robolectric 和 Espresso。
在登录示例中,当 LoginLogic 的 login 方法被调用时,程序主逻辑首先会执行对账户名和密码的校验,接着通过服务器对账户密码的有效性做校验。
当登录成功时,程序主逻辑会通过 SharedPreferences 保存用户的信息,并在最后返回登录的状态。
从示例代码中可以看出,LoginActivity 类主要都是 UI 的操作,所以对该类主要覆盖的是 UI 相关的测试;
对于 LoginLogic 的类核心方法,login 主要负责整体的业务验证逻辑。
如何通过 Espresso 和 Robolectric 对这两个类进行中型自动化测试的覆盖。
1、Espresso 的使用
Espresso 是 Google 官方提供的界面测试框架,使用简洁且可靠。
它可以声明预期、交互和断言,不用直接访问底层应用的 Activity 和视图,可以防止测试不稳定,提高测试运行的速度。
根据用户 UI 上的主要操作,将覆盖以下两个主要的业务场景。
- 用户输入正确的用户名(123@163.com)和密码(123456),点击登录按钮能成功跳转到登录界面。
- 用户输入错误的用户名(123)和密码(456),点击登录按钮提示登录失败的 Toast。
根据测试场景,使用 Espresso 对 LoginActivity 设计的测试用例代码如下。
public class LoginActivityTest
@Test
public void should_start_main_activity_when_execute_login_given_valid_username_and_password()
ActivityScenario.launch(LoginActivity.class);
onView(withId(R.id.username)).perform(typeText("123@163.com"));
onView(withId(R.id.password)).perform(typeText("123456"));
Intents.init();
onView(withId(R.id.login)).perform(click());
intended(allOf(
toPackage("com.jkb.junbin.autotestdemo"),
hasComponent(hasClassName(MainActivity.class.getName()))));
@Test
public void should_show_failed_toast_when_execute_login_given_invalid_username_and_password()
ActivityScenario<LoginActivity> launch = ActivityScenario.launch(LoginActivity.class);
onView(withId(R.id.username)).perform(typeText("123"));
onView(withId(R.id.password)).perform(typeText("456"));
onView(withId(R.id.login)).perform(click());
View decorView = null;
launch.onActivity(activity ->
activity.getWindow().getDecorView();
);
onView(withText("login failed")).inRoot(withDecorView(not(decorView))).check(matches(isDisplayed()));
Espresso 提供的 API 能方便地进行元素的定位、执行操作和断言。
在上述两个用例中,用 onView 定位元素,用 perform 执行操作,用 check 进行断言。
如果想了解 Espresso 更多的操作 API,可以参考官网的介绍。
执行完上述用例后,运行结果是下图这样:
可以看到,这两个测试用例在模拟器中整体的运行时间在 5s 左右。
相比小型测试,中型测试耗时会更长。
并且用例运行需要依赖设备,这让运行测试的成本更高。
用例的执行过程如下图所示:
2、Robolectric 的使用
Robolectric 框架能为 Android 带来快速可靠的测试。
具体来说,依赖该框架的测试用例无须在真机或者模拟器上运行,在本地工作站上的 JVM 内完成运行即可,一般只需要几秒。
下面以 LoginLogic 的 login 方法为例,介绍一下 Robolectric 的使用。
这里需要覆盖下面三个主要的业务场景。
- 传入空的字符串或者密码,返回失败。
- 传入错误的账户及密码,返回失败。
- 传入正确的账户及密码,返回成功,并且进行数据缓存。
后面是测试用例的代码。
@RunWith(RobolectricTestRunner.class)
public class LoginLoginMediumTest
private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
@Test
public void should_return_false_when_given_invalid_username_or_password()
LoginLogic loginLogic = new LoginLogic();
boolean nullUserNameResult = loginLogic.login(mContext, null, "123");
Assert.assertFalse(nullUserNameResult);
boolean nullPasswordResult = loginLogic.login(mContext, "123", null);
Assert.assertFalse(nullPasswordResult);
@Test
public void should_return_false_when_given_error_username_and_password()
//验证错误的账户及密码
boolean result = new LoginLogic().login(mContext, "123", "456");
Assert.assertFalse(result);
@Test
public void should_return_true_when_given_correct_username_and_password()
String username = "123@163.com";
String password = "123456";
//验证正确的账户及密码
boolean result = new LoginLogic().login(mContext, username, password);
Assert.assertTrue(result);
//验证存在缓存信息
String cachePassword = (String) SharedPreferencesUtils.get(mContext, username, "");
Assert.assertEquals(password, cachePassword);
关于 Robolectric 的 API,可以参考官网的介绍。
执行上述测试用例后,运行结果如下图。
从结果可以看出,用 Robolectric 框架进行测试,用例的执行时间在毫秒到秒之间。其中第二个用例的执行耗时超过 2s,是因为启动 Robolectric 框架需要一定的时间。同时我们也能体会到 Robolectric 的核心优势:无须依赖设备,可以快速在本地的 JVM 进行验证,得到快速的反馈。
五、大型自动化测试实践
大型测试是指端到端测试
,用于验证跨越了应用的多个模块的用户操作流程。
前面介绍的 Espresso 和 Robolectric 主要是针对单个页面的测试场景,在实际的应用业务场景中,还有涉及跨应用和系统 UI 交互的场景。
通常会用 UI Automator 完成大型测试。
UI Automator 是一个界面测试框架,适用于整个系统和多个已安装应用间的跨应用功能界面测试。
它提供了一组 API,用于构建在用户应用和系统应用上执行交互的界面测试。
通过 UI Automator API,可以在测试设备中执行打开“设置”菜单或应用启动器等操作。
相比 Espresso 和 Robolectric 编写的白盒测试用例,UI Automator 测试框架非常适合编写黑盒式自动化测试,此类测试的测试代码不依赖于目标应用的内部实现细节。下面继续对登录示例进行完整的功能测试。
完整的用户测试场景是这样的:
用户在手机的任意界面,返回桌面启动测试的应用登录界面,然后输入正确的用户名和密码,成功跳转到主界面,验证主界面上显示的用户信息是否正确。这个用户场景会涉及到多个应用,其中包括了桌面和目标测试应用。
另外,应用内还会涉及到多个页面,主要是登录界面和主界面。
现在使用 UI Automator 框架进行测试,用例代码是这样。
@RunWith(AndroidJUnit4.class)
public class SmellTest
private static final String BASIC_SAMPLE_PACKAGE
= "com.example.sample";
private static final int LAUNCH_TIMEOUT = 5000;
private UiDevice mDevice;
@Before
public void startActivityFromHomeScreen()
// 初始化UiDevice
mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
// 回到主界面
mDevice.pressHome();
// 等待launcher
final String launcherPackage = mDevice.getLauncherPackageName();
assertThat(launcherPackage, notNullValue());
mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)),
LAUNCH_TIMEOUT);
// 启动目标APP
Context context = ApplicationProvider.getApplicationContext();
final Intent intent = context.getPackageManager()
.getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
context.startActivity(intent);
// 等待应用启动
mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)),
LAUNCH_TIMEOUT);
//账户密码登录成功后主界面显示用户名
@Test
public void should_show_username_in_main_activity_when_login_success()
//输入账户名
mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "username"))
.setText("123");
//输入密码
mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "password"))
.setText("123");
//点击登录
mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "login"))
.click();
//验证主界面上显示用户名信息
UiObject2 text = mDevice
.wait(Until.findObject(By.res(BASIC_SAMPLE_PACKAGE, "text")),
500);
assertEquals(text.getText(), "123");
UI Automator 测试框架提供了一个 UiDevice 类,用于在运行目标应用的设备上访问和执行操作。
通过调用 findObject 方法,可以定位到元素和执行操作。
关于 UI Automator 更多的 API 使用,你可以参考官网文档 。
执行上述测试用例后,运行结果如下图所示:
下面这个动图展示了用例的执行过程,可以对比一下,这个过程如果用手工执行需要多久。
通过执行结果可知,该用例的执行时间实际为 9s,比中小型测试的执行时间更长,并且需要依赖真机或模拟器。
不过,该用例基本都是模拟用户对界面的点击操作,更贴近实际用户的真实使用场景。
六、总结
- 小型测试能够快速帮
验证代码
中的核心逻辑和算法,通常使用的是 Junit 或者 Robolectric 等测试框架; - 中型测试能够帮验证代码中的一些核心
组件交互
流程,通常会用 Espresso 或者 Robolectric 等框架来完成; - 大型测试则能帮验证端
到端的用户使用场景
,通常使用的是 UIAutomator 或者 Appium 等框架。
重新思考一下开头的三个问题:
- 第一个是测试由谁来写的问题。中小型的测试大部分都是根据代码设计来编写的。编写者需要了解原来代码的设计,精确到各个方法以及方法内部的条件分支和异常处理。所以,中小型测试应该由开发人员来编写。另外,也鼓励开发人员参与到大型端到端自动化测试的编写中。因为只有开发代码和测试代码一起共同维护,成本才是最低的。
- 第二个问题是:之前一个自动化测试都没写过,怎么开始落地?对于开发人员来说,编写自动化测试用例的难度其实比功能开发设计还简单。
- 第三个问题是关于自动化测试价值的问题。这里有一个前提:不认为开发完代码就意味着结束,结束应该是在有足够的质量保证的前提下。
所以,自动化测试是应用开发过
第一个自动化测试框架(day10)
一.自动化测试分类:
1.数据驱动:
根据数据(读取excel)来去测试的。
2.代码驱动:
测试用例都是写代码的。
3.关键字驱动:
UI自动化,根据封装好的工具,输入关键字来测试,有点傻瓜式测试。
二.自动化框架:可以理解为工具的集合。日常工作中,根据工作需求,实现某些功能,封装起来。或结合其他自动化工具。
三.举例:数据驱动自动测试框架:
自动化测试框架步骤:
1.获取用例
2.调用接口
3.校验结果
4.发送测试报告
5.异常处理
6.日志模块
码起来:【宗旨:一个函数只做一件事情】
提示:
1.用例文件的内容:
2.ATP文件的结构:
一.在common.py里写OpCase类,及get_case,my_request,check_res,write_excel,dataToDict函数。
1.get_case()函数:获取用例。获取用例中需要的数据:url,请求方式,请求数据及预期结果,放到cases list里
import xlrd from lib.log import atp_log class OpCase(object): def get_case(self,file_path):#获取用例的函数 cases = [] #存放所有的case if file_path.endswith(\'.xls\') or file_path.endswith(\'.xlsx\'): #判断用例文件是否是excel try: book = xlrd.open_workbook(file_path) sheet = book.sheet_by_index(0) for i in range(1,sheet.nrows): row_data = sheet.row_values(i) #获取每行 cases.append(row_data[4:8]) #将用例中第5列至第9列截取到cases list中 atp_log.info(\'共读取%s条用例\'%(len(cases))) #读取用例的条数 self.file_path = file_path #在实例中将路径(文件名)定义,在后面write_excel()函数中不需要再传入该参数,可以直接用,否则需要在write_excel()函数中传入参数file_path except Exception as e: atp_log.error(\'【%s】用例获取失败,错误信息:%s\'%(file_path,e)) else: atp_log.error(\'用例文件不合法的,%s\'%file_path) return cases
2.my_request()及dataToDict()函数,调用接口。通过url,data请求获取返回的内容,其中获取的data需要先转成字典
import requests def my_request(self,url,method,data):#参数url,method,data method = method.upper() #传入参数有可能会有大小写,统一转成大写字母 data = self.dataToDict(data) #将获取的数据做处理,转成字典 try : if method==\'POST\': #判断method的类型 res = requests.post(url,data).text #发送请求并转换成字典 elif method==\'GET\': res = requests.get(url,params=data).text else: atp_log.warning(\'该请求方式暂不支持。。\') #调用日志文件 res = \'该请求方式暂不支持。。\' except Exception as e: msg = \'【%s】接口调用失败,%s\'%(url,e) atp_log.error(msg) res = msg return res #返回请求返回的内容 def dataToDict(self,data):#从excel表里获取的data格式:username=fancy,passwd=abc123456,把数据转成字典 res = {} data = data.split(\',\') for d in data: k,v = d.split(\'=\') res[k]=v return res
3.check_res()函数,校验结果成功或失败。将从服务器获取的返回结果与excel表里的预期结果比较,是否一致,若一致返回“成功”并写入结果,否则失败。
首先了解从my_request里获取的返回内容的样式:
excel表里预期结果的样式:
思路:将my_request()函数里返回的res样式变为:
然后判断预期结果check是否在res返回的这个字典里。
具体实现如下:
def check_res(self,res,check): res = res.replace(\'": "\',\'=\').replace(\'": \',\'=\') #两次替换,把res里返回的字典里所有的替换成=号(因为返回的Json串格式都是一样的) for c in check.split(\',\'): #将预期结果里的以逗号分开 if c not in res: #判断预期结果里的list是否在返回结果res里 atp_log.info(\'结果校验失败,预期结果:【%s】,实际结果【%s】\'%(c,res)) return \'失败\' return \'成功\'
4.write_excel()函数,写excel。获取返回每条用例的执行结果cases_res。需导入xlutils模块。
注:在get_case()函数里,定义了: self.file_path = file_path ,因此在该函数write_excel()中不需要再传入file_path参数,可以直接调用,否则需要在write_excel()函数中传入参数file_path。
首先需要了解用例返回结果样式:返回一个二维数组:[[\'abcxx,‘通过’],[abxx2,‘失败’]....],将结果写入返回报文和测试结果列。
from xlutils import copy def write_excel(self,cases_res): # [ [\'dsfd\',"通过"] ,[\'sdfsdf\',\'失败\'] ] book = xlrd.open_workbook(self.file_path) new_book = copy.copy(book) sheet = new_book.get_sheet(0) row = 1 #从第二行开始写,第一行是表头 for case_case in cases_res: #获取到每一个结果 sheet.write(row,8,case_case[0]) #写第8列 sheet.write(row,9,case_case[1]) #写第9列 row+=1 new_book.save(self.file_path.replace(\'xlsx\',\'xls\')) #如果是xlsx结尾的将替换为xls结尾,保证excel可以打开
二.在lib下写发邮件的类:send_mail.py
发邮件是需要定义一些变量、常量,因此将这些内容写在配置文件setting里便于管理。
import yagmail from conf import setting #将setting文件里定义的常量导入 from lib.log import atp_log #导入lib下的log文件,写日志 def sendmail(title,content,attrs=None): m = yagmail.SMTP(host=setting.MAIL_HOST,user=setting.MAIL_USER ,password=setting.MAIL_PASSWRD,smtp_ssl=True #当是qq邮箱时,需加入smtp_ssl=True ) m.send(to=setting.TO,subject=title, contents=content, attachments=attrs) atp_log.info(\'发送邮件完成\')
三.在conf下新建setting.py,配置文件里定义各种需要的变量,常量,修改时,方便。
import os BASE_PATH = os.path.dirname( os.path.dirname(os.path.abspath(__file__)) #获取ATP所在的路径 ) MAIL_HOST=\'smtp.163.com\' MAIL_USER=\'liu.****@163.com\' MAIL_PASSWRD = \'p888****g421\' TO = [ \'5472*****@qq.com\', ] LEVEL = \'debug\' #日志级别 LOG_PATH = os.path.join(BASE_PATH,\'logs\') #存放日志的路径,动态获取的,无论文件存放位置 CASE_PATH = os.path.join(BASE_PATH,\'cases\') #存放日志的路径 LOG_NAME=\'atp.log\' #日志的文件名
四.在lib下log.py文件。写日志文件,在其他文件抛异常时,会打印日志,直接调用该文件。
import logging,os from logging import handlers from conf import setting class MyLogger(): def __init__(self,file_name,level=\'info\',backCount=5,when=\'D\'): logger = logging.getLogger() # 先实例化一个logger对象,先创建一个办公室 logger.setLevel(self.get_level(level)) # 设置日志的级别的人 cl = logging.StreamHandler() # 负责往控制台输出的人 bl = handlers.TimedRotatingFileHandler(filename=file_name, when=when, interval=1, backupCount=backCount, encoding=\'utf-8\') fmt = logging.Formatter(\'%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s\') cl.setFormatter(fmt) # 设置控制台输出的日志格式 bl.setFormatter(fmt) # 设置文件里面写入的日志格式 logger.addHandler(cl) logger.addHandler(bl) self.logger = logger def get_level(self,str): level = { \'debug\':logging.DEBUG, \'info\':logging.INFO, \'warn\':logging.WARNING, \'error\':logging.ERROR } str = str.lower() return level.get(str) path = os.path.join(setting.LOG_PATH,setting.LOG_NAME) #拼好日志的绝对路径 atp_log = MyLogger(path,setting.LEVEL).logger #日志级别 #直接在这里实例化,用的时候就不用再实例化了
五.在bin下新建start.py。start.py文件是启动文件,程序的入口,串联整个系统功能的所有逻辑。
import os,sys BASE_PATH = os.path.dirname( os.path.dirname(os.path.abspath(__file__)) #获取ATP的路径 ) sys.path.insert(0,BASE_PATH) #将ATP目录下的所有文件加到环境变量,这样保证以后在哪里运行都可以 from lib.common import OpCase #导入读取用例的类 from lib.send_mail import sendmail #导入发邮件的类 from conf import setting #导入配置文件 class CaseRun(object): #启动的类 def find_cases(self): op = OpCase() #实例化获取用例的类,便于调用OpCase()类下的函数 for f in os.listdir(setting.CASE_PATH):#每次循环的时候读一个excel abs_path = os.path.join(setting.CASE_PATH,f) #拼成绝对路径,f只是一个文件名 case_list = op.get_case(abs_path) #调用OpCase()类下的获取用例的函数,获取所有用例 res_list = [] pass_count,fail_count = 0,0 for case in case_list:#循环每个excel里面所有用例 url,method,req_data,check = case #check:预期结果 res = op.my_request(url,method,req_data) #调用完接口返回的结果,res:实际结果 status = op.check_res(res,check) #调用check_res()函数,校验预期结果&实际结果 res_list.append([res,status]) #将返回结果和测试结果加入一个list,定义res_list,便于写入excel if status==\'成功\': pass_count+=1 else: fail_count+=1 op.write_excel(res_list) #将res_list(返回报文,测试结果)写入excel msg = \'\'\' xx你好: 本次共运行%s条用例,通过%s条,失败%s条。 \'\'\'%(len(res_list),pass_count,fail_count) sendmail(\'测试用例运行结果\',content=msg,attrs=abs_path) #发邮件
六.启动:在start.py文件中:
CaseRun().find_cases() #调用find_cases函数
欧了,整个简单的自动化框架包装完毕。
以上是关于Day924.自动化测试 -系统重构实战的主要内容,如果未能解决你的问题,请参考以下文章
Python实训day07am爬取数据接口webdriver自动化测试工具selenium