122
Java → Kotlin: Tests Made Simple Leonid Rudenko

Java → kotlin: Tests Made Simple

Embed Size (px)

Citation preview

Java → Kotlin: Tests Made Simple—

Leonid Rudenko

_2Why?—

_3Why?—

• Reference http://kotlinlang.org/docs/reference/

_5Useful links—

• Reference http://kotlinlang.org/docs/reference/

• List of Kotlin resources https://kotlin.link/

_6Useful links—

• Reference http://kotlinlang.org/docs/reference/

• List of Kotlin resources https://kotlin.link/

• Try Kotlin online http://try.kotl.in/

_7Useful links—

• Reference http://kotlinlang.org/docs/reference/

• List of Kotlin resources https://kotlin.link/

• Try Kotlin online http://try.kotl.in/

• Slack https://kotlinlang.slack.com/

_8Useful links—

• Problems Kotlin solves• Kotlin & Frameworks• Demo

_9What’s going on here—

• Problems Kotlin solves• Kotlin & Frameworks• Demo

_10What’s going on here—

// JavaMap<Integer, Credentials> users = new HashMap<>();users.put(1, new Credentials("vasya", "123456"));users.put(2, new Credentials("johny", "qwerty"));users.put(3, new Credentials("admin", "admin"));

List<Integer> responseCodes = new ArrayList<>();responseCodes.add(200);responseCodes.add(302);

_111. Problem: Collections in Java—

// JavaList<String> classpath = new ArrayList<>();classpath.add(getBundleJarPath());classpath.addAll(getPluginsPath());

_121. allure-framework/allure1 https://git.io/v9RQZ—

// JavaList<String> classpath = new ArrayList<>();classpath.add(getBundleJarPath());classpath.addAll(getPluginsPath());

// Kotlinval classpath = listOf(getBundleJarPath(), getPluginsPath())

_131. allure-framework/allure1 https://git.io/v9RQZ—

// JavaList<String> classpath = new ArrayList<>();classpath.add(getBundleJarPath());classpath.addAll(getPluginsPath());

// Kotlinval classpath = listOf(getBundleJarPath(), getPluginsPath())

List<String>

_141. allure-framework/allure1 https://git.io/v9RQZ—

// JavaList<String> classpath = new ArrayList<>();classpath.add(getBundleJarPath());classpath.addAll(getPluginsPath());

// Kotlinval classpath = listOf(getBundleJarPath(), getPluginsPath())

List<String>

classpath.add("/usr/bin")

_151. allure-framework/allure1 https://git.io/v9RQZ—

// JavaMap<TestItemIssueType, List<StatisticSubType>> types = new HashMap<>()

_161. reportportal/service-api https://git.io/v9RQy—

// JavaMap<TestItemIssueType, List<StatisticSubType>> types = new HashMap<>() {{

}};

_171. reportportal/service-api https://git.io/v9RQy—

// JavaMap<TestItemIssueType, List<StatisticSubType>> types = new HashMap<>() {{

put(AUTOMATION_BUG, Lists.newArrayList(new StatisticSubType(AUTOMATION_BUG.getLocator(),

AUTOMATION_BUG.getValue(), "Automation Bug", "AB", "#f5d752")));

}};

_181. reportportal/service-api https://git.io/v9RQy—

// JavaMap<TestItemIssueType, List<StatisticSubType>> types = new HashMap<>() {{

put(AUTOMATION_BUG, Lists.newArrayList(new StatisticSubType(AUTOMATION_BUG.getLocator(),

AUTOMATION_BUG.getValue(), "Automation Bug", "AB", "#f5d752")));

...

put(TO_INVESTIGATE, Lists.newArrayList(new StatisticSubType(TO_INVESTIGATE.getLocator(),

TO_INVESTIGATE.getValue(), "To Investigate", "TI", "#ffa500")));}};

_191. reportportal/service-api https://git.io/v9RQy—

// Javaval types = mapOf(

AUTOMATION_BUG to listOf(StatisticSubType(AUTOMATION_BUG.locator, AUTOMATION_BUG.value, "Automation Bug", "AB", "#f5d752")),

...

TO_INVESTIGATE to listOf(StatisticSubType(TO_INVESTIGATE.locator, TO_INVESTIGATE.value, "To Investigate", "TI", "#ffa500")))

_201. reportportal/service-api https://git.io/v9RQy—

_211. Collections: Traversing a map—// Javafor (Map.Entry<Integer, Credentials> pair : users.entrySet()) {

System.out.println(pair.getKey() + "->" + pair.getValue());}

// Kotlinusers.forEach { k, v ->

println("$k->$v")}

_221. allure-framework/allure1 https://git.io/v9R7E—// JavaList<String> names = new ArrayList<>();for (File file : files) {

TestSuiteResult result= JAXB.unmarshal(file, TestSuiteResult.class);

names.add(result.getName());}

_231. allure-framework/allure1 https://git.io/v9R7E—// JavaList<String> names = new ArrayList<>();for (File file : files) {

TestSuiteResult result= JAXB.unmarshal(file, TestSuiteResult.class);

names.add(result.getName());}

// Kotlinval names = files.map {

JAXB.unmarshal(it, TestSuiteResult::class.java).name}

• Java 7

• Java 8 (Stream API)

• Groovy

_241. Collections: Java & Groovy—

_252. Problem: framework can’t do what you need it to do—// Javapublic static void waitForElement(HtmlElement element, longtimeout) {

...}

waitForElement(link, 10);link.click();

_26

_272. Problem: framework can’t do what you need it to do—

_282. Solution: Extension functions—// Kotlinfun <T : HtmlElement> T.waitForIt(timeout: Long = 5): T {

...return this

}

link.waitForIt().click()

loginForm.waitForIt(10).guestButton.waitForIt().click()

_292. Solution: Extension functions—// Kotlinfun <T : HtmlElement> T.waitForIt(timeout: Long = 5): T {

...return this

}

link.waitForIt().click()

loginForm.waitForIt(10).guestButton.waitForIt().click()

_302. Solution: Extension functions—// Kotlinfun <T : HtmlElement> T.waitForIt(timeout: Long = 5): T {

...return this

}

link.waitForIt().click()

loginForm.waitForIt(10).guestButton.waitForIt().click()

_312. Solution: Extension functions—// Kotlinfun <T : HtmlElement> T.waitForIt(timeout: Long = 5): T {

...return this

}

link.waitForIt().click()

loginForm.waitForIt(10).guestButton.waitForIt().click()

_322. Solution: Extension functions—

• Java (Lombok https://projectlombok.org/)

• Groovy (Extension Modules)

_332. Extension functions: Java & Groovy—

// Javapublic class Credentials {

private final String username;private final String password;

}

_343. Problem: small classes are not small—

// Javapublic class Credentials {

private final String username;private final String password;

public Credentials(String username, String password) {this.username = username;this.password = password;

}}

_353. Problem: small classes are not small—

// Javapublic class Credentials {

private final String username;private final String password;

public Credentials(String username, String password) {this.username = username;this.password = password;

}

public String getUsername() { return username; }public String getPassword() { return password; }

}

_363. Problem: small classes are not small—

// Javapublic class Credentials {

private final String username;private final String password;

public Credentials(String username, String password) {this.username = username;this.password = password;

}

public String getUsername() { return username; }public String getPassword() { return password; }

@Overridepublic String toString() {

return username + '/' + password;}

}

_373. Problem: small classes are not small—

// Javapublic class Credentials {

private final String username;private final String password;

public Credentials(String username, String password) {this.username = username;this.password = password;

}

public String getUsername() { return username; }public String getPassword() { return password; }

@Overridepublic String toString() {

return username + '/' + password;}

@Overridepublic boolean equals(Object o) {

if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Credentials that = (Credentials) o;if (username != null ? !username.equals(that.username) : that.username != null) return false;return password != null ? password.equals(that.password) : that.password == null;

}

@Overridepublic int hashCode() {

int result = username != null ? username.hashCode() : 0;return 31 * result + (password != null ? password.hashCode() : 0);

}}

_383. Problem: small classes are not small—

// Javapublic class Credentials {

private final String username;private final String password;

public Credentials(String username, String password) {this.username = username;this.password = password;

}

public String getUsername() { return username; }public String getPassword() { return password; }

@Overridepublic String toString() {

return username + '/' + password;}

@Overridepublic boolean equals(Object o) {

if (this == o) return true;if (o == null || getClass() != o.getClass()) return false;Credentials that = (Credentials) o;if (username != null ? !username.equals(that.username) : that.username != null) return false;return password != null ? password.equals(that.password) : that.password == null;

}

@Overridepublic int hashCode() {

int result = username != null ? username.hashCode() : 0;return 31 * result + (password != null ? password.hashCode() : 0);

}}

_393. Problem: 27 lines—

_403. griddynamics/jagger https://git.io/v9035—

_413. Solution: Kotlin data classes—// Kotlindata class Credentials(val username: String, val password: String)

val creds = Credentials("a", "b")println(creds.username) // acreds.username = "you can't do that"println(creds) // Credentials(username=a, password=b)println(creds == Credentials("a", "b")) // true

• Java (Lombok https://projectlombok.org/)

• Groovy (@groovy.transform.Canonical)

_423. Data classes: Java & Groovy—

// Javadriver.findElement("button").click();

_434. Problem: steps in Allure report—

// Java@Step("Click the button")public void clickButton() {

driver.findElement("button").click();}

clickButton();

_444. Problem: steps in Allure report—

// Java 8@Step("{0}")public void step(String title, Runnable code) {

code.run();}

step("Click the button", () -> {driver.findElement("button").click();

});

_454. Solution: steps in Allure report—

// Kotlin@Step("{0}")fun step(title: String, code: () -> Any) = code()

step("Click the button") {driver.findElement("button").click()

}

_464. Solution: steps in Allure report—

// Java 8step("Click the button", () -> {

// your code here});

// Kotlinstep("Click the button") {

// your code here}

_474. Solution: just compare—

• Java 7

• Java 8

• Groovy

_484. Steps in Allure report: Java & Groovy—

// JavaURLDecoder.decode(param, "UTF-8");

_495. Problem: checked exceptions—

// Java: String to md5try {

...} catch (NoSuchAlgorithmException ex) {

throw new RuntimeException("Unable apply MD5 algorithm for password hashing: ", ex);

} catch (UnsupportedEncodingException unsEx) {throw new RuntimeException(

"Unable apply UTF-8 encoding for password string: ", unsEx);}

_505. reportportal/service-api https://git.io/v9RF5—

// Javatry {

screenshotUrl = new URL(screenshotUrl).toExternalForm();} catch (MalformedURLException ignore) { }

_515. codeborne/selenide https://git.io/v9Rbe—

// Javatry {

screenshotUrl = new URL(screenshotUrl).toExternalForm();} catch (MalformedURLException ignore) { }

// KotlinscreenshotUrl = URL(screenshotUrl).toExternalForm();

_525. Solution: no checked exceptions—

• Java

• Groovy

_535. Checked exceptions: Java & Groovy—

// JavaObject errors

= executeJavaScript("return window._selenide_jsErrors");

if (errors instanceof List) {return errorsFromList((List<Object>) errors);

}

_546. Problem: unchecked cast https://git.io/v9Ek1—

// JavaObject errors

= executeJavaScript("return window._selenide_jsErrors");

if (errors instanceof List) {return errorsFromList((List<Object>) errors);

}

_556. Problem: unchecked cast https://git.io/v9Ek1—

// JavaObject errors

= executeJavaScript("return window._selenide_jsErrors");

if (errors instanceof List) {return errorsFromList(((List<?>) errors)

.stream()

.filter(Object.class::isInstance)

.map(Object.class::cast)

.collect(toList()));

}

_566. Problem: unchecked cast https://git.io/v9Ek1—

// Kotlinval errors

= executeJavaScript("return window._selenide_jsErrors");

if (errors is List<*>) {return errorsFromList(errors.filterIsInstance<Object>())

}

_576. Solution: Kotlin smart cast—

// Kotlinval errors

= executeJavaScript("return window._selenide_jsErrors");

if (errors is List<*>) {return errorsFromList(errors.filterIsInstance<Object>())

}

_586. Solution: Kotlin smart cast—

• Java

• Groovy

_596. Unchecked cast: Java & Groovy—

_607. Problem:— Java is verbose

// JavaactualText.equals(expectedText)

// KotlinactualText == expectedText

_617. Solution: Kotlin syntactic sugar—

// JavaString s = "date: " + date + " and author: " + USER.getName();String s =

format("date: %s and author: %s", date, USER.getName());

// Kotlinval s = "date: $date and author: ${USER.name}"

_627. Solution: String templates—

// Javafor (int i = 0; i <= 10; i += 2)

// Kotlinfor (i in 0..10 step 2)

_637. Solution: ranges—

// Java!list.isEmpty()

// Kotlinlist.isNotEmpty()

_647. Solution: Kotlin rich standard library—

• Java

• Groovy

_657. Verbosity: Java & Groovy—

8. Problem:—

var username: Stringusername = null // compilation error

_678. Solution: Null safety—

var username: Stringusername = null // compilation error

var username: String?username = null // ok

_688. Null safety—

var username: String // non-nullable Stringusername = null

var username: String? // nullable Stringusername = null

_698. Null safety—

var username: String? = "Vasya"var count = username.length // compilation error

_708. Null safety: safe call—

var username: String? = "Vasya"var count = username?.length // ok, count is 5

username = nullvar count = username?.length // ok, count is null

_718. Null safety: safe call—

// Javausername != null ? username : "Guest"

_728. Null safety: Elvis operator—

// Javausername != null ? username : "Guest"

// Kotlinusername ?: "Guest"

var images = findAllImagesOnPage()?: throw AssertionError("No images!")

_738. Null safety: Elvis operator—

?:

• Null safety: Java (Optional<T> in Java8), Groovy• Safe call: Java, Groovy• Elvis operator: Java, Groovy

_748. Null safety: Java & Groovy—

_75

1 2 3 4 5 6 7 8

Java 7 − − − − − − − −Java 8 ± − − + − − − −Groovy + ± + + + − + −Kotlin + + + + + + + +

Java vs Groovy vs Kotlin—

• Not statically typed: runtime bugs

• Not statically typed: performance

• Not statically typed: IDE support

• No null safety

_76Why not Groovy?—

• Problems Kotlin solves• Kotlin & Frameworks• Demo

_77What’s going on here—

Kotlin ~ Java—// Kotlinvar counter: Int = 0

// Javaprivate int counter = 0;

public final int getCounter() {return counter;

}

public final void setCounter(int newCounter) {counter = newCounter;

}

_78

1. JUnit 4—

@Before fun `start browser`() { ... }

@Test fun `test name`() { ... }

@Ignore("ISSUE-9000")@Test fun `ignored test`() { ... }

@After fun `quit browser`() { ... }

_79

_80

1. JUnit 4: @Rule—

_81

// Kotlin@Rule val tempFolder = TemporaryFolder()

1. JUnit 4: @Rule—

_82

// Kotlin@Rule val tempFolder = TemporaryFolder()

org.junit.internal.runners.rules.ValidationError: The @Rule 'tempFolder' must be public.

1. JUnit 4: @Rule—

_83

// Kotlin@Rule val tempFolder = TemporaryFolder()

// Java@Ruleprivate final TemporaryFolder tempFolder = new TemporaryFolder();

public final TemporaryFolder getTempFolder() {return tempFolder;

}

1. JUnit 4: @Rule solution #1—

_84

// Kotlin@JvmField @Rule val tempFolder = TemporaryFolder()

// Java@Rulepublic final TemporaryFolder tempFolder = new TemporaryFolder();

1. JUnit 4: @Rule solution #2—

_85

// Kotlin@get:Rule val tempFolder = TemporaryFolder()

// Javaprivate final TemporaryFolder tempFolder = new TemporaryFolder();

@Rulepublic final TemporaryFolder getTempFolder() {

return tempFolder;}

1. JUnit 4: @Parameters—

_86

// Kotlin@RunWith(Parameterized::class)class ParameterizedTest {

@Parameters(name = "{0}")fun data(): Collection<Array<String>> = asList(

arrayOf("firefox User-Agent"),arrayOf("chrome User-Agent")

)

@Parameter lateinit var userAgent: String}

1. JUnit 4: @Parameters—

_87

// Kotlin@RunWith(Parameterized::class)class ParameterizedTest {

@Parameters(name = "{0}")fun data(): Collection<Array<String>> = asList(

arrayOf("firefox User-Agent"),arrayOf("chrome User-Agent")

)@Parameter lateinit var userAgent: String

}

java.lang.Exception: No public static parameters method on class com.jetbrains.ParameterizedTest

Companion object—// Kotlinclass MyClass {

companion object {fun looksLikeStatic() { ... }

}

}

MyClass.looksLikeStatic()

_88

1. JUnit 4: @Parameters—

_89

// Kotlin@RunWith(Parameterized::class)class ParameterizedTest {

companion object {@Parameters(name = "{0}")fun data(): Collection<Array<String>> = asList(

arrayOf("firefox User-Agent"),arrayOf("chrome User-Agent")

)}

@Parameter lateinit var userAgent: String}

1. JUnit 4: @Parameters—

_90

// Kotlin@RunWith(Parameterized::class)class ParameterizedTest {

companion object {@Parameters(name = "{0}")fun data(): Collection<Array<String>> = asList(

arrayOf("firefox User-Agent"),arrayOf("chrome User-Agent")

)}@Parameter lateinit var userAgent: String

}java.lang.Exception: No public static parameters method on class com.jetbrains.ParameterizedTest

1. JUnit 4: @Parameters solution—

_91

// Kotlin@RunWith(Parameterized::class)class ParameterizedTest {

companion object {@Parameters(name = "{0}") @JvmStaticfun data(): Collection<Array<String>> = asList(

arrayOf("firefox User-Agent"),arrayOf("chrome User-Agent")

)}

@Parameter lateinit var userAgent: String}

1. JUnit 4: @Parameters solution—

_92

// Java@RunWith(Parameterized.class)class ParameterizedTest {public final static class Companion {public Collection<String[]> data() {return asList(...);

}}

public final static Companion Companion = new Companion();

@Parameters(name = "{0}")public final static Collection<String[]> data() {return Companion.data();

}}

1. JUnit 4: @Parameters solution—

_93

// Java@RunWith(Parameterized.class)class ParameterizedTest {public final static class Companion {public Collection<String[]> data() {return asList(...);

}}

public final static Companion Companion = new Companion();

@Parameters(name = "{0}")public final static Collection<String[]> data() {return Companion.data();

}}

1. JUnit 4: @Parameters solution—

_94

// Java@RunWith(Parameterized.class)class ParameterizedTest {public final static class Companion {public Collection<String[]> data() {return asList(...);

}}

public final static Companion Companion = new Companion();

@Parameters(name = "{0}")public final static Collection<String[]> data() {return Companion.data();

}}

1. JUnit 4: @Parameters solution—

_95

// Java@RunWith(Parameterized.class)class ParameterizedTest {public final static class Companion {public Collection<String[]> data() {return asList(...);

}}

public final static Companion Companion = new Companion();

@Parameters(name = "{0}")public final static Collection<String[]> data() {return Companion.data();

}}

2. HtmlElements 1.*: element—

_96

@FindBy(css = "form[name='LoginForm']")class LoginForm : HtmlElement() {

@FindBy(css = "#username")lateinit var usernameInput: HtmlElement

@FindBy(css = "#password")lateinit var passwordInput: HtmlElement

@FindBy(xpath = ".//button")lateinit var loginButton: HtmlElement

fun login(username: String, password: String) {usernameInput.sendKeys(username)passwordInput.sendKeys(password)loginButton.click()

} }

2. HtmlElements 1.*: page object—

_97

class Page(val driver: WebDriver) {init {PageFactory.initElements(

HtmlElementDecorator(HtmlElementLocatorFactory(driver)), this)

}

lateinit var loginForm: LoginForm

@FindBy(css = ".dashboard-buttons_add")lateinit var addWidgetButton: HtmlElement

}

_98

2. HtmlElements 1.*: collection of elements—

_99

// Kotlin@FindBy(css = "a")lateinit var links: List<HtmlElement>

...

links.forEach { ... }

2. HtmlElements 1.*: collection of elements—

_100

// Kotlin@FindBy(css = "a")lateinit var links: List<HtmlElement>

...

links.forEach { ... }

kotlin.UninitializedPropertyAccessException

2. HtmlElements 1.*: collection of elements—

_101

// Kotlin@FindBy(css = "a")lateinit var links: List<HtmlElement>

java.util.List<? extends ru.yandex.qatools.htmlelements.element.HtmlElement>

2. HtmlElements 1.*: collection of elements—

_102

// Kotlin@FindBy(css = "a")lateinit var links: List<@JvmSuppressWildcards HtmlElement>

java.util.List<ru.yandex.qatools.htmlelements.element.HtmlElement>

• https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html#variant-generics

3. Allure—

_103

// Kotlin@Title("This is title")@Description("This is description")@Test fun test() { ... }

@Step("Change Home URL to {0}")fun changeHomeURL(homeURL: String) { ... }

@Attachment(value = "{0}", type = "text/plain")fun attachText(name: String = "text", text: String?) = text

_104

3. Allure: org.aspectj:aspectjweaver:1.8.10—

_105

// Kotlinlinks.forEach {

println(it.text)}

3. Allure: org.aspectj:aspectjweaver:1.8.10—

_106

// Kotlinlinks.forEach {

println(it.text)}

java.lang.ClassFormatError: Invalid index 6 in LocalVariableTable

3. Allure: org.aspectj:aspectjweaver:1.8.10—

_107

// Kotlinlinks.forEach {

println(it.text)}

java.lang.ClassFormatError: Invalid index 6 in LocalVariableTable

• https://bugs.eclipse.org/bugs/show_bug.cgi?id=500796

4. Selenide: $ is reserved—

_108

// Kotlinimport com.codeborne.selenide.Selenide.$

Error: Kotlin: Qualified name must be a '.'-separated identifier list

4. Selenide: $ is reserved—

_109

// Kotlinimport com.codeborne.selenide.Selenide.`$`import com.codeborne.selenide.Selenide.`$$`

...

`$`("a")`$$`("a")

_110

5. Selenium WebDriver—

_111

// Kotlinval driver = RemoteWebDriver(

URL("http://grid.company.com:4444/wd/hub"),DesiredCapabilities.chrome())

driver.manage().window().size = Dimension(800, 600)driver.get("https://heisenbug-piter.ru")val link = driver.findElement(By.cssSelector("a.navbar-brand"))val text = link.textval clazz = link.getAttribute("class")Actions(driver).moveToElement(link).perform()(driver as TakesScreenshot).getScreenshotAs(OutputType.BYTES)driver.quit()

_112

6. REST Assured—

_113

// Kotlinfun createIssue(issue: Issue): Issue = given()

.baseUri("http://host:8080")

.header("Authorization", "*token*")

.contentType("application/json")

.accept("application/json")

.queryParams(mapOf("fields" to"id,project(id,shortName),numberInProject"))

.with().body(issue)

.post("/api/issues")

.`as`(Issue::class.java)

• Problems Kotlin solves• Kotlin & Frameworks• Demo

_114What’s going on here—

_115Example web tests project in Kotlin—

gradle-docker-plugin

_116Example web tests project in Kotlin—

gradle-docker-plugin

JUnit,HtmlElements

_117Example web tests project in Kotlin—

gradle-docker-plugin

JUnit,HtmlElements

_118

• Kotlin + { Gradle, JUnit, Selenium, Html Elements, Allure } = OK

https://github.com/leonsabr/web-tests-in-kotlin-demo—

_119

• Kotlin + { Gradle, JUnit, Selenium, Html Elements, Allure }• Java interoperability

https://github.com/leonsabr/web-tests-in-kotlin-demo—

_120https://github.com/leonsabr/web-tests-in-kotlin-demo—

_121

java kotlin ∆main 675 434 35,7%test 89 84 5,6%total 764 518 32,2%

• Kotlin + { Gradle, JUnit, Selenium, Html Elements, Allure }• Java interoperability• Conciseness (lines of code)

https://github.com/leonsabr/web-tests-in-kotlin-demo—

Thank you for your attention—

jetbrains.com

[email protected]@leonsabr