Автоматическая проверка названий тестовых методов для Java

digzknhlkqkajlrw38bfwdgru3s.png

Без сомнений, автоматические тесты важны для поддержания высокого качества кода, снижения вероятности повторного возникновения ранее обнаруженных ошибок и уменьшения времени подготовки к релизу. Но также не менее важно обеспечивать качество и соответствие стандартам кода самих тестов. В ряде случаев для проверок можно использовать существующие инструменты проверки стиля кода (checkstyle, pmd, sonarqube), но кроме самого кода, хороший тест также должен иметь осмысленные названия тестовых методов и давать адекватное описание причины возникшей ошибки. В этой статье мы рассмотрим использование maven-плагина статического анализа jtcop для поддержания единого стандарта именований для тестового кода.

При оценке качества тестового кода будем опираться на несколько правил (прежде всего говорим о юнит-тестах):

  • тестовые классы должны явно показывать, какой класс в коде проверяется (идеально, если структура каталогов для юнит-тестов повторяет структуру каталогов src);

  • из названия тестового метода должно быть однозначно понятно, какая функциональность проверяется, при этом желательно придерживаться единого стиля именования и не использовать числа;

  • для assert должны использоваться семантически осмысленные сообщения (с описанием ожидаемого поведения или расхождения между фактическим и ожидаемым результатом);

  • по возможности тестовые данные должны быть сохранены отдельно от теста и подключаться как источник данных (DataProvider);

  • тесты должны обладать хорошей воспроизводимостью, при повторном запуске либо в 100% случаев успешно проходить, либо всегда проваливаться;

  • при наличии соответствующей функции для проверки результата (например, assertEquals) предпочтительно использовать её, а не общую проверку assertTrue или возможности assert в JVM;

  • в коде тестовых методов нужно избегать дублирования кода (в общем случае можно перенести его в setup/teardown-методы или в DataProvider);

  • функции инициализации/завершения тестового сценария и шага должны быть неизбыточными, но при этом должны обеспечивать возврат системы в начальное состояние (для воспроизводимости результатов независимо от последовательности выполнения тестовых методов);

  • кроме положительных сценариев, также должны быть проверены негативные и пограничные сценарии (например, обработку некорректных входных данных);

  • для управления запуском тестов желательно использовать тестовые фреймворки (например, JUnit4/5 или TestNG).

Есть много практических подходов к определению хорошего теста:

  • один тест — один assert (проверяем тестом только одно изменение состояния или результат);

  • шаблон AAA (Arrange, Act, Assert) — сначала готовим начальное состояние, выполняем изменения и проверяем ожидаемое состояние;

  • все, что не должно попасть в контур тестирования, должно быть заменено на тестовые двойники (mock/stub);

  • в тестах не должно быть if и результат теста не должен зависеть от порядка выполнения тестовых методов.

Рассмотрим теперь библиотеки и инструменты для автоматической проверки качества тестового кода и именований методов.

Оценка качества кодовой базы

Несмотря на наличие большого количества инструментов для выполнения статического анализа, для оценки тестов по вышеперечисленным правилам требуется специальная настройка правил (например, для проверки названий тестовых методов). Так, например в SonarQube есть большой набор правил статического анализа для тестов, но даже они не покрывают все аспекты качественного теста. Для решения проблемы проверки качества теста можно использовать maven-плагин jcop и далее мы рассмотрим примеры его использования для обнаружения неудачных практик для тестовых сценариев и способов их улучшения.

Создадим простой пример класса для выполнения теста:

public class Calculator {
    int listSum(Collection items) {
        return items.stream().mapToInt(Integer::intValue).sum();
    }
}

и тест для него

public class CalculatorTest {
    static Calculator calculator;

    @BeforeClass
    public static void initialize() {
        calculator = new Calculator();
    }

    @Test
    public void testSum() {
        assertEquals(5, calculator.listSum(Arrays.asList(2, 3)));
    }
}

Давайте запустим анализатор и посмотрим, какие ошибки будут обнаружены в этом тесте и как мы могли бы его улучшить:

mvn com.github.volodya-lombrozo:jtcop-maven-plugin:check

После запуска будут обнаружены три ошибки:

  • Test name 'testSum' doesn’t follow naming rules, because test name doesn’t have to contain the word 'test' (можно отключить через добавление аннотации @SuppressWarnings("JTCOP.RuleNotContainsTestWord"). Ожидается, что в названии теста не будет содержаться слово «test» (например, можно переименовать calculatorSumIsValid);

  • Test name 'testSum' doesn’t follow naming rules, because the test name has to be written using present tense (правило для отключения — JTCOP.RulePresentTense), ожидается что название метода будет записано глаголом в настоящем времени и существительным (например, calculatesSum);

  • Method 'testSum' has assertion without message: 'assertEquals (5, calculator.listSum (Arrays.asList (2, 3)))' (правило — JTCOP.RuleAssertionMessage), может быть исправлено добавлением сообщения, поясняющего причину ошибки при срабатывании этого assert. Для исправления изменим название тестового метода и добавим сообщение в assert:

    @Test
    public void calculatesSum() {
        assertEquals("Sum is not valid", 5, calculator.listSum(Arrays.asList(2, 3)));
    }

Какие еще проверки используются при валидации кода теста:

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

  • правило JTCOP.RuleNotUsesSpecialCharacters — отсутствие специальных символов (_, $) в названии тестового метода;

  • правило JTCOP.RulePresentTense — название тестового метода должно быть записано глаголом в настоящем времени + существительное (т.е. calculatesSum является правильным названием, в то время как passwordIsCreated — нет);

  • правило JTCOP.RuleCorrectTestName — проверка названия тестового класса (должен начинаться или заканчиваться на Test, TestCase, IT, ITCase);

  • правило JTCOP.RuleAssertionMessage — проверка на осмысленное сообщение в assert (с описанием ожидаемого поведения или с включением ожидаемых и вычисленных значений);

  • правило JTCOP.RuleInheritanceInTests — тестовый класс не должен использовать наследование (усложняет дальнейшую поддержку);

  • правило JTCOP.RuleAllTestsHaveProductionClass — проверяет соответствие теста реальному классу (с одним из разрешенных префиксов или суффиксов), например для класса Calculator название теста может быть CalculatorTest;

  • правило JTCOP.LineHitterRule — проверка наличия кода в тесте, который используется только для повышения code coverage, но фактически ничего не проверяет (без соответствующих assert или с assert, которые не используют результаты вычисления кода или всегда считаются правильными);

  • правило JTCOP.RuleOnlyTestMethods проверяет, что тестовый класс содержит только тестовые методы;

  • правило JTCOP.RuleNotSpam — проверка, что название тестового метода является осмысленным, а не просто случайным набором символов (или несколькими последовательными одинаковыми символами);

Проверка может быть интегрирована в процесс сборки через добавление плагина (автоматически присоединяется к фазе verify):


  com.github.volodya-lombrozo
  jtcop-maven-plugin
  1.2.0
  
    
      
        check
      
    
  

В gradle плагин может быть добавлен через использование maven wrapper и вызов через него задачи com.github.volodya-lombrozo:jtcop-maven-plugin:check.

Инструмент jtcop, совместно с другими плагинами для валидации кода (как основного, так и для тестов), такими как spotbugs (com.github.com:spotbugs-maven-plugin:4.8:2), SonarQube Scanner, CheckStyle и другими, может быть полезен для повышения читаемости и качества тестового кода для приложений на Java.

В завершение хочу пригласить вас на бесплатный урок, в рамках которого вы научитесь запускать тесты с помощью Selenium Grid 4. Также будет рассмотрен параллельный запусĸ тестов через JUnit/TestNG.

© Habrahabr.ru