文档结构  
翻译进度:已翻译     翻译赏金:0 元 (?)    ¥ 我要打赏

Java 8早在2014年就出来了,但是我仍发现有很多团队并没有尽量多的去使用其新的特性。可以说Java 8中最大的新特性就是Lambda表达式,这终于给Java世界引入了函数式编程的味道。

在这篇文章中,我想要给大家看一个简单的例子,通过这个具体的例子来展示Java 8和Lambda表达式是如何使你的生活更轻松的。 假如我们想要为一个使用frame或iframe的遗留应用编写一个测试。 Java的API 就是一个很好的例子。使用WebDriver对使用frame和iframe的页面进行自动化测试会比较很棘手,因为当你要操作一个网页元素时,你需要首先切换到它所在的frame中,就像这样:

第 1 段(可获 1.63 积分)
driver.switchTo().frame("MyFrame");
button.click();
driver.switchTo().defaultContent();

让我们来看看在Java 8里如何做会更优雅些。设想我们需要写一个测试来检查什么时候从Packages列表里选择一个包,该列表即在frame的左下方出现的正确的类和接口的列表。我们可以写一个相当简单的Serenity BDD(译者注:一个自动化验收测试报告的类库 ,之前的名字是Thucydides)测试,如下所示:

@RunWith(SerenityRunner.class)
public class WhenConsultingPackageDetails {

    @Managed WebDriver driver;

    JavaAPIDocs apiDocs;

    @Test
    public void should_be_able_to_view_the_classes_for_a_given_package() {

        // Given
        apiDocs.open();

        // When
        apiDocs.selectAPackage("java.applet");

        // Then
        assertThat(apiDocs.getClassesAndInterfaces())
                          .contains("AppletContext",
                                    "AppletStub",
                                    "AudioClip",
                                    "Applet");
    }
}
第 2 段(可获 0.75 积分)

JavaAPIDocs类是一个Serenity Page Object,这个Serenity将会为我们初始化。它的代码如下所示:

@DefaultUrl("https://docs.oracle.com/javase/7/docs/api/")
public class JavaAPIDocs extends PageObject {

    @FindBy(tagName = "li")
    private List<WebElementFacade> packageNames;

    public JavaAPIDocs(WebDriver driver) {
        super(driver);
    }

    public void selectAPackage(final String packageName) {
        ...
    }

    public List<String> getClassesAndInterfaces() {
        ...
    }
}

现在假设我们想要实现selectAPackage方法。使用Java 7时,我们需要首先定位至该frame,找到将要点击的对应链接,然后返回主窗口。

第 3 段(可获 0.66 积分)
public void selectAPackage(final String packageName) {
    getDriver().switchTo().frame(("packageListFrame")
    find(By.linkText(packageName)).click();
    getDriver().switchTo().defaultContent();
}

而在Java 8里,我们则可以创建一个类,用于在frame之间切换,然后将要执行的操作(以Lambda表达式)传入。 假设我们将该类命名为InFrame。 我们的方法看起来是这样的:

private InFrame inAFrame;

public JavaAPIDocs(WebDriver driver) {
    super(driver);
    inAFrame = new InFrame(driver);
}

public void selectAPackage(final String packageName) {
    inAFrame.called("packageListFrame")
            .attemptTo(() -> find(By.linkText(packageName)).click());
}
第 4 段(可获 0.64 积分)

注意到现在的selectAPackage()方法变得有多易读了吗?奇迹就发生在InFrame类里,它包含了一个attemptTo方法,其任务是要执行以参数形式传给它的Lambda表达式:

public void attemptTo(UIPerformable performable) {
    driver.switchTo().frame(iframeNameOrId);
    performable.perform();
    driver.switchTo().defaultContent();
}

UIPerformable 是一件简单的函数式接口,我们可以将一个Lambda表达式传递给它:

@FunctionalInterface
public interface UIPerformable {
    void perform();
}
第 5 段(可获 0.69 积分)

现在我们可以使用该方法去执行一段WebDriver代码,可随我们的需要可简可繁。我们就能保证在操作开始之前能够切换到正确的frame,并且在完成后再切换回来。

getClassesAndInterfaces() 方法则看起来是相似的:

public List<String> getClassesAndInterfaces() {
    return inAFrame.called("packageFrame").retrieve(() ->
            packageNames.stream()
                    .map(WebElement::getText)
                    .collect(Collectors.toList())
    );
}

在这里,我们使用retrieve() 方法,该方法接受了一个Supplier, 这是标准的Java 8函数式接口之一。跟之前的例子一样,我们切换到该frame,执行操作,然后又切换回去。只是这一次该操作返回了一个值:

第 6 段(可获 1.14 积分)
public <T> T retrieve(Supplier<T> performable) {
    driver.switchTo().frame(iframeNameOrId);
    T result = performable.get();
    driver.switchTo().defaultContent();
    return result;
}

我们也可以使用Java 8的streams功能将由WebDriver返回的web元素的列表转换为字符串列表,这是Java 8的另外一个不错的特性。

完整的InFrame 类如下所示:

public class InFrame {
    private final WebDriver driver;

    public InFrame(WebDriver driver) {
        this.driver = driver;
    }

    public InstantiatedIFrameContainer called(String iframeNameOrId) {
        return new InstantiatedIFrameContainer(driver, iframeNameOrId);
    }

    public class InstantiatedIFrameContainer {
        private final WebDriver driver;
        private final String iframeNameOrId;

        public InstantiatedIFrameContainer(WebDriver driver, 
                                           String iframeNameOrId) {
            this.driver = driver;
            this.iframeNameOrId = iframeNameOrId;
        }

        public void attemptTo(UIPerformable performable) {
            driver.switchTo().frame(iframeNameOrId);
            performable.perform();
            driver.switchTo().defaultContent();
        }

        public <T> T retrieve(Supplier<T> performable) {
            driver.switchTo().frame(iframeNameOrId);
            T result = performable.get();
            driver.switchTo().defaultContent();
            return result;
        }
    }
}
第 7 段(可获 0.43 积分)

这个类最初需要多花费一点精力去编写,但是在一个拥有很多frame的应用里,这点努力很快就会显得很值,因为换来了更易读和简介的页面对象(Page Object)和测试代码。

第 8 段(可获 0.48 积分)

文章评论