Привет, хабровчане. Прямо сейчас в OTUS открыт набор на курс "Java QA Automation Engineer". Всех желающих приглашаем принять участие в открытом уроке на тему "Http. Postman, newman, fiddler (charles), curl, soap. Soapui".
А также делимся с вами переводом полезной статьи.
При написании автотестов на Java не обойтись без блоков с условным оператором if
. Всегда есть какие-то условия, истинность которых нужно проверять, чтобы выполнялся тот или иной код в зависимости от результата проверки. Однако если использовать слишком много ветвлений с операторами if
и else
, это может засорить код теста. Как же писать блоки if
, которые будут выполнять свою функцию, сохранив при этом читаемость кода тестов? Мы рассмотрим несколько примеров кода, который может встретиться в тестах, и найдем лучший способ написания аналогичного кода.
1. Сегодня выходной?
Представим, что в тесте есть строковая переменная, которая используется для обозначения дня недели. Нам нужен вспомогательный метод, который сообщит, является ли день, обозначенный этой переменной, выходным. Метод должен возвращать true
, если строковой переменной присвоено значение Saturday или Sunday, и false
в остальных случаях.
⛔ Неудачный вариант
Рассмотрим первый вариант решения этой задачи. Не рекомендую его использовать и сейчас объясню почему.
private boolean isWeekend_no(String weekDay) {
weekDay = weekDay.toUpperCase();
if (weekDay.equals("SATURDAY"))
return true;
if (weekDay.equals("SUNDAY"))
return true;
if (weekDay.equals("MONDAY"))
return false;
if (weekDay.equals("TUESDAY"))
return false;
if (weekDay.equals("WEDNESDAY"))
return false;
if (weekDay.equals("THURSDAY"))
return false;
if (weekDay.equals("FRIDAY"))
return false;
return false;
}
Во-первых, метод принимает параметр, обозначающий день недели, и переводит его значение в верхний регистр. Это делается потому, что внутри метода сравнение с известными днями недели происходит в верхнем регистре. Поэтому значение параметра, который передается в метод, может быть записано полностью в верхнем регистре, полностью в нижнем регистре или с использованием любого сочетания регистров. Это хорошо.
Код включает несколько блоков if
. Каждый блок сравнивает значение параметра с днем недели до тех пор, пока не будут проверены все дни (с понедельника по воскресенье). То есть выполняется 7 операций сравнения в 7 блоках if
.
Внутри каждого блока if
используется оператор return
. Значение true
могут вернуть только первые два блока, то есть блоки, в условии которых записаны выходные дни. Остальные блоки, в условии которых записаны рабочие дни, вернут значение false.
Затем добавлен оператор return
, расположенный вне блоков if
. Он вернет указанное после него значение, если не будет выполнено ни одно из условий, проверяемых в блоках if
.
Предположим, что мы передали в метод параметр со значением Saturday. Как только соответствующий блок if
сравнит параметр со значением SATURDAY, метод вернет значение true
и завершится. Оставшийся код выполняться не будет.
Это не самый лучший вариант написания кода. Вернемся к условию задачи. Мы знаем, что в неделе всего два выходных дня — суббота и воскресенье. Поэтому нам не нужно указывать никаких других значений для проверки переданного в метод параметра. Все параметры должны проверяться в одном блоке, не нужно писать 5 дополнительных условных конструкций. В этом варианте слишком много кода.
✅ Удачный вариант
Давайте посмотрим, как лучше написать код, который проверит, является ли указанный день выходным.
private boolean isWeekend_yes(String weekDay) {
if (weekDay.toUpperCase().equals("SATURDAY") || weekDay.toUpperCase().equals("SUNDAY"))
return true;
return false;
}
В этом варианте у нас лишь один блок if
. Нас интересуют только те значения параметра, которые соответствуют субботе или воскресенью, поэтому только при совпадении с ними метод вернет значение true
. Если в метод будет передан параметр с другим значением (другим днем недели или случайным набором символов), вернется значение false
.
Не забывайте, что оператор return
завершает выполнение метода. Поэтому, как только метод определит, что значение параметра совпадает с названием выходного дня, он вернет значение true
и больше выполняться не будет. В этом случае инструкция return false
не выполняется. Если переданная в метод строковая переменная не является равной null
и содержит значение, отличное от интересующих нас двух значений, то вернется значение false.
2. Получаем текст веб-элемента (WebElement)
В этой задаче нам нужно извлечь и вернуть текст веб-элемента. Для этого мы используем метод getText()
, который будет считывать данные из веб-элемента и, конечно же, возвращать строку.
⛔ Неудачный вариант
Посмотрим на код, в котором используется вспомогательный метод. Я не рекомендую так поступать.
private String getElementText_no(WebElement element) {
if (!element.getText().isEmpty())
return element.getText();
return "";
}
В этом варианте блок if
проверяет, содержит ли веб-элемент какой-либо текст, и возвращает результат выполнения метода getText()
только в том случае, если текст веб-элемента не является пустым. В этом случае последующий код в методе не выполняется. Но если строка, которую возвращает метод getText()
, является пустой, инструкция return
в блоке if
выполняться не будет. Выполняться будет последняя инструкция return, которая в этом примере возвращает пустую строку. С логической точки зрения это неправильно.
✅ Удачный вариант
Наша задача — вернуть текст веб-элемента вне зависимости от его значения. В примере, который мы рассмотрели выше, код выполняет что-то странное. Сначала он проверяет, является ли текст веб-элемента пустым. Если нет, то возвращается этот самый текст. А если да, код возвращает пустую строку, которая по сути представляет собой то же самое, что и значение полученного текста веб-элемента. И в том и в другом случае возвращаемый результат — это результат выполнения метода getText()
. Поэтому для решения этой задачи лучше написать код без блоков if
и вспомогательных методов, например такой:
element.getText();
Для решения этой задачи я не создавала нового метода, поскольку, как я писала в посте об удачных приемах написания кода, не нужно создавать новый метод для выполнения единственной строки кода.
3. Является ли текст веб-элемента пустым?
В этой задаче нужно проверить, пуст ли текст веб-элемента, и вернуть соответствующее логическое значение.
⛔ Неудачный вариант
В первом варианте решения этой задачи мы впервые встречаемся с условным ветвлением if/else
. До этого у нас были только блоки if
.
private boolean isElementTextEmptyno(WebElement element) {
if (element.getText().isEmpty()) {
return true;
} else return false;
}
Инструкция if
проверяет, является ли пустым текст веб-элемента, полученный с помощью метода getText()
. Если это так, то метод, который мы создали для решения задачи, вернет логическое значение true
. Блок else
вернет значение false
, потому что полученная строка, очевидно, не является пустой.
✅ Удачный вариант
Рассмотренный нами пример неудачен, и вот почему. Метод isEmpty()
возвращает логическое значение. Суть этого кода можно кратко изложить так: если получено истинное значение, возвращаем true
, иначе возвращаем false
. Для решения этой задачи можно написать одну строку кода без создания новых методов:
element.getText().isEmpty();
Мы просто возвращаем значение, полученное в результате выполнения метода isEmpty()
. Если в него передан пустой текст, возвращается значение true
, иначе возвращается false
. Задача решена.
4. Запуск браузера при определенном значении параметра
Для решения этой задачи нужно создать метод, который запустит экземпляр браузера через соответствующий WebDriver. В качестве параметра метод принимает строку, в которой записано название браузера. Этот параметр определяет, какой браузер нужно запустить.
⛔ Неудачный вариант
Рассмотрим пример, как не надо писать код. В нем используется классическое условное ветвление if-else-if-else-if… Тут слишком много блоков, которые делают код трудным для восприятия и понимания. С такой сложной структурой бывает нелегко понять, все ли возможные сценарии мы предусмотрели.
public WebDriver startBrowser_no(String browserName) {
if (browserName.equals("Chrome") || browserName.equals("chrome") ||
browserName.equals("CHROME")) {
System.out.println("Chrome will start!");
return new ChromeDriver();
} else if (browserName.equals("Firefox") || browserName.equals("firefox") ||
browserName.equals("FIREFOX")) {
System.out.println("Firefox will start!");
return new FirefoxDriver();
} else if (browserName.equals("Edge") || browserName.equals("edge") ||
browserName.equals("EDGE")) {
System.out.println("Edge will start!");
return new EdgeDriver();
} else throw new RuntimeException("Unsupported browser! Will not start any browser!");
}
Для того чтобы усовершенствовать код, нам в первую очередь нужно помнить о том, что мы сравниваем значение параметра browserName
всего с одним значением для каждого браузера. То есть нужно сравнить значение параметра browserName
, написанного, например, в нижнем регистре, со значениями строк, в которых названия браузеров записаны также в нижнем регистре.
Затем нужно изменить структуру кода, избавившись от ветвления if-else-if-else-if-… Поскольку мы сравниваем только одно значение параметра с несколькими другими значениями, можно использовать конструкцию switch.
✅ Удачный вариант
Более чистая версия этого кода выглядит так:
public WebDriver startBrowser_yes(String browserName) {
switch (browserName.toLowerCase()) {
case "chrome":
System.out.println("Chrome will start!");
return new ChromeDriver();
case "firefox":
System.out.println("Firefox will start!");
return new FirefoxDriver();
case "edge":
System.out.println("Edge will start!");
return new EdgeDriver();
default:
throw new RuntimeException("Unsupported browser! Will not start any browser!");
}
}
Здесь четко видно, что будет происходить при каждом значении параметра. Например, не составит труда найти код, который будет выполняться, если параметр имеет значение Chrome. Поскольку в каждом блоке case мы используем инструкцию return
, можно быть уверенными в том, что запустится только один браузер: как только мы нашли нужный браузер, метод запускает новый экземпляр браузера и прекращает выполняться. Также несложно понять, что произойдет, если значение параметра не совпадет ни с одним известным значением: смотрим блок default
.
5. До обеда или после?
Предположим, что нам нужно вернуть строковую переменную, которая содержит одно из двух значений: Before Lunch («До обеда») или After Lunch («После обеда»). Ее значение будет зависеть от текущего времени: если текущий час больше 12, то переменной будет присвоено значение After Lunch. Это, конечно, странный пример, но с его помощью я хочу показать, как использовать упрощенную конструкцию if
. Вместо удачного и неудачного вариантов мы рассмотрим два возможных. Они оба хороши, но один чуть лучше другого.
И в том и в другом случае нам нужно объявить строковую переменную и присвоить ей соответствующее значение (Before Lunch или After Lunch). Нам также нужно вычислить текущее время, точнее — текущий час. Для этого мы будем использовать класс LocalDateTime
:
int currentHour = LocalDateTime.now().getHour();
В этой строке кода мы сначала получаем текущую дату и время, вызвав метод now()
, а затем с помощью метода getHour()
извлекаем целое число (int)
, соответствующее значению текущего часа. Переменной currentHour
будет присвоено значение от 0 до 23, соответствующее текущему часу.
Возможный вариант № 1
Рассмотрим первый способ, который можно использовать для присвоения строковой переменной значения в зависимости от текущего часа.
String momentOfDay1;
if (currentHour > 12)
momentOfDay1 = "After Lunch";
else momentOfDay1 = "Before Lunch";
Здесь мы сначала объявили строковую переменную momentOfDay1
, которая будет хранить искомое значение. Затем в блоке if/else
мы сравниваем значение текущего часа с числом 12 и присваиваем переменной momentOfDay1
значение в зависимости от результата сравнения. Все довольно просто.
Единственный недостаток этого варианта состоит в том, что в нем слишком много кода для решения такой простой задачи.
Возможный вариант № 2
В этом варианте вместо 4 строк кода мы напишем всего одну.
String momentOfDay2 = (currentHour > 12) ? "After Lunch" : "Before Lunch";
В этой строке есть все: объявление строковой переменной, сравнение значения текущего часа с числом 12 и присвоение переменной momentOfDay2
требуемого значения в зависимости от результата сравнения. Здесь мы используем упрощенную конструкцию if/else
. Код между знаком вопроса (?) и двоеточием (:) соответствует ветви кода в блоке if
, который мы использовали раньше. Все, что идет после двоеточия, соответствует ветви else
. Как видите, этот вариант очень компактный, и, как только вы поймете синтаксис, вам не составит труда его прочитать.
Дополнительные рекомендации
Мы рассмотрели приемы, с помощью которых можно упростить сложные конструкции с if
, и мне бы хотелось поделиться еще несколькими советами.
Не оставляйте пустых блоков else
. Если вы вдруг написали подобную конструкцию, исправьте ее:
if (condition) {
... doSomethingHere() ...
} else {}
Поскольку в блоке else
кода нет, нет смысла писать этот блок. Оставьте только if
, и код будет проще понять:
if (condition) {
... doSomethingHere() ...
}
Используйте логический оператор И вместо нового блока if. Взгляните на этот код:
if (condition1) {
if (condition2) {
... doSomethingHere() ...
}
}
Единственное, что содержит первый блок if
в этом примере, — это второй блок if
и больше ничего. Код, который должен выполняться в первом блоке if
, — это на самом деле код второго блока if
(если, конечно, второе условие condition2
выполняется). Поэтому мы можем переписать код, используя только один блок if
и логический оператор И
(который в Java пишется как два амперсанда: &&
).
if (condition1 && condition2) {
... doSomethingHere() ...
}
Не повторяйте одно и то же условие. Если у вас слишком много вложенных if
, можно запросто заблудиться в массе условий, которые вы написали для каждой ветви. Например:
if (condition1) {
... someCodeHere() ...
if (condition2) {
... someOtherCodeHere() ...
if (condition3) {
... moreCodeHere() ...
if (condition1) {
... evenMoreCodeHere() ...
}
if (!condition2) {
... aLotMoreCodeHere() ...
}
}
}
}
В этом примере в первом блоке if
проверяется истинность первого условия (condition1
). Но в одной из вложенных ветвей это условие проверяется еще раз. В этом нет смысла, если код внутри первого if
никак не влияет на результат проверки этого условия. То же самое относится ко второму условию (condition2
). Если код второго блока if
не влияет на результат проверки второго условия, блок if
, где проверяется !condition2
, выполняться не будет.
Заключение
Из этих примеров видно, что иногда нам просто не нужно использовать конструкцию с if
. Но когда мы ее используем, уровней вложенности должно быть как можно меньше. Пишите максимально простой и чистый код. При необходимости его проще отлаживать, а если через несколько месяцев вам нужно будет разобраться, что этот код должен был делать, вы без труда справитесь с этой задачей.
- Узнать подробнее о курсе "Java QA Automation Engineer".
- Записаться на открытый урок "Http. Postman, newman, fiddler (charles), curl, soap. Soapui".