Сложности использования TestNG с существующей тестовой архитектурой - PullRequest
0 голосов
/ 29 апреля 2018

Ниже приведено точное (минимальное) представление структуры моего класса, обратите внимание, что в структуре есть только одна точка интеграции с TestNG (аннотация @Test для абстрактного класса).

public abstract class AbstractValidator2{
protected WebDriver driver;
protected String url;

public AbstractValidator2(String url, WebDriver driver) {
    this.url = url;
    this.driver = driver == null ? new ChromeBrowser() : driver; //Setup Selenium
    //more common setups reports, utilities, etc...
}
@Test
public void run() {
    setup();
    validate();
    tearDown();
}
public void setup() {}
public void tearDown() {}
public abstract boolean validate();
}

Теперь приведен пример тестового примера, который прекрасно работает с описанной выше структурой.

public class sampletest extends AbstractValidator2{
    public sampletest() {
        super("www.google.com", null);
    }
    @Override
    public boolean validate() {
        return true;
    }
}

В связи с тем, что в приведенном выше нет конструктора args, его очень легко использовать с TestNG. Я могу включить это в любой тестовый XML-файл и настроить его на запуск любым способом. Отлично, жизнь удалась!

Следующий тест ниже - это интеграция с TestNG.

public class sampletest2 extends AbstractValidator2{
    Date date;
    public sampletest2(String url, Date date) {
        super(url, null);
        date = date;
    }
    @Override
    public boolean validate() {
        driver.get(url);
        Utils.wait(5000);
        return false;
    }
}

В этом примере мне нужно предоставить аргументы моему конструктору. Мне также нужен новый экземпляр для каждого теста, который я хотел бы проверить. Это исключает поставщиков данных в одиночку. Чтобы добавить сложности, мне также нужно работать параллельно. Фабрики действительно работают, но есть проблема. Мне кажется невозможным выбрать отдельные варианты использования при использовании фабрики (я должен запустить их все без изменения кода). Ежедневно мне очень нравится запускать варианты использования индивидуально.

Решение, которое я нашел, заключается в следующем.

public class sampletest2suite {
    @Test
    public void sampletest2google(){
        new sampletest2("https://www.google.com/", null).run();
    }
    @Test
    public void sampletest3bing(){
        new sampletest2("https://www.bing.com/", null).run();
    }
}

Эта структура, однако, нанесла мне вред и создала структуру, которая не позволяет мне использовать многие замечательные возможности Testng. Моей целью было бы использовать конфигурацию xml testng, чтобы я мог удалить этот дубликат кода и конфигурацию из своей структуры классов Java.

Я также предложил другое решение. Это решение злоупотребляет наследованием и создает подклассы по единственной причине обеспечения конструктора no args для совместимости с testng. Вы можете увидеть следующий вопрос stackexchange о том, почему это плохая схема: https://softwareengineering.stackexchange.com/questions/257562/is-creating-subclasses-for-specific-instances-a-bad-practice

public class sampletest2suitealternativeapproach {
    public static class sampletest2Google extends sampletest2{
        public sampletest2Google() { super("https://www.google.com/", null); }
    }

    public static class sampletest2Bing extends sampletest2{
        public sampletest2Bing() { super("https://www.bing.com/", null); }
    }
}

Учитывая текущие проблемы, есть ли у кого-нибудь совет, чтобы вернуться на более здоровый путь.

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

Вот один из способов решения вашей проблемы.

Состав:

  1. Пользовательская аннотация, которая будет представлять параметры для каждого теста.
  2. Базовый класс для каждого варианта ваших тестов (или) у вас может быть даже один базовый класс, который просто работает с объектами.
  3. Реализация IHookable реализации в вашем базовом классе, которая будет играть роль исправления вызова в тесте после определения правильных параметров для внедрения.
  4. Фабрика объектов (если требуется), которая будет отвечать за создание объектов для создания экземпляров в тестах (что-то вроде того, что делает Spring).

Вот образец

Аннотация

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target({METHOD, TYPE})
public @interface InjectParameters {
  String url() default "";

  String getComplexClass() default "testngImplementation.acomplexclass";
}

Базовый класс, из которого будут расширяться ваши тесты

import org.testng.IHookCallBack;
import org.testng.IHookable;
import org.testng.ITestResult;
import org.testng.internal.ClassHelper;

import java.util.ArrayList;
import java.util.List;

public class AbstractValidator implements IHookable {

  public AbstractValidator() {
    // more common setups reports, selenium, utilities, etc...
  }

  public void setup() {}

  public void tearDown() {}

  @Override
  public void run(IHookCallBack callBack, ITestResult testResult) {
    System.out.println("We made it to run on the Abstract Validator 2");
    setup();
    Object[] parameters = callBack.getParameters();
    Object[] injected = getParametersToInject(testResult);
    if (parameters.length == injected.length) {
      System.arraycopy(injected, 0, parameters, 0, parameters.length);
    }
    callBack.runTestMethod(testResult);
    tearDown();
  }

  private Object[] getParametersToInject(ITestResult testResult) {
    List<Object> parameters = new ArrayList<>();
    InjectParameters toInject =
        testResult
            .getMethod()
            .getConstructorOrMethod()
            .getMethod()
            .getAnnotation(InjectParameters.class);
    if (toInject != null) {
      parameters.add(toInject.url());
      parameters.add(ClassHelper.newInstance(ClassHelper.forName(toInject.getComplexClass())));
    }
    return parameters.toArray(new Object[0]);
  }
}

образец класса испытаний

public class TestCreation extends AbstractValidator {
  @Test
  @InjectParameters(url = "https://www.google.com/")
  // The @Parameters is being used just as a place holder here. To prevent TestNG from triggering
  // Validations saying that parameter required but not provided. I think this would be fixed
  // as part of fix for https://github.com/cbeust/testng/issues/564 which would be available in
  // TestNG 7.0.0

  @Parameters({"url", "a"})
  public void sampletest2google(@Optional String url, @Optional acomplexclass a) {
    System.err.println("URL is " + url);
    System.err.println("Complex object  is " + a.toString());
  }

  @Test
  @InjectParameters(url = "https://www.bing.com/")
  @Parameters({"url", "a"})
  public void sampletest3bing(@Optional String url, @Optional acomplexclass a) {
    System.err.println("URL is " + url);
    System.err.println("Complex object  is " + a.toString());
  }
}

Дальнейшие улучшения для соответствия специфике вашей проблемы.

Если вы хотите сделать это как можно более универсальным, вы можете сделать следующее:

  • Поскольку TestNG поддерживает Guice Injection, изучите его, чтобы позаботиться об экземпляре объекта и его повторном использовании для ваших @Test методов.
  • Если каждый тест в основном должен работать с различным набором параметров, возможно, вы могли бы определить имя метода в качестве одного из параметров в вашей пользовательской аннотации, и, таким образом, реализация базового класса, которая анализирует пользовательскую аннотацию, в итоге станет универсальной. один (аналогично тому, что делает TestNG при указании имени поставщика данных)
0 голосов
/ 29 апреля 2018

Я думаю, что есть некоторые особенности TestNG, которые, если их использовать, облегчили бы вашу жизнь. setup() и tearDown() должны быть соответствующим образом помечены @Before* и @After*. Это заставит их работать в нужное время, не вызывая их в каком-либо методе run(). Инстанцирующий драйвер должен быть в setup(). Рекомендуется создать экземпляр браузера, использовать его для 1 теста и уничтожить его. Это позволяет сохранить каждый прогон отдельно и избежать перекрестного загрязнения прогонов (один прогон выходит из строя и портит последующие прогоны и т. Д.).

Вот простой пример, который, я надеюсь, поможет вам понять, как использовать провайдер данных с TestNG, чтобы сделать что-то вроде того, что вы хотите.

public class TestNg
{
    private WebDriver driver;

    @Test(dataProvider = "getData")
    public void test(String url, String title)
    {
        driver.navigate().to(url);
        assertEquals(driver.getTitle(), title, "Verify title");
    }

    @BeforeMethod
    public void setup()
    {
        String chromeDriverPath = "C:\\path\\to\\chromedriver.exe";
        System.setProperty("webdriver.chrome.driver", chromeDriverPath);
        driver = new ChromeDriver();
    }

    @AfterMethod
    public void tearDown()
    {
        driver.close();
        driver.quit();
    }

    @DataProvider
    public Object[][] getData()
    {
        return new Object[][] { { "http://www.google.com", "Google" }, { "http://www.bing.com", "Bing" } };
    }
}

Существует множество способов сделать провайдеров данных с TestNG. Это только один из них. Вы бы лучше знали, какой метод может соответствовать вашим потребностям, и можете изучить официальные документы и выбрать тот, который подходит именно вам.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...