Как стать автором
Обновить

SOLID на практике в автоматизации тестирования

Время на прочтение5 мин
Количество просмотров8K

Как на практике реализовать все принципы и упростить разработку тестирования BDD ?

Для чего нужны принципы SOLID

При создании программных систем использование принципов SOLID способствует созданию такой системы, которую будет легко поддерживать и расширять в течение долгого времени. Принципы SOLID — это руководства, которые также могут применяться во время работы над существующим программным обеспечением для его улучшения, например, для удаления «дурно пахнущего кода».

Примером будет интеграционное тестирование, но вам ничего не помешает реализовать для End2End

  • SRP принцип единой ответственности

Возьмем четыре базовые функции работы с БД

  • Create

  • Read

  • Update

  • Delete

Создадим интерфейс, класс которого будет имплементировать его

public interface CrudEntity  {

    Response getId(String... values);

    void create(Object o,String... values);

    void update(Object o,String... values);

    void delete(String ...values);

}
@Service
public class User implements CrudEntity, ImageEntity, FindAll, FindMe {


        public void create(Object o,String... value) {
        given().when()
                .body(o)
                .post("employee/create");
    }
}

На примере метода create передаем объект, который будет собираться из POJO.

String ... value это массив строк, через него передаем статусы кода или аргументы, которые нам будут нужны. Задать массив можно только один раз.

  • Open - closed открыт для расширения, закрыт для модификаций.

Создадим класс Condition в нем метод проверки, который будет возвращать Response

    public ValidatableResponse bodyCheckSize(Response response, String valueOne, String valueTwo) {
        return response
                .then()
                .body(valueOne, is(Integer.parseInt(valueTwo)));

    }

Вы можете его расширить как считаете нужным.

Шаг описываем максимально просто.

 @Step("получаем  {string} в  {string} и статус {string} в {string} и тело проверки {string}")
    @Затем("получаем  {string} в  {string} и статус {string} в {string} и тело проверки {string}")
    public void bodyPostCheck(String endpoint, String count, String statusCode, String countBody, String bodyCheck) {
        Response response = apiMap.getFindAll(endpoint)
                .findAll(count, statusCode);

        bodyCondition.bodyCheckSize(response, bodyCheck, countBody);
    }

Пример с библиотекой Hamsters, более ярко раскрывает этот принцип.

    @Step("Перейти на {string} и Получить себя и проверяем {string} {string}")
    @Затем("Перейти на {string} и Получить себя и проверяем {string} {string}")
    public void goToEndpointAndGetMyUserAndCheck(String endpoint, String expected, String actual) {

        CreateUser createUser = apiMap.getFindMe(endpoint)
                .findMe()
                .as(CreateUser.class);

        assertThat(createUser,
                hasProperty(expected, equalToIgnoringCase(actual)));

    }

ApiMap класс ,который через обычный switch возвращает нужный endpoint описанный ранее в шагах Cucumber.

Описание шага Перейти на -> endpoint -> подставить параметр запроса

bodyCheckSize делаем проверку запроса через аргументы, которые мы передаем

  • LSP принцип подстановки Барбары Лисков

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

https://ru.wikipedia.org/wiki/Принцип_подстановки_Барбары_Лисков

Наследуемся от абстрактного класса DataEntity, который будет хранить в себе переменные базового типа.

Подключаем в pom.xml

        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>1.0.2</version>
        </dependency>
 Faker faker = new Faker();
    FakeValuesService fakeValuesService = new FakeValuesService(new Locale("en-GB"),
    new RandomService());

    public String generate() {
        return UUID.randomUUID().toString();
    }

    public String firstName() {
        return faker.name().firstName();
        
    }

     public String mail() {
       return fakeValuesService.bothify("????##@gmail.com");
       
       }

    public String skype() {
        return fakeValuesService.bothify("????##");
        
    }

Библиотека очень гибкая и позволяет генерацию всего, что придет в голову.

https://github.com/DiUS/java-faker

  • ISP разделение интерфейсов

Есть класс User и Product

Пользователь воспроизводит все функции CRUD и может загружать себе картинки, а класс Product воспроизводит только CRUD.

public interface ImageEntity {

    void uploadBinaryFile(String ... value);
    void deleteBinaryFile(String o);
}
public class Product implements CrudEntity, FindAll

public class User implements CrudEntity, ImageEntity, FindAll, FindMe
  • Dependency inversion зависимость на абстракциях

Простыми словами мы НЕ должны каждый раз создавать объект, только задаем пример объекта с аргументами, а он собирается сам.

@Data
@Accessors(fluent = true)
@Component
@ToString
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CreateUser {

    @JsonProperty("fullName")
    private String fullName;

    @JsonProperty("telegram")
    private String telegram;

    @JsonProperty("login")
    private String login;

    @JsonProperty("skype")
    private String skype;

   @JsonProperty("employeeProducts")
    private List<Object> employeeProducts;

    @JsonProperty("phone")
    private String phone;

    @JsonProperty("id")
    private String id;

    @JsonProperty("email")
    private String email;




}

Если мы не будем прописывать поле, оно просто не создастся@JsonInclude(JsonInclude.Include.NON_NULL)

https://projectlombok.org/

Дает возможность создавать объект текучем стиле @Accessors(fluent = true)

пример

    new CreateUser().id("1")
                .city("2")
                .phone("3")
                .email("4");
    @Step("перейти на {string} обновить и статус код {string}")
    @Затем("перейти на {string} обновить и статус код {string}")
    public void updateUser(String endpoint, String code, Map<String, String> map) {
    

        EmployerProducts employerProducts = new EmployerProducts();
        CreateUser createUser = new CreateUser(
                map.get("1")
                , map.get("2")
                , map.get("3")
                , map.get("4")
                , map.get("5")
                , map.get("6")
                , map.get("7")
                , map.get("8")
                , map.get("9")
                , map.get("10")
                , map.get("11")
                , Collections.singletonList(
                employerProducts.roleId(map.get("12"))
                        .productId(map.get("13")))
                , map.get("14")
                , map.get("15") != null ? map.get("15") : UUID.randomUUID().toString()
                , map.get("16"), map.get("17"));

        apiMap.getSelectedCrud(endpoint).update(createUser, code);

    }

Тут мы через Cucumber передаем в Map аргументы String (vlue=key)

value поле которое описываем в сценарии

key кладется аргумент поля, которое мы указываем

 @employeeContact
  Структура сценария: API -> Employee обновление пользователя телефон,почта,телеграм
    Затем перейти на "пользователь" обновить и статус код "400"


      | workStartDate | 2022-02-25   |
      | city          | Moskov       |
      | workTimesheet | remote       |
      | phone         | <phone>      |
      | email         | <email>      |
      | skype         | <skype>      |
      | telegram      | <telegram>   |
      | id            | ключ |


    Примеры:
      | phone          | email              | skype  | telegram     |
      | +79199999992   | testovich@test.ru  | skype  | @testtelegram|
      | +79169999902   | @test.ru_testovich | skype  | @testtelegram|
     

Получается, что мы не зависим от конкретного объекта, сейчас он полностью абстрактен. Теперь мы можем написать сценарий на основе кейса, который написан по техникам тест дизайна и объект создастся не зависимо какие поля мы в него будем передавать. Если вы столкнетесь с тем, что у вас будут поля, которые передаются как массив, советую использовать stream

    ArrayList<Integer> weekDays = (ArrayList<Integer>) map.entrySet()
                .stream()
                .filter(entry -> entry.getKey().contains("Пример поля"))
                .filter(p -> p.getValue().length() > 0)
                .map(Map.Entry::getValue)
                .map(Integer::parseInt)
                .collect(Collectors.toList());

Вам будет достаточно указать
Пример поля 1 пример1

Пример поля 2

Пример поля 3 пример3

Будет создан Пример поля [пример1, пример3]

Надеюсь информация будет для вас полезна и вы будете придерживаться SOLID, чтобы ваша разработка стала

  • Легко поддерживаемой

  • Гибкой

  • Расширяемой

Теги:
Хабы:
Всего голосов 9: ↑5 и ↓4+5
Комментарии1

Публикации

Истории

Работа

Java разработчик
347 вакансий

Ближайшие события

7 – 8 ноября
Конференция byteoilgas_conf 2024
МоскваОнлайн
7 – 8 ноября
Конференция «Матемаркетинг»
МоскваОнлайн
15 – 16 ноября
IT-конференция Merge Skolkovo
Москва
22 – 24 ноября
Хакатон «AgroCode Hack Genetics'24»
Онлайн
28 ноября
Конференция «TechRec: ITHR CAMPUS»
МоскваОнлайн
25 – 26 апреля
IT-конференция Merge Tatarstan 2025
Казань