Использование отражения при приведении с абстрактным типом
Я полностью за кого-то, кто порекомендовал бы лучше название для этого конкретного вопроса. Я также более чем готов работать, чтобы упростить описание проблемы.
Контекст: у меня есть настройка автоматизации, где я разрешаю настройку браузера через файл свойств. Так что, если у кого-то есть "browser = chrome" в этом файле, то конкретный WebDriver
экземпляр, который должен быть создан ChromeDriver
,
Я также использую WebDriverManager, где вы можете загрузить двоичные файлы для конкретного WebDriver
типы. Поэтому в этом случае я хочу загрузить только тот драйвер браузера, который находится в этом файле свойств. Так что, если это Chrome, я хочу использовать ChromeDriverManager
,
Ключевым моментом здесь, конечно, является то, что я должен обобщить все это, потому что я не знаю, что кто-то собирается использовать. Но в целях моего вопроса здесь, и чтобы показать проблему, давайте придерживаться этих движущихся частей: "хром", ChromeDriver
, ChromeDriverManager
,
Код:
у меня есть driverMap
который содержит экземпляр WebDriver
класс, связанный с именем браузера.
private static final Map<String, Class<?>> driverMap = new HashMap<String, Class<?>>() {
{
put("chrome", ChromeDriver.class);
put("firefox", FirefoxDriver.class);
}
};
у меня тоже есть driverManager
что связывает BrowserManager
класс с определенным WebDriver
учебный класс.
private static final Map<Class<?>, Class<?>> driverManager = new HashMap<Class<?>, Class<?>>() {
{
put(ChromeDriver.class, ChromeDriverManager.class);
put(FirefoxDriver.class, FirefoxDriverManager.class);
}
};
Просто для большего контекста, все это в классе под названием Driver
и это начинается так:
public final class Driver {
private static WebDriver driver;
private static BrowserManager manager;
....
}
Эти две переменные актуальны здесь для следующего бита. add
метод вызывается для добавления определенной конфигурации браузера к тестам. Итак, вот тот метод, который показывает, как вышеупомянутое используется, когда браузер добавлен к соединению:
public static void add(String browser, Capabilities capabilities) throws Exception {
Class<?> driverClass = driverMap.get(browser);
Class<?> driverBinary = driverManager.get(driverClass);
manager = (BrowserManager) driverBinary.getConstructor().newInstance(); /// <<--- PROBLEM
driver = (WebDriver) driverClass.getConstructor(Capabilities.class).newInstance(capabilities);
}
Вы можете видеть, что я использую
driverClass
, что будет примерно так:org.openqa.selenium.chrome.ChromeDriver
,Вы можете видеть, что я использую
driverBinary
, что будет примерно так:io.github.bonigarcia.wdm.ChromeDriverManager
,
Но я прокомментировал строку выше, где у меня есть проблема.
Проблема: вы можете видеть, что я использую driver
переменная для хранения WebDriver
экземпляр и manager
переменная для хранения BrowserManager
пример.
Вот как и почему я делаю это в случае driver
:
Так что же, это получить мне соответствующий тип (ChromeDriver
) из более общего (WebDriver
). Так по моему driver
переменная, я могу бросить вызов WebDriver
и, таким образом, ссылка driver
как будто это был тот экземпляр.
Я не могу сделать то же самое для manager
,
И я не знаю, из-за того, как работает эта конкретная библиотека Java. В частности:
Поэтому я не могу вызывать методы на manager
как будто это был определенный тип BrowserManager
(лайк ChromeDriverManager
) как я могу для driver
(который является конкретным типом WebDriver
, лайк ChromeDriver
).
Казалось бы, потому что в конечном итоге WebDriver
это интерфейс, но BrowserManager
абстрактно.
Поэтому я не знаю, как добиться желаемого эффекта. В частности, эффект, который я хочу, - сделать вызов эквивалентным этому:
ChromeDriverManager.getInstance().setup();
Но я должен сделать это с помощью отражения, так как я не знаю, каким менеджером я буду пользоваться. Так что в идеале я хочу, чтобы я мог сделать это:
manager.getInstance().setup();
Я не знаю, к чему я могу бросить, чтобы сделать manager
Работа. Или я не знаю, могу ли я привести конкретный класс, как только я определюсь, что это за класс.
Я могу просто отказаться от использования WebDriverManager, но это хорошее решение, и я надеюсь найти способ сделать то, что мне нужно.
3 ответа
Поэтому я не знаю, как добиться желаемого эффекта. В частности, эффект, который я хочу, - сделать вызов эквивалентным этому:
ChromeDriverManager.getInstance().setup();
Но я должен сделать это с помощью отражения, так как я не знаю, каким менеджером я буду пользоваться. Так что в идеале я хочу, чтобы я мог сделать это:
manager.getInstance().setup();
Я не знаю, к чему я могу обратиться, чтобы заставить менеджера работать. Или я не знаю, могу ли я привести конкретный класс, как только я определюсь, что это за класс.
После расследования я обнаружил, что ChromeDriverManager.getInstance()
это статический метод. Статические методы связаны во время компиляции, а не во время выполнения, поэтому вы не можете вызывать этот метод с помощью обычного выражения вызова метода, если вы не знаете во время компиляции, какой метод класса вы хотите вызвать. И дело в том, что вы этого не знаете.
Но это глупо. Смысл этого метода заключается в предоставлении экземпляра класса, зарегистрированного BrowserManager
в качестве специального особого случая. Нет смысла пытаться сделать это, сначала получив какой-то другой экземпляр, который вам больше ни для чего не нужен, потому что вам также не нужен экземпляр класса для вызова статических методов класса.
Похоже, что бетон BrowserManager
подклассы реализуют шаблон такого getInstance()
методы. Хотя они не являются полиморфными и, следовательно, не обязательно присутствуют, вы можете полагаться на шаблон, чтобы найти и вызвать их рефлексивно (вместо того, чтобы вызывать конструктор рефлексивно). Например,
Class<?> driverBinary = driverManager.get(driverClass);
try {
// Retrieves a no-arg method of the specified name, declared by the
// driverBinary class
Method getInstanceMethod = driverBinary.getDeclaredMethod("getInstance");
// Invokes the (assumed static) method reflectively
BrowserManager manager = (BrowserManager) getInstanceMethod.invoke(null);
manager.setup();
} catch ( IllegalAccessException
| IllegalArgumentException
| InvocationTargetException
| NoSuchMethodException
| SecurityException e) {
// handle exception
}
Вы можете вызвать все методы экземпляра, объявленные BrowserManager
на получившийся объект. В частности, вы можете вызвать setup()
, как показано.
С другой стороны, если вам не нужно регистрировать свой экземпляр как BrowserManager
Например, тогда вам не нужно проходить getInstance()
совсем. Метод, который у вас уже есть для получения экземпляра, будет достаточен для получения экземпляра, и вы можете затем вызвать его setup()
метод напрямую. Я не уверен, зарегистрирован ли экземпляр в BrowserManager
представит любую проблему.
Переходя к комментариям и помощи Джона о том, имеет ли этот подход смысл, я нашел способ грубой силы справиться с этим, а именно:
Class<?> driverBinary = driverManager.get(driverClass);
if (driverBinary.newInstance() instanceof ChromeDriverManager) {
ChromeDriverManager.getInstance().setup();
}
Так что здесь я избавляюсь от manager
переменная и просто использовать driverBinary
экземпляр, чтобы проверить, является ли он экземпляром одного из менеджеров драйверов. Тогда я могу просто добавить ряд других условий для каждого браузера. Например:
if (driverBinary.newInstance() instanceof ChromeDriverManager) {
ChromeDriverManager.getInstance().setup();
} else if (driverBinary.newInstance() instanceof FirefoxDriverManager) {
FirefoxDriverManager.getInstance().setup();
} else if (...) {
...
}
Я говорю "грубой силой", потому что я понимаю, что это решение не дает большой утонченности. Мне также нужно поиграть с предложенным Джоном решением.
Подобные проблемы часто возникают в средах тестирования, где вы не можете знать, в каких условиях будут работать тесты. Таким образом, возможность создавать лучшие или худшие способы выполнения этих действий кажется полезной.
Выше в настоящее время показано в моем классе водителя.
WebDriverManager имеет разные драйвера-менеджеры для разных браузеров, т.е. ChromeDriverManager
для Chrome, FirefoxDriverManager
для Firefox и так далее. Более того, у него есть универсальный драйвер Manager, который можно параметризировать. Этот драйвер назван напрямую WebDriverManager
, Метод getInstance()
of Driver принимает класс WebDriver для используемого браузера (т.е. ChromeDriver
, FirefoxDriver
, так далее):
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
import io.github.bonigarcia.wdm.WebDriverManager;
// ...
Class<? extends WebDriver> driverClass = ChromeDriver.class;
// ... other option:
// driverClass = FirefoxDriver.class;
WebDriverManager.getInstance(driverClass).setup();
WebDriver driver = driverClass.newInstance();
Здесь вы можете найти рабочий пример (параметризованный тест JUnit 4 для использования Chrome и Firefox с одинаковой логикой теста, в котором универсальный driverManager разрешает правильный двоичный файл для Chrome и Firefox)