📜 ⬆️ ⬇️

Functional autotest

Elements of functional programming appeared in Java relatively recently, but it is becoming more and more popular. Especially in the stream API, there is probably no Java developer who would not hear / read / use this API for working with collections. Unfortunately, the majority does not go further than using the Stream API, whereas the functional approach allows you to significantly simplify the life of autotest developers. Below, I will tell you about two examples of this simplification - check dictionaries and specialized matchrs.


Dictionaries checks.


If you are using the BDD approach, then you probably used parameterized verification steps.


    «»       |1| |2| |3| 

To implement this verification step using OOP / procedural approach, you can use a set of methods and switch to determine the field name for verification:


  private void checkMinBalanceAmount(String checkMinAmount) throws Exception { String minBalanceAmountStr = checkMinAmount; String minBalanceAmount = String.format("%.2f", Double.parseDouble(minBalanceAmountStr)); String amountMinIF = amountLink.getText().replaceAll("(.|\\$|€)", "").replaceAll(" ", ""); Assert.assertEquals(minBalanceAmount, amountMinIF); } private void checkMaxBalanceAmount(String checkMaxAmount) throws Exception { String maxBalanceAmountStr = checkMaxAmount; String maxBalanceAmount = String.format("%.2f", Double.parseDouble(maxBalanceAmountStr)); String amountmaxIF = maxAmountDepositLink.getText().replaceAll("(.|\\$|€)", "").replaceAll(" ", ""); Assert.assertEquals(maxBalanceAmount, amountmaxIF); } private void checkBalanceAmount(String checkBalanceAmount) throws Exception { String maxBalanceAmountStr = checkBalanceAmount; String maxBalanceAmount = String.format("%.2f", Double.parseDouble(maxBalanceAmountStr)); String amountmaxIF = amountDepositLink.getText().replaceAll("(.|\\$|€)", "").replaceAll(" ", ""); Assert.assertEquals(maxBalanceAmount, amountmaxIF); } public void __(String name) throws Throwable { String query = "select * from deposit_and_account_data"; List<Map<String, String>> fetchAll = Db.fetchAll(query, "main"); switch (name) { case " ": Assert.assertEquals(fetchAll.get(0).get("ACCOUNT_NAME"), nameDepositLink.getText()); break; case " ": checkDate(fetchAll.get(0).get("CLOSE_DATE")); break; case " ": checkCurrency(fetchAll.get(0).get("NAME")); case "  ": checkMinBalanceAmount(fetchAll.get(0).get("MIN_BALANCE_AMOUNT")); break; case "   ": checkMaxBalanceAmount(fetchAll.get(0).get("MAX_SUM_AMOUNT")); break; case " ": checkBalanceAmount(fetchAll.get(0).get("BALANCE_AMOUNT")); break; default: throw new AutotestError(" "); } 

The code above is nothing bad, it is well structured. But he has a problem - the time-consuming addition of one more check: it is necessary, first, to implement the check, and, second, to add it to the switch. The second step seems redundant. If you apply a "dictionary of checks", then you can do only the first steps.
The check dictionary is the Map, in which the key is the name of the check, and the value is the function that takes a record from the database as parameters, and returns a Boolean. That is java.util.function.Predicate


 Map<String,Predicate<Map<String,String>>> checkMap = new HashMap<>(); 

We rewrite checks:


 checkMap.put(" ",exp -> exp.get("ACCOUNT_NAME").equals(nameDepositLink.getText())); 

We rewrite the method of calling checks:


  public void __(String name) throws Throwable { String query = "select * from deposit_and_account_data"; Map<String, String> expected = Db.fetchAll(query, "main").get(0); Assert.assertTrue(name, Optional.ofNullable(checkMap.get(name)) .orElseThrow(()->new AutotestError(" ")) .test(expected)); } 

What happens in the snippet above: Trying to get a check from the field name Optional.ofNullable (checkMap.get (name)), if it is NULL, then we throw an exception. Otherwise, perform the resulting test.
Now, in order to add a new check it is enough to add it to the dictionary. In the method of calling checks, it becomes available automatically. The full source code of the example and other examples of using the FP for autotests are available in the repository on GitHub:
https://github.com/kneradovsky/java8fp_samples/


Custom matchers


Practice shows that assertions are rarely used in the Autotest Selenuim WebDriver. In my opinion, the most likely reason for this is that standard Matchers do not provide functionality for checking the status of WebElement. Why use assertions? This is a standard mechanism that is supported by any means of generating reports and presenting test results. Why reinvent the wheel, if you can modify it.
How can a functional approach make using assertions to check the properties and state of WebElements convenient? And why be limited only to web elements?
Imagine that we have a function that takes 2 arguments: an error message in the event of a failure and a predicate function (takes the checked WebElement and returns the result of the check), and returns a Matcher.


  public static BiFunction<String, Predicate<WebElement>, BaseMatcher<WebElement>> customMatcher = (desc, pred) -> new BaseMatcher<T>() { @Override public boolean matches(Object o) { return pred.test((WebElement) o); } @Override public void describeTo(Description description) { description.appendText(desc); } }; } 

This allows you to construct any checks with specialized error messages.


 BaseMacther<WebElement> m1 = customMatcher.apply("   qaconf.ru",e -> e.getAttribute("href").contains("qaconf.ru")); 

But why limit yourself only to web elements? We make a static generic method that takes the type of the object, returns a function of 2 arguments: an error message in the case of failure and a predicate function (takes an object of the specified type and returns the result of the test) and a returning Matcher.


  public static <T> BiFunction<String, Predicate<T>, BaseMatcher<T>> typedMatcher2(Class<T> cls) { return (desc, pred) -> new BaseMatcher<T>() { @Override public boolean matches(Object o) { return pred.test((T) o); } @Override public void describeTo(Description description) { description.appendText(desc); } }; } 

Now we have a specialized matcher that can be used in assertions:


  BiFunction<String, Function<Predicate<WebElement>, BaseMatcher<WebElement>>> webElMatcherSupp = typedMatcher2(WebElement.class); BaseMatcher<WebElement> shouldBeTable = apply("Should be Table",e->e.getTagName().equalsIgnoreCase("table")); assertThat(elem2Test,shouldBeTable); 

In combinations with other matchmakers:


 assertThat(elem2test,not(shouldBeTable)); 

Or so


 BaseMatcher<WebElement> hasText1 = webElMatcherSupp.apply("Should be contain text1",e->e.getText().equalsIgnoreCase("text1")); assertThat(elem2test,allOf(not(shouldBeTable),hasText1)); 

In addition, matchers can be used in assumptions.


 assumeThat(elem2test,not(shouldBeTable)); 

But that's not all. You can create a parameterized custom match:


 Function<String,BaseMatcher<WebElement> textEquals = str -> webElMatcherSupp.apply("Text should equals to: " + str,e-> e.getText().equals(str)); assertThat(elem2test,textEquals.apply("text2")); assertThat(elem2test,not(textEquals.apply("text3"))); 

Thus, we get a specialized matcher, whose message and verification is parameterized by any value transmitted at the execution stage.


Conclusion:
The use of the functional approach in the development of autotests allows, on the one hand, to reduce the amount of code, on the other - to increase its readability. Own matchers make it easy to create sets of typed checks, complementing the standard assertions mechanism. Dictionaries checks eliminate the need to perform unnecessary work.
The full code for the examples is in the repository: https://github.com/kneradovsky/java8fp_samples/


')

Source: https://habr.com/ru/post/301166/


All Articles