Java 8早在2014年就出来了,但是我仍发现有很多团队并没有尽量多的去使用其新的特性。可以说Java 8中最大的新特性就是Lambda表达式,这终于给Java世界引入了函数式编程的味道。
在这篇文章中,我想要给大家看一个简单的例子,通过这个具体的例子来展示Java 8和Lambda表达式是如何使你的生活更轻松的。 假如我们想要为一个使用frame或iframe的遗留应用编写一个测试。 Java的API 就是一个很好的例子。使用WebDriver对使用frame和iframe的页面进行自动化测试会比较很棘手,因为当你要操作一个网页元素时,你需要首先切换到它所在的frame中,就像这样:
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 ");
}
}
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,找到将要点击的对应链接,然后返回主窗口。
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());
}
注意到现在的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();
}
现在我们可以使用该方法去执行一段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,执行操作,然后又切换回去。只是这一次该操作返回了一个值:
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;
}
}
}
这个类最初需要多花费一点精力去编写,但是在一个拥有很多frame的应用里,这点努力很快就会显得很值,因为换来了更易读和简介的页面对象(Page Object)和测试代码。