为 appium 测试脚本创建独立的 jar

Create stand-alone jar for appium test scripts

我想为 Appium 测试脚本创建一个不依赖的独立(瘦 jar)jar。

我有一个亚军class

import org.junit.runner.JUnitCore;
import java.net.MalformedURLException;
public class Runner {
    public static void main(String[] args) throws MalformedURLException {
        try{
            JUnitCore.runClasses(Calculator.class);
        }finally {
        }
    }
}

和 我有一个计算器测试 class


import java.net.MalformedURLException;
import java.net.URL;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
//import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;
import io.appium.java_client.MobileElement;
import io.appium.java_client.android.AndroidDriver;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;


public class Calculator {
//    WebDriver driver;
    public AndroidDriver<MobileElement> driver;

    @Before
    public void setUp() throws MalformedURLException{
        DesiredCapabilities caps = new DesiredCapabilities();
        caps.setCapability("udid", "ZH33L2Z6KL"); //Give Device ID of your mobile phone
        caps.setCapability("platformName", "Android");
        caps.setCapability("platformVersion", "6.0.1");
        caps.setCapability("automationName", "uiautomator2");
        caps.setCapability("skipUnlock","true");
        caps.setCapability("appPackage", "com.google.android.calculator");
        caps.setCapability("appActivity", "com.android.calculator2.Calculator");
        caps.setCapability("noReset","true");
        driver = new AndroidDriver<MobileElement>(new URL("http://127.0.0.1:4723/wd/hub"), caps);
    }

    @Test
    public void testCal() throws Exception {
        //locate the Text on the calculator by using By.name()
        WebElement two=driver.findElement(By.id("digit_2"));
        two.click();
        WebElement plus=driver.findElement(By.id("op_add"));
        plus.click();
        WebElement four=driver.findElement(By.id("digit_4"));
        four.click();
        WebElement equalTo=driver.findElement(By.id("eq"));
        equalTo.click();
        //locate the edit box of the calculator by using By.tagName()
        WebElement results=driver.findElement(By.id("result_final"));
        //Check the calculated value on the edit box
        assert results.getText().equals("6"):"Actual value is : "+results.getText()+" did not match with expected value: 6";

    }

    @After
    public void teardown(){
        //close the app
        driver.quit();
    }
}

我已经阅读了一篇关于 ThinJar 和 hollowJar 的文章。

https://dzone.com/articles/the-skinny-on-fat-thin-hollow-and-uber

问题

  1. 如何添加 Gradle 任务(在 intellij 中)以按照文章构建 thin jar?
  2. 如何根据文章添加 Gradle 任务来构建 'Hollow' jar?
  3. 如果我构建一个 'fat' jar,我的 jar 大小是 18mb。如何构建体积更小的瘦 jar 或薄 jar,并单独保留依赖项?
  4. 如何运行在不同PC上创建'skinny'或'thin'jar?

以下文章回答了这些问题: Java 构建自动化第 2 部分:使用 Gradle 创建可执行 jar https://vocon-it.com/2016/11/15/how-to-build-a-lean-jar-file-with-gradle/

相应的示例代码位于: https://github.com/oveits/gradle-tutorial-build-executable-jar/releases/tag/v1.0

最终的 build.gradle 文件如下:


group 'io.cloudgrey.appiumpro'

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
    maven { url "https://jitpack.io" }
    mavenCentral()
    maven { url "https://repo.maven.apache.org/maven2" }
}

dependencies {
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testCompile group: 'log4j', name: 'log4j', version:'1.2.17'
    testCompile group: 'io.appium', name: 'java-client', version: '7.3.0'
    testCompile group: 'org.seleniumhq.selenium', name: 'selenium-java', version: '3.13.0'
    testCompile group: 'org.hamcrest', name: 'hamcrest-library', version: '1.3'
    testCompile group: 'com.eclipsesource.minimal-json', name: 'minimal-json', version: '0.9.5'
    testCompile group: 'org.java-websocket', name: 'Java-WebSocket', version: '1.3.9'
    testCompile group: 'org.zeroturnaround', name: 'zt-exec', version: '1.10'
    testCompile group: 'me.xdrop', name: 'fuzzywuzzy', version: '1.2.0'
    testCompile group: 'io.appium', name: 'mitmproxy-java', version: '1.6.1'
    testCompile group: 'com.applitools', name: 'eyes-appium-java4', version: '4.2.1'
    testCompile group: 'com.github.testdotai', name: 'classifier-client-java', version: '1.0.0'
    testCompile group: 'com.google.guava', name: 'guava', version: '28.1-jre'
}

test {
    outputs.upToDateWhen {false}
    useJUnit()
    testLogging {
        exceptionFormat = 'full'
        showStandardStreams = true
    }
    maxParallelForks = 3
    forkEvery = 1
}

task copyJarsToLib (type: Copy) {
    def toDir = "build/libs/dependency-jars"

    // create directories, if not already done:
    file(toDir).mkdirs()

    // copy jars to lib folder:
    from configurations.compile
    into toDir
}

task marshallClasspathDeps(type: Copy) {
    from sourceSets.test.runtimeClasspath
    // if you need this from the dependencies in the build.gradle then it should be :
    // from sourceSets.main.runtimeClasspath
    include '**/*.class'
    include '**/*.jar'
    exclude { FileTreeElement details ->
        details.file.name.contains('Edition') || details.file.name.contains('kotlin')
    }
    into 'build/libs/dependency-jars'
}

build.dependsOn(marshallClasspathDeps)

task buildJar(dependsOn:classes,type: Jar) {
    // exclude log properties (recommended)
    exclude ("log4j.properties")

    // make jar executable: see 
    manifest {
        attributes (
                'Main-Class': 'com.example.appium.Runner',
                // add classpath to Manifest; see 
                "Class-Path": '. dependency-jars/' + sourceSets.test.runtimeClasspath.collect { it.getName() }.join(' dependency-jars/')
        )
    }
    baseName = "AppiumTests"
    from sourceSets.test.output
    include 'com/example/appium/*.class'

}

// always call copyJarsToLib when building jars:
jar.dependsOn copyJarsToLib
build.dependsOn(buildJar)
// END AUTOMATICALLY INSERTED

可以使用以下方式执行测试用例:

java -jar -Dlog4j.configuration=file:%cd%\log4j.properties build\libs\AppiumTests.jar

您 link 中使用的术语有点奇怪。使用 gradle,总是构建“瘦”jar。它是默认工件,检查 build/libs 文件夹。如果您应用应用程序插件,则在 build/distribution 下也会构建一个分发 zip,它几乎是 fat jar(它是所有相关 jar 的 zip)。但根据定义,您不能将胖 jar 构建成更小的尺寸,也不能简单地 运行 目标主机上的“瘦”或“瘦”jar。

运行 您的应用程序始终只需要三样东西:

  1. 你的代码的编译工件 - 一堆 .class 文件对应于,并且只对应于你编写的代码,通常以 jar 格式打包。这是那个术语中的瘦罐子。如果您不做任何特别的事情,这也是从构建(任何构建系统、maven、gradle 等)中产生的最常见的工件。

  2. 库依赖项 - 所有第 3 方 jar

  3. 运行时间 - 这个通常指的是Java本身。

以上所有内容都需要出现在您即将 运行 您的应用程序的主机上。现在,变得有点复杂的是您实际需要 ship 到该主机的东西(这称为部署):

  • 显然你需要1号寄给主机
  • 通常你会expect/assume主机上预装3号

第2个第3方依赖呢?答案是取决于.

  • 如果可以在目标上预安装(其中一些)这些依赖项 楼主,不用寄。在这种情况下,通常人们会 只需将这些依赖项也称为“运行时间”。例如, Maven是运行时代,也是Gradle时代。这些本身就是 Java 当你写一个 Maven/Gradle 插件时给你的库。你 通常会期望使用您的代码的人拥有 maven/gradle 已经安装了。他们 运行 通过 maven/gradle 您的代码,并且 maven/gradle 将 提供 您的代码所需的依赖项 当运行宁它。这就是为什么在 Maven 中这种依赖关系是 称为“提供”。 Maven 有专门的依赖范围。

  • 如果目标主机上没有提供您的任何依赖项,您 需要发货,期间。

在Gradle中,如果你应用应用程序插件(它会自动应用分发插件),你可以同时拥有你的工件和你的依赖项(不包括java 运行时间) 在单个 zip 中 - 这称为分发。

plugins {
  id 'java'
  id 'application'
}

构建后,您会在 build/distributions 下找到一个 zip 文件。解压后有两个文件夹:bin 和 lib。 在 lib 文件夹中有您的 jar 和所有依赖项 jar。这在技术上不是一个胖罐子,因为它不是一个单一的罐子。但 jar-or-not 只是一种格式。最终,您所追求的只是将您的代码和依赖项传送到目标主机。分发 zip 不会与 jar 混淆,因为 jar 合并不像文件夹合并那么简单。相反,分发 zip 希望您在目标主机上解压缩并调用 bin 文件夹下的脚本来启动应用程序。