CodeFest 2018. Алексей Виноградов (Vinogradov IT-Consulting) — KISS-Driven Test Automation - по лезвию бритвы Оккама

CodeFest 2018. Алексей Виноградов (Vinogradov IT-Consulting) — KISS-Driven Test Automation - по лезвию бритвы Оккама

Посмотрите выступление Алексея: https://2018.codefest.ru/lecture/1251/

KISS - Keep It Simple Stupid. В данном докладе Алексей расскажет вам про KISS-driven подход в автоматизации тестирования, и объяснит, почему некоторые популярные приёмы программирования могут стать ненужными или даже вредными в коде ваших тестов. Также, будут приведены общие примеры того, как KISS-тесты помогут улучшить читаемость и поддерживаемость тестировочного кода. После доклада каждый зритель получит бритву Оккама с тремя запасными лезвиями в подарок.

16b6c87229eaf58768d25ed7b2bbbf52?s=128

CodeFest

April 05, 2018
Tweet

Transcript

  1. KISS-Driven Test Automation по лезвию бритвы Оккама Alexei Vinogradov Master

    class
  2. Alexei Vinogradov
 IT-Kонсультант
 
 тестирование, управление тестированием, автоматизация в тестировании,

    коучинг В области IT c 1998, докладчик c 2014 года Студент-практикант -> Программист -> Тестировщик ->…
  3. http://radio-qa.com

  4. None
  5. Реклама Слака

  6. KISS-Driven Test Automation

  7. None
  8. ПРОСТОТА это круто!!!

  9. None
  10. The best since the 14. century

  11. "Бритва Оккама" Уильям Оккам
 1285 - 1349, Англия Не следует

    привлекать
 новые сущности без крайней
 на то необходимости
  12. «Что может быть сделано на основе меньшего числа [предположений], не

    следует делать, исходя из большего» Не аксиома, а предложение X <- A, B, C X <- A, B, C, D
  13. KISS-Driven Test Automation
 (KDTA)

  14. 1. Начинай с KISS 2. Продолжай KISS 3. Отказывайся от

    KISS только по хорошим причинам Три принципа KDTA
  15. KISS over SOLID

  16. KISS over Design Patterns

  17. KISS over DRY

  18. Make KISS not over-engineering

  19. Слайды! Примеры

  20. Примеры: UI Test Automation

  21. Не надо писать фреймворки*, надо писать тесты *на работе

  22. http://selenide.org Selenium inside UI Testing Framework

  23. Java

  24. Типизированные UI элементы

  25. Button Link Image Checkbox

  26. None
  27. None
  28. PageObject public class OtherPageObject{ Text description=new Text(By.cssSelector("#text")); Button submit=new Button(By.cssSelector("#button"));

    Label productName=new Label(By.cssSelector("#label")); Link followMe=new Link(By.cssSelector("#link")); TextField passwordField=new TextField(By.cssSelector("#textfield")); public void useElements(){ description.getText(); submit.click(); productName.shouldBe(visible); followMe.click(); passwordField.setValue("password"); } }
  29. PageObject public class OtherPageObject{ Text description=new Text(By.cssSelector("#text")); Button submit=new Button(By.cssSelector("#button"));

    Label productName=new Label(By.cssSelector("#label")); Link followMe=new Link(By.cssSelector("#link")); TextField passwordField=new TextField(By.cssSelector("#textfield")); public void useElements(){ description.getText(); submit.click(); productName.shouldBe(visible); followMe.click(); passwordField.setValue("password"); } }
  30. KISS PageObject public class KISSPageObject { SelenideElement description=$("#text"), submitBtn=$("#button"), productName=$("#label"),

    followMe=$("#link"), passwordField=$("#textfield"); public void useElements(){ description.getText(); submitBtn.click(); productName.shouldBe(visible); followMe.click(); passwordField.setValue("password"); } }
  31. getter/setter

  32. None
  33. PageObject public class OtherPageObject { private SelenideElement productName=$("#label"); public SelenideElement

    getProductName() { return productName; } private SelenideElement submitBtn=$("#button"); private SelenideElement followMe=$("#link"); public void useElements(){ submitBtn.click(); followMe.click(); } }
  34. Test public class OtherTest { @Test public void testProductName(){ new

    OtherPageObject().getProductName().shouldBe(visible); } }
  35. PageObject public class OtherPageObject { private SelenideElement productName=$("#label"); public SelenideElement

    getProductName() { return productName; } private SelenideElement submitBtn=$("#button"); private SelenideElement followMe=$("#link"); public void useElements(){ submitBtn.click(); followMe.click(); } }
  36. KISS PageObject public class KISSPageObject { public SelenideElement productName=$("#label"); private

    SelenideElement submitBtn=$("#button"); private SelenideElement followMe=$("#link"); public void useElements(){ submitBtn.click(); followMe.click(); } }
  37. Test public class KISSTest { @Test public void testProductName(){ new

    OtherPageObject().productName.shouldBe(visible); } }
  38. Lombok PageObject public class LombokPageObject { @Getter private SelenideElement productName=$("#label");

    //Lombok 
 private SelenideElement submitBtn=$("#button"); private SelenideElement followMe=$("#link"); public void useElements(){ submitBtn.click(); followMe.click(); } }
  39. Lombok PageObject public class LombokPageObject { @Getter private SelenideElement productName=$("#label");

    //Lombok 
 private SelenideElement submitBtn=$("#button"); private SelenideElement followMe=$("#link"); public void useElements(){ submitBtn.click(); followMe.click(); } }
  40. @FindBy & PageFactory

  41. PageObject public class OtherPageObject { @FindBy(css = "#text") SelenideElement description;

    @FindBy(css = "#button") WebElement submit; @FindBy(css = "#label") SelenideElement productName; @FindBy(css = "#link") WebElement followMe; public void useElements(){ description.getText(); submit.click(); productName.shouldBe(visible); followMe.click(); } }
  42. PageObject public class OtherPageObject { public OtherPageObject(){ PageFactory.initElements(WebDriverRunner.getWebDriver(), OtherPageObject.class); }

    @FindBy(css = "#text") SelenideElement description; @FindBy(css = "#button") WebElement submit; @FindBy(css = "#label") SelenideElement productName; @FindBy(css = "#link") WebElement followMe; public void useElements(){ description.getText(); submit.click(); productName.shouldBe(visible); followMe.click(); } }
  43. KISS PageObject public class KISSPageObject { SelenideElement description=$("#text"), submitBtn=$("#button"), productName=$("#label"),

    followMe=$("#link"); public void useElements(){ description.getText(); submitBtn.click(); productName.shouldBe(visible); followMe.click(); } }
  44. Selenium PageObject // will not work, because Selenium doesn't use

    Lazy WebElement evaluation public class SeleniumPageObjectNotWorking { WebDriver driver=WebDriverRunner.getWebDriver(); // search will start immediately during class/object initialization WebElement description=driver.findElement(By.cssSelector("#text")), submitBtn=driver.findElement(By.cssSelector("#text")), productName=driver.findElement(By.cssSelector("#text")), followMe=driver.findElement(By.cssSelector("#link")); public void useElements(){ description.getText(); submitBtn.click(); productName.getText(); followMe.click(); } }
  45. Selenium PageObject // will work public class SeleniumPageObjectWorking { WebDriver

    driver=WebDriverRunner.getWebDriver(); By description=By.cssSelector("#text"), submitBtn=By.cssSelector("#button"), productName=By.cssSelector("#label"), followMe=By.cssSelector("#link"); public void useElements(){ driver.findElement(description).getText(); driver.findElement(submitBtn).click(); driver.findElement(productName).getText(); driver.findElement(followMe).click(); } }
  46. Fluent PageObjects

  47. Test public class OtherTest { @Test public void testProductName(){ new

    OtherSearchPage().search() .showDetails() .productName.shouldBe(visible); } }
  48. public class OtherSearchPage { private SelenideElement search =$("#button"); public OtherDetailsPage

    search(){ search.click(); return new OtherDetailsPage(); } }
 
 public class OtherDetailsPage { public SelenideElement productName=$("#label"); private SelenideElement details =$("#link"); public OtherDetailsPage showDetails(){ details.click(); return this; } } Page Objects
  49. Razor Понятие переходов между "страницами" Какие "страницы"? SinglePageApplication (SPA) Бывают

    исключения! Portals with widgets
  50. public class KISSSearchWidget { private SelenideElement search =$("#button"); public void

    search(){ search.click(); } } public class KISSDetailsWidget { public SelenideElement productName=$("#label"); private SelenideElement details =$("#link"); public void showDetails(){ details.click(); } } KISS Page Widgets
  51. public class KISSTest { @Test public void testProductName(){ new KISSSearchWidget().search();

    KISSDetailsWidget kissDetailsWidget = new KISSDetailsWidget(); kissDetailsWidget.showDetails(); kissDetailsWidget.productName.shouldBe(visible); // also okay new KISSDetailsWidget().showDetails(); new KISSDetailsWidget().productName.shouldBe(visible); } } KISS Test
  52. Test public class PaymentsTest { @Test public void testPaymentProcess(){ new

    PaymentWizard().confirm() .fillRecipientData() .confirm() .fillAmountData() .confirm() .enterTAN() .confirm(); } }
  53. Inheritance

  54. accountNo: 123456 
 balance: -156,12 € accountNo: 786312 
 interest:

    2% p.a. Checking Account Saving Account UI details details
  55. public class CheckingAccountPage extends BaseBankAccountPage{ public SelenideElement balance=$("#balance"); } public

    class SavingAccountPage extends BaseBankAccountPage{ public SelenideElement interestRate=$("#interest"); } Page Objects
  56. public class BaseBankAccountPage { public SelenideElement accountNumber=$("#account"); private SelenideElement details

    =$("#link"); public void showDetails(){ details.click(); } } Base Page Object
  57. 
 balance: -156,12 € 
 interest: 2% p.a. Checking Account

    Saving Account accountNo: 123456 
 Base Account details
  58. A bit of duplication is better than a bit of

    dependency автора…
  59. public class KISSCheckingAccountPage { public SelenideElement accountNumber=$("#account"); public SelenideElement balance=$("#balance");

    private SelenideElement details =$("#link"); public void showDetails(){ details.click(); } } public class KISSSavingAccountPage { public SelenideElement accountNumber=$("#account"); public SelenideElement interest=$("#interest"); private SelenideElement details =$("#link"); public void showDetails(){ details.click(); } } KISS Page Objects
  60. public class InheritedTest extends BaseTest{ @Test public void testSomething(){ $("#button").click();

    $("#text").shouldBe(visible); } } Inherited Tests
  61. public class BaseTest { @Before public void openSite() { open("http://example.com");

    login("username","pwd"); } } Base Test
  62. public class KISSTest{ @Before public void openSite() { open("http://example.com"); login("username","pwd");

    } @Test public void testSomething(){ $("#button").click(); $("#text").shouldBe(visible); } } KISS Tests
  63. Good inheritance-free tests possible

  64. Composition

  65. Composition over Inheritance

  66. header accountNo: 123456 
 balance: -156,12 € footer Checking Account

    Saving Account UI details header accountNo: 123456 
 interest: 3,5% p.a footer details
  67. // KISS Widget public class Header { public SelenideElement menu=$("#menu");

    public SelenideElement logo=$("#logo"); } 
 // KISS Widget public class Footer { public ElementsCollection links=$$("#links"); public SelenideElement copyright=$("#copyright"); } KISS Page Widgets
  68. public class CompositionSavingAccountPage { public Header header; public Footer footer;

    public SelenideElement accountNumber=$("#account"); public SelenideElement interest=$("#interest"); private SelenideElement details =$("#link"); public void showDetails(){ details.click(); } } Page Object
  69. public class CompositionTest { @Test public void testDetails(){ new CompositionSavingAccountPage().showDetails();

    new CompositionSavingAccountPage().accountNumber.shouldHave(text("12345")); } @Test public void testFooterAndHeader(){ CompositionSavingAccountPage savingAccountPage = new CompositionSavingAccountPage(); savingAccountPage.showDetails(); savingAccountPage.header.logo.shouldBe(visible); savingAccountPage.footer.links.shouldHaveSize(5); } } Test
  70. public class CompositionSavingAccountPage { public Header header; public Footer footer;

    public SelenideElement accountNumber=$("#account"); public SelenideElement interest=$("#interest"); private SelenideElement details =$("#link"); public void showDetails(){ details.click(); } } Page Object
  71. public class KISSSavingAccountWidget { public SelenideElement accountNumber=$("#account"); public SelenideElement interest=$("#interest");

    private SelenideElement details =$("#link"); public void showDetails(){ details.click(); } } KISS Page Object Widget
  72. public class KISSTest { @Test public void testDetails(){ new KISSSavingAccountWidget().showDetails();

    new KISSSavingAccountWidget().accountNumber.shouldHave(text("12345")); } @Test public void testFooterAndHeader(){ new KISSSavingAccountWidget().showDetails(); new Header().logo.shouldBe(visible); new Footer().links.shouldHaveSize(5); } } KISS Tests
  73. None
  74. None
  75. Test data and constants

  76. public class OtherTest { @Test public void testInput(){ $("#firstname").setValue("Alexei"); $("#lastname").setValue("Vinogradov");

    $("#submit").click(); $("#fullname").shouldHave(exactText("Alexei Vinogradov")); } @Test public void testSearchField(){ $("#search").setValue("Vinogradov"); $("#submit").click(); $("#fullname").shouldHave(exactText("Alexei Vinogradov")); } } Test
  77. public class OtherTest { public static final String FIRSTNAME =

    "Alexei"; public static final String LASTNAME = "Vinogradov"; @Test public void testInput(){ $("#firstname").setValue(FIRSTNAME); $("#lastname").setValue(LASTNAME); $("#submit").click(); $("#fullname").shouldHave(exactText(FIRSTNAME+" "+LASTNAME)); } @Test public void testSearchField(){ $("#search").setValue(LASTNAME); $("#submit").click(); $("#fullname").shouldHave(exactText(FIRSTNAME+" "+LASTNAME)); } } Test
  78. public class KISSTest { @Test public void testInput(){ $("#firstname").setValue("Alexei"); $("#lastname").setValue("Vinogradov");

    $("#submit").click(); $("#fullname").shouldHave(exactText("Alexei Vinogradov")); } @Test public void testSearchField(){ $("#search").setValue("Vinogradov"); $("#submit").click(); $("#fullname").shouldHave(exactText("Alexei Vinogradov")); } } KISS Test
  79. Locators (Single Responsibility)

  80. public class OtherPageObject { SelenideElement description=Locators.DESCRIPTION, submitBtn=Locators.SUBMIT, productName=Locators.LABEL; public void

    useElements(){ description.getText(); submitBtn.click(); productName.shouldBe(visible); } } Page Object public class Locators { public final static SelenideElement DESCRIPTION=$("#description"); public final static SelenideElement SUBMIT=$("#submit"); public final static SelenideElement LABEL=$("#label"); }
  81. public class OtherPageObject { SelenideElement description=Locators.DESCRIPTION, submitBtn=Locators.SUBMIT, productName=Locators.LABEL; public void

    useElements(){ description.getText(); submitBtn.click(); productName.shouldBe(visible); } } Page Object public class Locators { public final static SelenideElement HOMEPAGE_HEADER_DESCRIPTION=$("#description"); public final static SelenideElement PRODUCTPAGE_SEARCH_SUBMIT=$("#submit"); public final static SelenideElement ACCOUNTTABLE_COLUMN_PRICE_LABEL=$("#label"); }
  82. None
  83. public class OtherPageObject { SelenideElement description=Locators.DESCRIPTION, submitBtn=Locators.SUBMIT, productName=Locators.LABEL; public void

    useElements(){ description.getText(); submitBtn.click(); productName.shouldBe(visible); } } Page Object public class Locators { public final static SelenideElement DESCRIPTION=$("#description"); public final static SelenideElement SUBMIT=$("#submit"); public final static SelenideElement LABEL=$("#label"); }
  84. public class KISSPageObject { SelenideElement description=$("#text"), submitBtn=$("#button"), productName=$("#label"); public void

    useElements(){ description.getText(); submitBtn.click(); productName.shouldBe(visible); } } KISS PageObject
  85. JavaDoc & Comments

  86. public class OtherPageObject { SelenideElement element=$("#element",1); public SelenideElement findUser(String name,

    int row){ <…> } } PageObject
  87. public class OtherPageObject { SelenideElement element=$("#element",1); // there are 3

    #element-s, take second /** * Finds user in the table row */ public SelenideElement findUser(String name, int row){ <…> } } PageObject
  88. public class KISSPageObject { SelenideElement element=$("#element",1); // there are 3

    #element-s, take second /** * Finds user in the table row, substring will be searched * @param name case-sensitive, substring * @param row 0..N * @return SelenideElement representing a table row or null if not found. */ public SelenideElement findUser(String name, int row){ <…> } } PageObject
  89. public class KISSPageObject { SelenideElement element=$("#element",1); // there are 3

    #element-s, take second /** * Finds user in the table row, substring will be searched * Examples: findUser("lex",0) - finds Alexei in the first row * findUser("alex",0) - returns null, if Alexei in the first row * @param name case-sensitive, substring * @param row 0..N * @return SelenideElement representing a table row or null if not found. */ public SelenideElement findUser(String name, int row){ <…> } } PageObject
  90. None
  91. KISS и сокращение ненужных сущностей упрощает чтение и поддержку кода


    
 Код становится скучным и простым
  92. None
  93. None
  94. None
  95. Простой прямолинейный код 
 просто и прямолинейно поддерживать

  96. Не значит, что SOLID,DRY,Design Patterns плохие! Не значит, что их

    никогда не надо использовать! ПРОСТОТА
  97. None
  98. The End. Questions? skype: alexejv
 email: alexei@vinogradov-it.de twitter: @vinogradoff