JavaRush /Java Blogu /Random-AZ /Java Unit Testing: texnikalar, anlayışlar, təcrübə

Java Unit Testing: texnikalar, anlayışlar, təcrübə

Qrupda dərc edilmişdir
Bu gün siz testlərdə əhatə olunmayan bir proqram tapa bilməyəcəksiniz, buna görə də bu mövzu təcrübəsiz tərtibatçılar üçün həmişəkindən daha aktual olacaq: testlər olmadan heç bir yerə gedə bilməzsiniz. Reklam olaraq keçmiş yazılarıma baxmağı təklif edirəm. Onlardan bəziləri testləri əhatə edir (və buna görə də məqalələr çox faydalı olacaq):
  1. MySql-i əvəz etmək üçün MariaDB istifadə edərək verilənlər bazasının inteqrasiya sınağı
  2. Çoxdilli tətbiqin həyata keçirilməsi
  3. Faylların proqrama və onlar haqqında məlumatların verilənlər bazasına saxlanması
Prinsipcə hansı test növlərinin istifadə edildiyini nəzərdən keçirək və bundan sonra vahid testi haqqında bilmək lazım olan hər şeyi ətraflı öyrənəcəyik.

Test növləri

Test nədir? Wiki-nin dediyi kimi: “ Sınaq və ya test sistemi müxtəlif vəziyyətlərdə yerləşdirmək və orada müşahidə olunan dəyişiklikləri izləməklə sistemin əsas proseslərini öyrənmək üsuludur.” Başqa sözlə, bu, müəyyən vəziyyətlərdə sistemimizin düzgün işləməsinin testidir. Vahid testi haqqında hər şey: metodlar, anlayışlar, təcrübə - 2Gəlin görək hansı test növləri var:
  1. Vahid sınağı, vəzifəsi sistemin hər bir modulunu ayrıca yoxlamaq olan testlərdir. Arzu edilir ki, bunlar sistemin minimal bölünə bilən hissələri, məsələn, modullar.

  2. Sistem testi tətbiqin daha böyük bir hissəsinin və ya bütövlükdə sistemin işini yoxlamaq üçün yüksək səviyyəli testdir.

  3. Reqressiya testi yeni funksiyaların və ya səhvlərin aradan qaldırılmasının tətbiqin mövcud funksionallığına təsir edib-etmədiyini və köhnə səhvlərin yenidən görünüb-görünmədiyini yoxlamaq üçün istifadə edilən sınaqdır.

  4. Funksional sınaq tətbiqin bir hissəsinin spesifikasiyalarda, istifadəçi hekayələrində və s.-də göstərilən tələblərə uyğunluğunun yoxlanılmasıdır.

    Funksional test növləri:

    • sistemin daxili tətbiqi bilikləri ilə ərizənin bir hissəsinin tələblərə uyğunluğu üçün “ağ qutu” testi ;
    • Tətbiqin bir hissəsinin sistemin daxili tətbiqindən xəbəri olmadan tələblərə uyğunluğu üçün "qara qutu" testi .
  5. Performans testi bir sistemin və ya onun bir hissəsinin müəyyən bir yük altında işləmə sürətini təyin etmək üçün yazılmış bir test növüdür.
  6. Yük testi - standart yüklər altında sistemin dayanıqlığını yoxlamaq və tətbiqin düzgün işlədiyi maksimum mümkün zirvəni tapmaq üçün hazırlanmış testlər.
  7. Стресс-тестирование (stress testing) — вид тестирования, предназначенный для проверки работоспособности applications при нестандартных нагрузках и для определения максимально возможного пика, при котором система не упадёт.
  8. Тестирование безопасности (security testing) — тесты, используемые для проверки безопасности системы (от атак хакеров, вирусов, несанкционированного доступа к конфиденциальным данным и прочих радостей жизни).
  9. Тестирование локализации (localization testing) — это тесты локализации для applications.
  10. Юзабorти тестирование (usability testing) — вид тестирования, направленный на проверку удобства использования, понятности, привлекательности и обучаемости для пользователей.
  11. Это всё звучит хорошо, но How оно происходит на практике? Все просто: используется пирамида тестирования Майка Кона: Vahid testi haqqında hər şey: metodlar, anlayışlar, təcrübə - 4Это упрощенный вариант пирамиды: сейчас её делят на более мелкие детали. Но сегодня мы не будем извращаться и рассмотрим самый простой вариант.
    1. Unit — модульные тесты, применяемые в различных слоях applications, тестирующие наименьшую делимую логику applications: например, класс, но чаще всего — метод. Эти тесты обычно стараются по максимуму изолировать от внешней логики, то есть создать иллюзию того, что остальная часть applications работает в стандартном режиме.

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

    2. Integration — интеграционное тестирование. Оно проверяет более крупные кусочки системы, то есть это либо объединение нескольких кусочков логики (несколько методов or классов), либо корректность работы с внешним компонентом. Этих тестов How правило меньше, чем Unit, так How они тяжеловеснее.

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

    3. UI — тесты, которые проверяют работу пользовательского интерфейса. Они затрагивают логику на всех уровнях applications, из-за чего их еще называют сквозными. Их How правило в разы меньше, так они наиболее тяжеловесны и должны проверять самые необходимые (используемые) пути.

      На рисунке выше мы видим соотношение площадей разных частей треугольника: примерно такая же пропорция сохраняется в количестве этих тестов в реальной работе.

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

    Ключевые понятия юнит-тестирования

    Покрытие тестов (Code Coverage) — одна из главных оценок качества тестирования applications. Это процент codeа, который был покрыт тестами (0-100%). На практике многие гонятся за этим процентом, с чем я не согласен, так How начинается навешивание тестов там, где они не нужны. Например, у нас в сервисе есть стандартные CRUD (create/get/update/delete) операции без дополнительной логики. Эти методы — сугубо посредники, делегирующие работу слою, работающему с репозиторием. В данной ситуации нам нечего тестировать: разве то, что вызывает ли данный метод — метод из дао, но это не серьёзно. Для оценки покрытия тестами обычно используют дополнительные инструменты: JaCoCo, Cobertura, Clover, Emma и т.д. Для более детального изучения данного вопроса держи пару годных статей: TDD (Test-driven development) — разработка через тестирование. В рамках этого подхода в первую очередь пишется тест, который будет проверять определенный code. Получается тестирование чёрного ящика: мы знаем, что есть на входе и знаем, что должно получиться на выходе. Это позволяет избежать дублирования codeа. Разработка через тестирование начинается с проектирования и разработки тестов для каждой небольшой функциональности applications. В подходе TDD, во-первых, разрабатывается тест, который определяет и проверяет, что будет делать code. Основная цель TDD — сделать code более понятным, простым и без ошибок. Vahid testi haqqında hər şey: üsullar, anlayışlar, təcrübə - 6Подход состоит из таких составляющих:
    1. Пишем наш тест.
    2. Запускаем тест, прошел он or нет (видим, что всё красное — не психуем: так и должно быть).
    3. Добавляем code, который должен удовлетворить данный тест (запускаем тест).
    4. Выполняем рефакторинг codeа.
    Исходя из того, что модульные тесты являются наименьшими elementми в пирамиде автоматизации тестирования, TDD основан на них. С помощью модульных тестов мы можем проверить бизнес-логику любого класса. BDD (Behavior-driven development) — разработка через поведение. Это подход основан на TDD. Если говорить точнее, он использует написанные понятным языком примеры (How правило на английском), которые иллюстрируют поведение системы для всех, кто участвует в разработке. Не будем углубляться в данный термин, так How он в основном затрагивает тестировщиков и бизнес-аналитиков. Тестовый сценарий (Test Case) — сценарий, описывающий шаги, конкретные условия и параметры, необходимые для проверки реализации тестируемого codeа. Фикстуры (Fixture) — состояние среды тестирования, которое необходимо для успешного выполнения испытуемого метода. Это заранее заданный набор an objectов и их поведения в используемых условиях.

    Этапы тестирования

    Тест состоит из трёх этапов:
    1. Задание тестируемых данных (фикстур).
    2. Использование тестируемого codeа (вызов тестируемого метода).
    3. Проверка результатов и сверка с ожидаемыми.
    Vahid testi haqqında hər şey: metodlar, anlayışlar, təcrübə - 7Whatбы обеспечить модульность теста, нужно нужно изолироваться от других слоев applications. Сделать это можно помощью заглушек, моков и шпионов. Мок (Mock) — an objectы, которые настраиваются (например, специфично для каждого теста) и позволяют задать ожидания вызовы методов в виде ответов, которые мы планируем получить. Проверки соответствия ожиданиям проводятся через вызовы к Mock-an objectм. Заглушки (Stub) — обеспечивают жестко зашитый ответ на вызовы во время тестирования. Также они могут сохранять в себе информацию о вызове (например, параметры or количество этих вызовов). Такие иногда называют своим термином — шпион (Spy). Иногда эти термины stubs и mock путают: разница в том, что стаб ничего не проверяет, а лишь имитирует заданное состояние. А мок — это an object, у которого есть ожидания. Например, что данный метод класса должен быть вызван определенное число раз. Иными словами, ваш тест никогда не сломается из-за «стаба», а вот из-за мока может.

    Среды тестирования

    Итак, теперь ближе к делу. Для Java доступно несколько сред тестирования (фреймворков). Самые популярные из них — JUnit и TestNG. Для нашего обзора мы используем: Vahid testi haqqında hər şey: üsullar, anlayışlar, təcrübə - 8JUnit тест представляет собой метод, содержащийся в классе, который используется только для тестирования. Класс, How правило, называется так же, How и класс, который он тестирует с +Test в конце. Например, CarService→ CarServiceTest. Система сборки Maven автоматически включает такие классы в тестовую область. По сути этот класс и называется тестовым. Немного пройдёмся по базовым annotationм: @Test — определение данного метода в качестве тестируемого (по сути — метод, помеченный данной аннотацией и есть модульный тест). @Before — помечается метод, который будет выполняться перед каждым тестом. Например, заполнение тестовых данных класса, чтение входных данных и т. д. @After — ставится над методом, который будет вызывать после каждого теста (чистка данных, восстановление дефолтных значений). @BeforeClass — ставится над методом — аналог @Before. Но этот метод вызывается лишь однажды перед всеми тестами для данного класса и поэтому должен быть статическим. Он используется для выполнения более тяжелых операций, How например подъем тестовой БД. @AfterClass — противоположность @BeforeClass: исполняется один раз для данного класса, но исполняется после всех тестов. Используется, например, для очистки постоянных ресурсов or отключения от БД. @Ignore — отмечает, что метод ниже отключен и будет игнорироваться при общей прогонке тестов. Используется в разных случаях, например, если изменor базовый метод и не успели переделать под него тест. В таких случаях ещё желательно добавить описание — @Ignore("Some description"). @Test (expected = Exception.class) — используется для отрицательных тестов. Это тесты, которые проверяют, How ведёт себя метод в случае ошибки, то есть тест ожидает, что метод выкинет некоторое исключение. Такой метод обозначается аннотацией @Test, но с указанием ошибки для отлова. @Test(timeout=100) — проверяет, что метод исполняется не более чем 100 миллисекунд. @Mock — используется над полем класс для задания данного an object моком (это не из Junit библиотеки, а из Mockito), и если нам будет необходимо, мы зададим поведение мока в конкретной ситуации, непосредственно в методе теста. @RunWith(MockitoJUnitRunner.class) — метод ставится над классом. Он и является кнопкой для прогона тестов в нем. Runner-ы могут быть различными: например, есть такие: MockitoJUnitRunner, JUnitPlatform, SpringRunner и т. д.). В JUnit 5 аннотацию @RunWith заменor более мощной аннотацией @ExtendWith. Взглянем на некоторые методы сравнения результатов:
    • assertEquals(Object expecteds, Object actuals) — проверяет, равны ли передаваемые обьекты.
    • assertTrue(boolean flag) — проверяет, возвращает ли переданное meaning — true.
    • assertFalse(boolean flag) — проверяет, возвращает ли переданное meaning — false.
    • assertNull(Object object) – проверяет, является ли an object нулевым (null).
    • assertSame(Object firstObject, Object secondObject) — проверяет, ссылаются ли передаваемые значения на один и тот же обьект.
    • assertThat(T t, Matcher<T> matcher) — проверяет, удовлетворяет ли t условию, указанному в matcher.
    Ещё есть полезная форма сравнения из assertj — assertThat(firstObject).isEqualTo(secondObject) Здесь я рассказал о базовых методах, так How остальные — это различные вариации приведенных выше.

    Практика тестирования

    А теперь давайте рассмотрим приведенный выше материал на конкретном примере. Будем тестировать метод для сервиса — update. Рассматривать слой дао не будем, так How он у нас дефолтный. Добавим стартер для тестов:
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-test</artifactId>
       <version>2.2.2.RELEASE</version>
       <scope>test</scope>
    </dependency>
    Итак, класс сервиса:
    @Service
    @RequiredArgsConstructor
    public class RobotServiceImpl implements RobotService {
       private final RobotDAO robotDAO;
    
       @Override
       public Robot update(Long id, Robot robot) {
           Robot found = robotDAO.findById(id);
           return robotDAO.update(Robot.builder()
                   .id(id)
                   .name(robot.getName() != null ? robot.getName() : found.getName())
                   .cpu(robot.getCpu() != null ? robot.getCpu() : found.getCpu())
                   .producer(robot.getProducer() != null ? robot.getProducer() : found.getProducer())
                   .build());
       }
    }
    8 — вытягиваем обновляемый обьект из БД 9-14 — создаём an object через билдер, если в приходящем an objectе есть поле — задаем его, если нет — оставляем то, что есть в БД И смотрим наш тест:
    @RunWith(MockitoJUnitRunner.class)
    public class RobotServiceImplTest {
       @Mock
       private RobotDAO robotDAO;
    
       private RobotServiceImpl robotService;
    
       private static Robot testRobot;
    
       @BeforeClass
       public static void prepareTestData() {
           testRobot = Robot
                   .builder()
                   .id(123L)
                   .name("testRobotMolly")
                   .cpu("Intel Core i7-9700K")
                   .producer("China")
                   .build();
       }
    
       @Before
       public void init() {
           robotService = new RobotServiceImpl(robotDAO);
       }
    1 — наш Runner 4 — изолируем сервис от слоя дао, подставляя мок 11 — задаем для класса тестовую сущность (ту, которую мы будем юзать в качестве испытуемого хомячка) 22 — задаём an object сервиса, который мы и будем тестить
    @Test
    public void updateTest() {
       when(robotDAO.findById(any(Long.class))).thenReturn(testRobot);
       when(robotDAO.update(any(Robot.class))).then(returnsFirstArg());
       Robot robotForUpdate = Robot
               .builder()
               .name("Vally")
               .cpu("AMD Ryzen 7 2700X")
               .build();
    
       Robot resultRobot = robotService.update(123L, robotForUpdate);
    
       assertNotNull(resultRobot);
       assertSame(resultRobot.getId(),testRobot.getId());
       assertThat(resultRobot.getName()).isEqualTo(robotForUpdate.getName());
       assertTrue(resultRobot.getCpu().equals(robotForUpdate.getCpu()));
       assertEquals(resultRobot.getProducer(),testRobot.getProducer());
    }
    Здесь мы видим четкое разделение теста на три части: 3-9 — задание фикстур 11 — выполнение тестируемой части 13-17 — проверка результатов Подробнее: 3-4 — задаём поведение для мока дао 5 — задаём экземпляр, который мы будем апдейтить поверх нашего стандартного 11 — используем метод и берём результирующий экземпляр 13 — проверяем, что он не ноль 14 — сверяем айди результата и заданные аргументы метода 15 — проверяем, обновилось ли Name 16 — смотрим результат по cpu 17 – так How в экземпляре для обновления мы не задавали это поле, оно должно остаться прежним, проверяем это. Vahid testi haqqında hər şey: metodlar, anlayışlar, təcrübə - 9Запускаем: Vahid testi haqqında hər şey: texnikalar, anlayışlar, təcrübə - 10Тест зелёный, можно выдыхать)) Итак, подведём итоги: тестирование улучшает качество codeа и делает процесс разработки более гибким и надёжный. Представьте себе, How много сил мы потратим при изменении дизайна программного обеспечения с сотнями файлов классов. Когда у нас есть модульные тесты, написанные для всех этих классов, мы можем уверенно провести рефакторинг. И самое главное — это помогает нам легко находить ошибки во время разработки. Гайз, на этом у меня сегодня всё: сыпем лайки, пишем комменты))) Vahid testi haqqında hər şey: üsullar, anlayışlar, təcrübə - 11
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION