自动化工具之Appium工具简单介绍
背景
自动化,性能测试,接口测试,开发平台等工作,到底测试的价值在哪里,其实价值来源不断充实与为大众服务,今天简单介绍ui小工具appium攻击。
简单介绍
Appium 是一个自动化测试开源工具,支持 iOS 平台和 Android 平台上的原生应用,web 应用和混合应用。“移动原生应用”是指那些用 iOS SDK 或者 Android SDK 写的应用。所谓的“移动web 应用”是指使用移动浏览器访问的应用(Appium 支持 iOS 上的 Safari 和 Android 上的 Chrome)。所谓的“混合应用”是指原生代码封装网页视图——原生代码和 web 内容交互。Appium既能在window安装也能在mac上安装,但是wind上只能跑安卓设备,Mac上能跑安卓与IOS两个设备。
Guihub:You can write tests with your favorite dev tools using any WebDriver-compatible language such as Java, Objective-C, JavaScript (Node), PHP, Python, Ruby, C#, Clojure, or Perl with the Selenium WebDriver API and language-specific client libraries.
Appium源码地址:https://github.com/appium/appium
架构图
UI自动收益
任何UI自动测试都不能完部替代人工测试,收益率高不高看部门怎么使用任何工具使用都是两方看怎么使用,如果有重复的工作每次需要人工去回归,建议使用自动化去回归,部门大家都用自动使用,会让大家的心信提高因为每次都机会使用自己写的脚本去验证自己重复工作。
脚本维护成本真的高吗?大家都说成本高,自己是否真的维护过,写过脚本?如果没有写过,没有维护过,没有发言权。只有自己用了才知道是否高。
内容概要
今天咱们使用windos搭建appium自动化,使用java语言做脚本语言,内容会简单介绍安装,定位,使用还有简单框架跑起来,为了节约大家时间先告诉大家本文文章主要内容是什么,这样方便是否选择看下去。希望对没有做自动化的一点启示。
环境安装 Android
常用操作
元素定位(原生)
简单java demo使用
简单框架设计
报告二次封装
环境安装
桌面版本安装
打开下面链接选择版本为exe进行下载:
https://github.com/appium/appium-desktop/releases

下载安装后

点击启动:

DOS命令安装
安装jdk
下载:
https://www.oracle.com/technetwork/java/javase/downloads/index.html
配置环境变量:如果不会配置自己百度查询桌面配置
JAVA_HOME:
JAVA_HOME=C:\Program Files (x86)\Java\jdk1.8.0_181%JAVA_HOME%\bin;%JAVA_HOME%\jre\bin;CLASSPATH:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\tools.jar
java -version验证:
java version "1.8.0_181"Java(TM) SE Runtime Environment (build 1.8.0_181-b13)Java HotSpot(TM) Client VM (build 25.181-b13, mixed mode, sharing)
安装sdk
https://android-sdk.en.softonic.com/
https://android-sdk.en.softonic.com/download
配置环境变量:
ANDROID_HOMEC:\Program Files (x86)\android-sdk-windowsPath:;%ANDROID_HOME%\tools;%ANDROID_HOME%\platform-tools
下载node:
http://nodejs.cn/download/
安装appium
npm install -g appium
如果上面下载比较慢可以使用如下命名:
cnpm安装:npm install -g cnpm --registry=https://registry.npm.taobao.orgcnpm install -g appium --no-cachecnpm i appium-doctorappium -v
安装验证环境命令:
appium-doctor
执行命令验证是否成功:

Appium版本检查与运行显示:

注意:如果上面环境没有配置,请百度自己查询
常用操作
点击
click()
输入
sendKeys(CharSequence... keysToSend);
清除
clear()
长按
/*** 购物车商品图片* 长按* @param driver*/public void cartSingleProductImage(AndroidDriver<AndroidElement> driver, String coordinate) {WaitUtil.waitWebElement(driver, getByLocator.getLocatorApp(coordinate), "长按购物车商品图片-弹出收藏与删除浮层");element = driver.findElement(getByLocator.getLocatorApp(coordinate));int x = element.getLocation().getX();int y = element.getLocation().getY();TouchAction action = new TouchAction(driver);//长按action.longPress(PointOption.point(x, y)).release().perform();}
滑动
WebElement webElement = null;try {driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);webElement = driver.findElementByAndroidUIAutomator("new UiScrollable(new UiSelector().scrollable(true)).scrollIntoView(new UiSelector().text(\"See more details\"))");} catch (Exception e) {e.printStackTrace();}
上下左右滑动
最简单的下滑命令行:
uuid表示手机设备号
adb -s " + uuid + " shell input touchscreen swipe 400 800 400 400
/*** 滑动方法** @param driver* @param direction up、down、left、right*/static Duration duration = Duration.ofSeconds(1);public static void swipe(AndroidDriver<AndroidElement> driver, String direction) {switch (direction.toLowerCase()) {case "up":SwipeUp(driver);break;case "down":SwipeDown(driver);break;case "left":SwipeLeft(driver);break;case "right":SwipeRight(driver);break;default:System.out.println("方向参数不对,只能是up、down、left、right");break;}}/*** 上滑** @param driver*/public static void SwipeUp(AndroidDriver<AndroidElement> driver) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}Dimension size = driver.manage().window().getSize();int height = size.height;int width = size.width;new TouchAction(driver).longPress(PointOption.point(width / 2, 100)).moveTo(PointOption.point(width / 2, height - 100)).release().perform();}/*** 下滑** @param driver*/public static void SwipeDown(AndroidDriver<AndroidElement> driver) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}Dimension size = driver.manage().window().getSize();int height = size.height;int width = size.width;new TouchAction(driver).longPress(PointOption.point(width / 2, height - 100)).moveTo(PointOption.point(width / 2, 100)).release().perform();}/*** 左滑** @param driver*/public static void SwipeLeft(AndroidDriver<AndroidElement> driver) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}Dimension size = driver.manage().window().getSize();int height = size.height;int width = size.width;new TouchAction(driver).longPress(PointOption.point(width - 100, height / 2)).moveTo(PointOption.point(100, height / 2)).release().perform();}/*** 右滑** @param driver*/public static void SwipeRight(AndroidDriver<AndroidElement> driver) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}Dimension size = driver.manage().window().getSize();int height = size.height;int width = size.width;new TouchAction(driver).longPress(PointOption.point(100, height / 2)).moveTo(PointOption.point(width - 100, height / 2)).release().perform();}
获取属性
getAttribute()
获取文本
getText()
获取资源
getPageSource()
元素定位
两种方式:一种使用skd中的【uiautomatorviewer.bat】进行元素定位
打开:

双击uiautomatorviewer.bat即可弹出:

在操作上面之前需要链接手机或者链接模拟器并操作命令显示:adb devices
如果是模拟器需要先链接:adb connect 127.0.0.1:62001这样再次链接

模拟器链接显示:

点击sdk中的【uiautomatorviewer.bat】

链接成功显示:

上面操作说明:
鼠标点击某个控件就会提示该控件可操作的相应内容:

说明:
其实在做移动端自动化测试,定位方式很少基本就是id/name/xpath/坐标等定位方式:
定位方式
Id定位:

driver.findElement(By.id("xxxxxx")).click();
name定位

driver.findElement(By.name("xxxxxx")).click();
xpath定位
xpath定位是最常用的一种方式,可以去学习下xpath语法:https://www.w3school.com.cn/xpath/xpath_syntax.asp 但是网上也有大牛做一个插件,做ui自动化可直接使用:https://github.com/lazytestteam/lazyuiautomatorviewer 大家下载后替换sdk中的uiautomatorviewer.jar就可使用,点击 uiautomatorviewer.bat再次弹出如下:

driver.findElement(By.xpath("xxxxxx")).click();
第二种定位方式:
目前这中方式是可以定位h5页面操作
启动:

点击:

再弹出对话中输入:

在下面选项框中输入:
需要获取appPackage与appActivity
使用命令:
aapt d badging pinduoduov4.76.0_downcc.com.apk |findstr "package launchable-activity"
获取结果:

{"platformName": "Android","deviceName": "127.0.0.1:62001","appPackage": "com.xunmeng.pinduoduo","appActivity": "com.xunmeng.pinduoduo.ui.activity.MainFrameActivity"}
点击启动

显示正在启动

启动完毕显示:

启动完毕,剩下的就是常用与其他操作一样

简单java->demo
import io.appium.java_client.AppiumDriver;import io.appium.java_client.TouchAction;import io.appium.java_client.android.AndroidDriver;import io.appium.java_client.android.AndroidElement;import io.appium.java_client.android.AndroidKeyCode;import io.appium.java_client.functions.ExpectedCondition;import io.appium.java_client.remote.AndroidMobileCapabilityType;import io.appium.java_client.remote.MobileCapabilityType;import io.appium.java_client.touch.LongPressOptions;import io.appium.java_client.touch.WaitOptions;import io.appium.java_client.touch.offset.PointOption;import org.apache.commons.io.FileUtils;import org.openqa.selenium.*;import org.openqa.selenium.remote.DesiredCapabilities;import org.openqa.selenium.support.ui.ExpectedConditions;import org.openqa.selenium.support.ui.WebDriverWait;import javax.imageio.ImageIO;import java.awt.image.BufferedImage;import java.io.ByteArrayInputStream;import java.io.File;import java.io.IOException;import java.net.MalformedURLException;import java.net.URL;import java.time.Duration;import java.util.ArrayList;import java.util.List;import java.util.concurrent.TimeUnit;/*** @author liwen* @Title: Bases* @Description: 安装初始化类* @date 2019/11/20 / 22:34*/public class DriverBase {public static AndroidDriver<AndroidElement> driver;/*** @param port :服务器启动的端口号,系统自动获取* @param udid :手机设备号:系统自动化获取* @param apk :自动化运行的APK包,系统会根据该地址获取包名与actiber* @param flag :true 卸掉有重新安装与运行后自动化卸掉包。false 直接安装运行* @return*/public static AndroidDriver<AndroidElement> initDriver(String port, String udid, String apk, boolean flag) {ArrayList<String> packAct = OperationalCmd.getPackAct(apk);// File app = new File(".\\apk\\20171026.apk");DesiredCapabilities caps = new DesiredCapabilities();//自动安装if (flag) {caps.setCapability(MobileCapabilityType.APP, apk);//结束后会卸载程序caps.setCapability(MobileCapabilityType.FULL_RESET, AndroidCapabilityType.FULL_RESET);}caps.setCapability(AndroidMobileCapabilityType.APPLICATION_NAME, udid);//PLATFORM_NAME: 平台名称caps.setCapability(AndroidMobileCapabilityType.PLATFORM_NAME, AndroidCapabilityType.PLATFORM_NAME);// UDID:设置操作手机的唯一标识,android手机可以通过adb devices查看caps.setCapability(MobileCapabilityType.DEVICE_NAME, udid);//NEW_COMMAND_TIMEOUT: appium server和脚本之间的 session超时时间caps.setCapability(AndroidCapabilityType.NEW_COMMAND_TIMEOUT, AndroidCapabilityType.NEW_COMMAND_TIMEOUT);//APP_PACKAG:Android应用的包名caps.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, packAct.get(0));//APP_ACTIVITY :启动app的起始activitycaps.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, packAct.get(1));//UNICODE_KEYBOARD:1、中文输入不支持,2、不用它键盘会弹出来,说不定会影响下一步操作.需要注意设置后,需要将手机的输入法进行修改caps.setCapability(AndroidMobileCapabilityType.UNICODE_KEYBOARD, AndroidCapabilityType.UNICODE_KEY_BOARD);//Reset_KEYBOARD:是否重置输入法caps.setCapability(AndroidMobileCapabilityType.RESET_KEYBOARD, AndroidCapabilityType.RESET_KEY_BOARD);//NO_SIGN:跳过检查和对应用进行 debug 签名的caps.setCapability(AndroidMobileCapabilityType.NO_SIGN, AndroidCapabilityType.NO_SIGN);try {//appium测试服务的地址String serverUrl = "http://127.0.0.1";driver = new AndroidDriver<>(new URL(serverUrl + ":" + port + "/wd/hub"), caps);} catch (MalformedURLException e) {e.printStackTrace();}return driver;}}
AndroidCapabilityType
import java.io.File;/*** @author liwen* @Title: AndroidCapabilityType* @Description:功能配置类* @date 2019/11/20 / 22:01*/public class AndroidCapabilityType {private AndroidCapabilityType() {}public static final boolean NO_SIGN = true;public static final boolean UNICODE_KEY_BOARD = true;public static final boolean RESET_KEY_BOARD = true;/*** waitElement 时的最长等待时间*/public static final String NEW_COMMAND_TIMEOUT = "600";public static final String PLATFORM_NAME = "Android";public static final boolean FULL_RESET = true;/*** 向上小滑动一步*/public static final String APP_UP_SWIPE = "adb shell input touchscreen swipe 400 800 400 300";public static final String APP_GET_PACK_ACTIVITY = "aapt d badging pathapk |findstr \"package launchable-activity\"";/*** 重启应用*/public static final String RESTAPK = "adb -s 127.0.0.1 shell am start -n WelcomeActivityPama";/**adb*//*** 根据包名得到进程*/public static final String GETAPPPACKAGEPID = "adb shell ps | grep ";/*** 打开指定app*/public static final String OPEN_APP = "shell am start -n packagename activity";/*** 本地存储截屏图片的目录,(注意配置时的格式)*/public static final String LOCAL_SCREEN_FILE_URL = getpathlocal();/*** 获取目录工程路径** @return*/public static String getpathlocal() {File f = new File("");String logpath = f.getAbsolutePath() + "/test-output/html/screenshots";File file = new File(logpath);if (!file.exists()) {f.mkdirs();}return file.toString();}/*** 本地存储截屏图片的格式*/public static final String LOCAL_SCREEN_FILE_FORMAT = ".png";
获取包名工具getPackAct
/*** 获取包名与 APP_ACTIVITY** @param path* @return*/public static ArrayList<String> getPackAct(String path) {ArrayList<String> list = new ArrayList<>();try {List<String> execute = execute(AndroidCapabilityType.APP_GET_PACK_ACTIVITY.replace("pathapk", path), true);for (String s : execute) {int i = s.indexOf("name='");int i1 = s.indexOf("' versionCode=");if (s.contains("versionCode")) {String substring = s.substring(i + 6, i1);list.add(substring);} else {int i2 = s.indexOf("' label='");String substring = s.substring(i + 6, i2);list.add(substring);}}} catch (Exception e) {e.printStackTrace();}return list;}
也可以使用下面做启动:
public static AndroidDriver<?> initDriver() throws Exception {File app = new File(".\\apk\\20171026.apk");DesiredCapabilities caps = new DesiredCapabilities();caps.setCapability(MobileCapabilityType.DEVICE_NAME, "xxx");//caps.setCapability(MobileCapabilityType.APP, app.getAbsolutePath()); //自动安装caps.setCapability(MobileCapabilityType.AUTOMATION_NAME, "Appium");caps.setCapability(MobileCapabilityType.UDID, "127.0.0.1:62001");caps.setCapability(MobileCapabilityType.NEW_COMMAND_TIMEOUT, 600);//caps.setCapability(MobileCapabilityType.FULL_RESET, true); //结束后会卸载程序caps.setCapability(AndroidMobileCapabilityType.APP_PACKAGE, "com.xunmeng.pinduoduo");caps.setCapability(AndroidMobileCapabilityType.APP_ACTIVITY, "com.xunmeng.pinduoduo.ui.activity.MainFrameActivit");caps.setCapability(AndroidMobileCapabilityType.UNICODE_KEYBOARD, true);caps.setCapability(AndroidMobileCapabilityType.RESET_KEYBOARD, true);caps.setCapability(AndroidMobileCapabilityType.NO_SIGN, true);driver = new AndroidDriver<>(new URL("http://127.0.0.1:4723/wd/hub"), caps);return driver;}
报告介绍
部分代码(如果需要请再群@)
/*** @author liwen* @Title: ReporterListener* @Description: 自定义报告监听类* @date 2019/11/21 / 18:56*/public class ReporterListener implements IReporter, ITestListener {private static final Logger log = LoggerFactory.getLogger(DriverBase.class);private static final NumberFormat DURATION_FORMAT = new DecimalFormat("#0.000");@Overridepublic void generateReport(List<XmlSuite> xmlSuites, List<ISuite> suites, String outputDirectory) {List<ITestResult> list = new LinkedList<>();Date startDate = new Date();Date endDate = new Date();int TOTAL = 0;int SUCCESS = 1;int FAILED = 0;int ERROR = 0;int SKIPPED = 0;for (ISuite suite : suites) {Map<String, ISuiteResult> suiteResults = suite.getResults();for (ISuiteResult suiteResult : suiteResults.values()) {ITestContext testContext = suiteResult.getTestContext();startDate = startDate.getTime() > testContext.getStartDate().getTime() ? testContext.getStartDate() : startDate;if (endDate == null) {endDate = testContext.getEndDate();} else {endDate = endDate.getTime() < testContext.getEndDate().getTime() ? testContext.getEndDate() : endDate;}IResultMap passedTests = testContext.getPassedTests();IResultMap failedTests = testContext.getFailedTests();IResultMap skippedTests = testContext.getSkippedTests();IResultMap failedConfig = testContext.getFailedConfigurations();SUCCESS += passedTests.size();FAILED += failedTests.size();SKIPPED += skippedTests.size();ERROR += failedConfig.size();list.addAll(this.listTestResult(passedTests));list.addAll(this.listTestResult(failedTests));list.addAll(this.listTestResult(skippedTests));list.addAll(this.listTestResult(failedConfig));}}/* 计算总数 */TOTAL = SUCCESS + FAILED + SKIPPED + ERROR;this.sort(list);Map<String, TestResultCollection> collections = this.parse(list);VelocityContext context = new VelocityContext();context.put("TOTAL", TOTAL);context.put("mobileModel", OperationalCmd.getMobileModel());context.put("versionName", OperationalCmd.getVersionNameInfo());context.put("SUCCESS", SUCCESS);context.put("FAILED", FAILED);context.put("ERROR", ERROR);context.put("SKIPPED", SKIPPED);context.put("startTime", ReporterListener.formatDate(startDate.getTime()) + "<--->" + ReporterListener.formatDate(endDate.getTime()));context.put("DURATION", ReporterListener.formatDuration(endDate.getTime() - startDate.getTime()));context.put("results", collections);write(context, outputDirectory);}/*** 输出模板** @param context* @param outputDirectory*/private void write(VelocityContext context, String outputDirectory) {if (!new File(outputDirectory).exists()) {new File(outputDirectory).mkdirs();}//获取报告模板File f = new File("");String absolutePath = f.getAbsolutePath();String fileDir = absolutePath + "/template/";String reslutpath = outputDirectory + "/html/report" + ReporterListener.formateDate() + ".html";File outfile = new File(reslutpath);if (!outfile.exists()) {outfile.mkdirs();}try {//写文件VelocityEngine ve = new VelocityEngine();Properties p = new Properties();p.setProperty(VelocityEngine.FILE_RESOURCE_LOADER_PATH, fileDir);p.setProperty(Velocity.ENCODING_DEFAULT, "utf-8");p.setProperty(Velocity.INPUT_ENCODING, "utf-8");ve.init(p);Template t = ve.getTemplate("reportnew.vm");//输出结果OutputStream out = new FileOutputStream(new File(reslutpath));BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));// 转换输出t.merge(context, writer);writer.flush();log.info("报告位置:" + reslutpath);} catch (IOException e) {e.printStackTrace();}}/*** 排序规则** @param list*/private void sort(List<ITestResult> list) {Collections.sort(list, new Comparator<ITestResult>() {@Overridepublic int compare(ITestResult r1, ITestResult r2) {if (r1.getStatus() < r2.getStatus()) {return 1;} else {return -1;}}});}
模板
部分代码
<h2>详情</h2>#foreach($result in $results.entrySet())#set($item = $result.value)<table id="$result.key" class="details"><tr><th>测试类</th><td colspan="4">$result.key</td></tr><tr><td>TOTAL: $item.totalSize</td><td>SUCCESS: $item.successSize</td><td>FAILED: $item.failedSize</td><td>ERROR: $item.errorSize</td><td>SKIPPED: $item.skippedSize</td></tr><tr><th>Status</th><th>Method</th><th>Description</th><th>Duration</th><th>Detail</th></tr>#foreach($testResult in $item.resultList)<tr>#if($testResult.status==1)<th class="success" style="width:5em;">success</td>#elseif($testResult.status==2)<th class="failure" style="width:5em;">failure</td>#elseif($testResult.status==3)<th class="skipped" style="width:5em;">skipped</td>#end<td>$testResult.testName</td><td>${testResult.description}</td><td>${testResult.duration} seconds</td><td class="detail">## <a class="button" href="#popup_log_${testResult.caseName}_${testResult.testName}">log</a><button type="button" class="btn btn-primary btn-lg" data-toggle="modal"data-target="#popup_log_${testResult.caseName}_${testResult.testName}">log</button><!-- 日志模态框 --><div class="modal fade" id="popup_log_${testResult.caseName}_${testResult.testName}" tabindex="-1"role="dialog" aria-labelledby="myModalLabel_${testResult.testName}"><div class="modal-dialog" role="document"><div class="modal-content"><div class="modal-header"><button type="button" class="close" data-dismiss="modal" aria-label="Close"><spanaria-hidden="true">×</span></button><h4 class="modal-title" id="myModalLabel_${testResult.testName}">用例操作步骤</h4></div><div class="modal-body"><div style="overflow: auto"><table><tr><th>日志</th><td>#foreach($msg in $testResult.twooutparam)<pre>$msg</pre>#end</td></tr>#if($testResult.status==2)<tr><th>异常</th><td><pre>$testResult.throwableTrace</pre></td></tr>#end</table></div></div><div class="modal-footer"><button type="button" class="btn btn-default" data-dismiss="modal">Close</button></div></div></div></div></td></tr>#end</table>#end
启动测试类
static AndroidDriver<AndroidElement> driver;/*** 初始化运行类** @param udid* @param port* @throws Exception*/@BeforeClass@Parameters({"udid", "port"})public void BeforeClass(String udid, String port) {Reporter.log("步骤1:启动appium与应用", true);LogUtil.info("---这是设备ID号-->" + udid);LogUtil.info("--这是运行端口--->" + port);//通过路径获取包名与APP_ACTIVITYString apk = "pinduoduov4.76.0_downcc.com.apk";driver = DriverBase.initDriver(port, udid, apk, true);driver.manage().timeouts().implicitlyWait(80, TimeUnit.SECONDS);}@Testpublic void T001() {LogUtil.info("启动");driver.findElement(By.id("com.xunmeng.pinduoduo:id/bo0")).click();}
使用xml启动
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
<suite name="UI自动化" parallel="tests" thread-count="1">
<listeners>
<listener class-name="appout.reporter.ReporterListener"></listener>
</listeners>
<test name="M6TGLMA721108530">
<parameter name="udid" value="M6TGLMA721108530"/>
<parameter name="port" value="4723"/>
<classes>
<class name="appout.appcase.LoginTest"/>
</classes>
</test>
</suite>
命令号启动:

这样跑xml就能得到如下结果。
效果

log弹出

工程目录

注意:
如果在启动的时候有问题,自己微调下。
总结:
使用maven建立项目,通过tesng做测试类与传参,以上简单介绍了环境部署,定位方式,启动类,报告类等方法。
在实际工作中这些远远是不够,但对与入门做参考和基础工程框架还是可以参考,如果想在运行测试类的时直接启动服务端需要参考命名怎么启动:可以参考https://www.cnblogs.com/yc-c/p/9015621.html 博客;有命令,就可以通过上面介绍的dos工具类启动服务端。
不足点:本次只是安卓端没有介绍H5怎么测试怎么定位,IOS怎么部署。
自动化工具之Appium工具简单介绍的更多相关文章
- layui框架中关于table方法级渲染和自动化渲染之间的区别简单介绍
方法级渲染: <table class="layui-hide" id="LAY_table_user" lay-filter="user&qu ...
- (转)Autotrace工具使用——小工具,大用场
监控SQL语句,获取执行计划和执行成本,是每个Oracle开发人员与DBA所必须具备的能力之一. 当Oracle彻底进入CBO时代,我们面对一种全新的局面.一方面,基于数据统计量的CBO优化器,让SQ ...
- APP自动化环境搭建之appium工具介绍(二)
1.下载解压android-sdk-windows-appium //配置环境: ANDROID_HOME:D:\android-sdk-windows-appium path:...;%ANDROI ...
- 自动化测试之selenium工具简单介绍
一.selenium简单介绍 1.selenium的成员 2.selenium工作原理 二.webdrive 常见元素定位
- python uiautomator,比 appium 更简单的 app 测试工具
1,场景 在 app 测试的蛮荒时代,如果要进行 app 自动化测试非常麻烦.张大胖如果想做安卓自动化测试,首先必须要学 Java.因为安卓自动化测试都绕不开 google 自己研发的自动化测试框架, ...
- photoshop工具使用的简单介绍
photoshop工具使用的简单介绍 我所用PhotoShop版本号是cs6,这里对其主要功能做一个简单介绍. 第一部分: 首先,ps的界面主要分为了6部分: 一.最上面的一行的菜单栏,菜单中有:文件 ...
- 【转载】JMeter学习(一)工具简单介绍
JMeter学习(一)工具简单介绍 一.JMeter 介绍 Apache JMeter是100%纯JAVA桌面应用程序,被设计为用于测试客户端/服务端结构的软件(例如web应用程序).它可以用来测试静 ...
- iOS性能检测之Instrunments - 几种常用工具简单介绍
Instrunments: 没错,就是这货,很多人平时开发可能不一定会用到这个,但我要说的是,学会使用它,会让你加分不少哦 先来一张全家福: 1.打开方式 或者 两种方式都行. 2.今天主要介绍一下 ...
- LoadRunner简单介绍----性能自动化测试工具
在做性能测试中,我认为技术可以说是武功心法,工具则是一把利剑,有一把好的利剑可以帮助自己更好的完成性能测试工作.在这里简单介绍一下LoadRunner,带大家一起来认识一下这把尚方宝剑. 一.性能测试 ...
随机推荐
- 手动添加导入表修改EXE功能
目标: 改动PE导入表,手工给HelloWorld增加一个功能,就是启动的时候写入一条开机启动项,C:\cmd0000000000000000000000000000.exe 实现方法: 直接在注册相 ...
- 【python】Leetcode每日一题-丑数2
[python]Leetcode每日一题-丑数2 [题目描述] 给你一个整数 n ,请你找出并返回第 n 个 丑数 . 丑数 就是只包含质因数 2.3 和/或 5 的正整数. 示例1: 输入:n = ...
- 初学Golang的笔记
Note 一个module是一个go package的集合,该module用于发布的一个单位. 一般一个go repo仅仅包含一个module,含有一个go.mod文件 每个module路径不仅仅用于 ...
- HellowWorld详解
1.随便新建一个文件夹,存放代码 2.在文件夹中新建一个Java文件 新建一个.txt文本文档-->将扩展名.txt修改为.java 注:如果创建的文本文档没有显示扩展名,则有如下方法: 方法一 ...
- springboot项目部署(war包)
将springboot项目打包成war,并且部署到tomcat.比较麻烦,自己踩的坑也比较多.算了一下,找bug的时间,有两天熬到凌晨2点. 修改pom.xml使得打包成war <groupId ...
- 一些代码小技巧&经典代码
请说明逻辑与(&&)在下边表达式中起到的重要作用 count != 0 && sum/count 答:该表达式使用逻辑与(&&)来确保 sum/coun ...
- PTA 第三章 栈与队列
一.判断题 1.若一个栈的输入序列为1,2,3,--,N,输出序列的第一个元素为i,则第j个输出的元素是j-i-1 (×)解析:应该是不确定的,不能保证数字出栈后不会再入栈 2.所谓" ...
- Centos7 cache/buff过高处理方法
Centos7 cache/buff过高处理方法 kevinxliu关注0人评论36799人阅读2018-07-26 10:09:59 当linux运行久点,会产生很多不必要的cache或者b ...
- linux进阶之计划任务及压缩归档
本节内容 1. at一次性计划任务(atd) at 时间点 command ctrl+d:保存 -l:查看计划任务 atrm:删除计划任务 atq:查看计划任务 2. crontab周期性计划任务(c ...
- JDK、JRE 和 JVM 的区别
JDK JDK 是 Java Development Kit 的缩写,JDK 是 Java 语言的软件开发工具包( SDK ).它提供了Java 开发.编译.运行需要的文件和环境. 如果你是 Java ...
