HimeraSearchDB
Carding_EbayThief
triada
CrackerTuch
d-shop
HimeraSearchDB

НОВОСТИ [Перевод] Пишем автотесты эффективно — Subcutaneous tests

Bonnie
Оффлайн
Регистрация
12.04.17
Сообщения
19.095
Реакции
107
Репутация
0
Давайте представим себе гипотетическую ситауацию (в которую мы регулярно, вляпываемся). Вас назначили на проект «запилить» автоматизацию. Вам дают огромный тест план с большим количеством (тысячи их!) «ручных» тестов, и говорят что надо что-то сделать, и вотпрямщас. А еще, чтоб быстро и стабильно.

Писать Unit тесты, или даже думать о — уже поздно, код продукта давным-давно написан. Ваше слово, товарищ автотестер!

c6l-p9jm_r8j9ix3dpkptsalx3i.jpeg


К счастью, есть небольшой трюк, который позволит и coverage повысить, и сделать тесты стабильными и быстрыми — Subcutaneous tests, но, обо всем по порядку.


Суть проблемы


Первый условный рефлекс автоматизатора — это взять Selenium (ну, или там, Selenide, или еще какую вундервафлю для UI тестов). Это такой стандарт индустрии, но есть много причин, почему «не взлетит»:

  • UI-тесты медленные. От этого никуда не деться. Их можно запускать параллельно, допиливать напильником и делать чуть-чуть быстрее, но они останутся медленными.
  • UI-тесты нестабильные. Отчасти потому, что они медленные. А еще потому, что Web-браузер и интерфейс пользователя не были созданы для того, чтобы ими управлял компьютер (в настоящее время данный тренд меняется, но не факт, что это хорошо).
  • UI-тесты — это наиболее сложные тесты в написании и поддержки. Они просто тестируют слишком много. (Это усиливается тем фактом, что, зачастую, люди берут «ручные» тест-кейсы и начинают их автоматизировать как есть, без учета разницы в ручном и автоматическом тестировании).
  • Нам говорят, что, якобы, UI-тесты эмулируют реального пользователя. Это не так. Пользователь не будет искать элемент на странице по . Пользователь не заполняет форму со скоростью света, и не «упадет» если какой-то элемент страницы не будет доступен в какую-то конкретную миллисекунду. И даже теперь, когда браузеры разрабатываются с учетом того, что браузером можно программно управлять — это всего-лишь эмуляция, даже если очень хорошая.
  • Кто-то скажет, что некоторый функционал просто нельзя протестировать иначе. Я скажу, что если есть функционал, который можно протестировать только UI тестами (за исключением самой UI логики) — это может быть хорошим признаком архитектурных проблем в продукте.

Единственный реальный плюс UI-тестов заключается в том, что они позволяют «накидать» более-менее полезные проверки без необходимости погружения и изучения кода самого продукта. Что вряд ли плюс в долгосрочной перспективе. Более подробно с объяснением, почему так, можно услышать в .

Альтернативное решение


В качестве очень простого случая, давайте рассмотрим приложение, состоящее из формы, куда можно ввести валидное имя пользователя. Если вы ввели имя пользователя, соответствующее правилам — User будет создан в системе и записан в Базу Данных.

efsxqs-1l5hrlvozwlt78knzps8.png


Исходный код приложения можно найти здесь: . Вы были предупрежденны — в приложении куча багов и нет модных тулов и фреймворков.

Если попробовать «накидать» чек лист для данного приложения, можно получить что-то вроде:
NumberStepsExpected results

1

1. Enter a valid user name
2. Click «Log in» button

1.
2. A new user is created.

2

1. Enter an empty user name
2. Click «Log in» button

1.
2. The error message is given.

Выглядит просто? Просто! Можно ли написать UI-тесты? Можно. Пример написанных тестов (вместе с полноценным ) можно найти в LoginFormTest.java если перейти на uitests метку в git (git checkout uitests):


public class LoginFormTest {

SelenideMainPage sut = SelenideMainPage.INSTANCE;
private static final String APPLICATION_URL = " ";

@BeforeClass
public static void setUpClass() {
final String[] args = {};
Main.main(args);
Configuration.browser = "firefox";
}

@Before
public void setUp() {
open(APPLICATION_URL);
}

@After
public void tearDown() {
close();
}

T Test
public void shouldBeAbleToAddNewUser() {
sut.setUserName("MyCoolNewUser");
sut.clickSubmit();
Assert.assertEquals("Status: user MyCoolNewUser was created", sut.getStatus());
Assert.assertTrue(sut.getUsers().contains("Name: MyCoolNewUser"));
}

T Test
public void shouldNotBeAbleToAddEmptyUseName() {
final int numberOfUsersBeforeTheTest = sut.getUsers().size();
sut.clickSubmit();
Assert.assertEquals("Status: Login cannot be empty", sut.getStatus());
Assert.assertEquals(numberOfUsersBeforeTheTest, sut.getUsers().size());
}
}



Немного метрик для данного кода:
Время выполнения: ~12 секунд (12 секунд 956 миллисекунд в последний раз, когда я запускал эти тесты)
Покрытие кода
Class: 100%
Method: 93.8% (30/32)
Line: 97.4% (75/77)

Теперь давайте предположим, что Функциональные автотесты могут быть написаны на уровне «сразу под» UI. Эта техника и называется Subcutaneous tests и была предложена Martin Fowler достаточно давно [ ].

Когда люди думают о «не UI» автотестах, зачастую они думают сразу о REST/SOAP или иже с ним API. Но API (Application Programming iInterface) — куда более широкое понятие, не обязательно затрагивающее HTTP и другие тяжеловесные протоколы.

Если мы поковыряем код продукта, мы можем найти кое-что интересненькое:

public class UserApplication {

private static IUserRepository repository = new InMemoryUserRepository();
private static UserService service = new UserService(); {
service.setUserRepository(repository);
}

public Map getUsersList() {
return getUsersList("N/A");
}

public Map addUser(final String username) {
final String status = service.addUser(username);
final Map model = getUsersList(status);
return model;
}

private Map getUsersList(String status) {
final Map model = new HashMap<>();
model.put("status", status);
model.put("users", service.getUserInfoList());
return model;
}
}



Когда мы кликаем что-то на UI — вызывается один из этим методов, или добавляется новый User, или возвращается список созданных User. Что, если мы используем эти методы напрямую? Ведь это самый настоящий API! И самое главное, что REST и иные API тоже работают по тому-же принципу — вызывают некий метод «уровня контроллера».

Используя напрямую эти методы, мы можем написать тест попроще да получше:

public class UserApplicationTest {

private UserApplication sut;

@Before
public void setUp() {
sut = new UserApplication();
}

T Test
public void shouldBeAbleToAddNewUser() {
final Map myCoolNewUser = sut.addUser("MyCoolNewUser");
Assert.assertEquals("user MyCoolNewUser was created", myCoolNewUser.get("status"));
Assert.assertTrue(((List) myCoolNewUser.get("users")).contains("Name: MyCoolNewUser"));
}

T Test
public void shouldNotBeAbleToAddEmptyUseName() {
final Map usersBeforeTest = sut.getUsersList();
final int numberOfUsersBeforeTheTest = ((List) usersBeforeTest.get("users")).size();
final Map myCoolNewUser = sut.addUser("");
Assert.assertEquals("Login cannot be empty", myCoolNewUser.get("status"));
Assert.assertEquals(numberOfUsersBeforeTheTest, ((List) myCoolNewUser.get("users")).size());
}
}


Этот код доступен по метке subctests:


git checkout subctests


Попробуем собрать метрики?
Time to execute: ~21 milliseconds
Покрытие кода:
Class: 77.8%
Method: 78.1 (30/32)
Line: 78.7 (75/77)

Мы потеряли немного покрытия, но скорость тестов выросла в 600 раз!!!​

Насколько важна\существенна потеря покрытия в данном случае? Зависит от ситуации. Мы потеряли немного glue code, который может быть (а может и не быть) важным (рекомендую в качестве упражнения определить, какой код потерялся).

Оправдывает ли данная потеря покрытия введения тяжеловесного тестирования на уровне UI? Это тоже зависит от ситуации. Мы можем, например:
  • Добавить один UI-тест для проверки glue code, или
  • Если мы не ожидаем частых изменений glue code — оставить его без автотестов, или
  • Если у нас есть какой-то объем «ручного» тестирования — есть отличный шанс, что проблемы с glue code будут замечены тестировщиком, или
  • Придумать что-то еще (тот же Canary deployment)


В итоге


  • Функциональные автотесты не обязательно писать на UI or REST/SOAP API уровне. Subcutaneous tests часто позволит протестировать тот-же функционал с бОльшей скоростью и стабильностью
  • Один из минусов подхода — определенная потеря покрытия
  • Один из способов избежать потери покрытия — “
  • Но даже при условии потери покрытия, прирост скорости и стабильности — значителен.
 
Сверху Снизу