У меня есть класс со статической переменной и функцией, которая использует эту переменную. Мне нужно написать модульный тест для метода shouldCreateNewToken().

public class Auth {
    private static Instant tokenExpiration = Instant.now();

    public boolean shouldCreateNewToken() {
        Instant currentTimestamp = Instant.now(); 
        Duration differenceInTime = Duration.between(currentTimestamp, tokenExpiration);

        if (currentTimestamp.compareTo(tokenExpiration) > 0 || differenceInTime.getSeconds() < 120) {
            return true;
        }   
        return false;
    }
}
0
Sweta Sharma 4 Фев 2022 в 12:11

3 ответа

Во-первых, почти невозможно правильно протестировать модуль, когда используются статические переменные. Изменение значения переменной или внутреннего состояния может привести к утечке в следующих тестах, и это намного хуже, когда несколько тестов выполняются параллельно (например, Surfire выполняет тесты параллельно).

Итак, первое, что нужно сделать, это реорганизовать код, чтобы не использовать статические переменные. Например, вам следует использовать внедрение зависимостей, если вы используете фреймворк с такой функцией, например Spring.

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

1
QuentinC 4 Фев 2022 в 12:37
  • У меня может быть недостаточно контекста о том, как вы планируете использовать класс Auth, однако, исходя из того, что я вижу, ваш метод shouldCreateNewToken ВСЕГДА будет возвращать true.

    Статические переменные создаются до выполнения main. Когда ваша программа/служба инициализируется, у вас уже будет значение для tokenExpiration (скажем, 10), и когда ваш метод будет выполнен, он всегда будет выбирать значение больше, чем tokenExpiration (скажем, 11 ,12,...)

    Здесь я поделился кратким руководством, чтобы понять это поведение: https://www.baeldung.com /java-статические-переменные-инициализация

  • Что касается фиктивного вопроса, который у вас есть, есть 2 подхода, которые можно использовать:

    1. Вы можете использовать mockStatic из Mockito или даже PowerMockito. имеет аналогичный подход (в зависимости от того, что вы используете/предпочитаете) и издевается над объектом Instant

      Мгновенное мгновение = Instant.now();   try (MockedStatic mockedInstant = Mockito.mockStatic(Instant.class)) {      mockedInstant.when(сейчас()).thenReturn(мгновенно);      ...  }    
    2. Второй подход заключается в передаче момента tokenExpiration в качестве параметра конструктора класса Auth, таким образом вы 1) избегаете насмешек, позволяя вам передать нужный момент, и 2) позволяете вам создавать различные конфигурации аутентификации в зависимости от ваших потребностей.

      Аутентификация класса {       закрытый окончательный Instant tokenExpiration;       публичная аутентификация (Instant tokenExpiration) {          this.tokenExpiration = tokenExpiration;      }       ...   }    
0
AlexMelgar 4 Фев 2022 в 12:42
Auth.tokenExpiration не является окончательным, поэтому его можно изменить позже, когда будет выпущен новый токен. Как вы сказали: вы упускаете какой-то контекст… 😉
 – 
tquadrat
4 Фев 2022 в 12:51
Согласен, мог бы и так, но в том виде, в каком он сейчас написан, не похоже, что он изменится
 – 
AlexMelgar
4 Фев 2022 в 12:57
Признались — мы все упускаем из виду этот контекст, но по названиям классов и методам, которые нам показали, мы можем догадаться о некоторых из них.
 – 
tquadrat
4 Фев 2022 в 13:25

В текущей форме вы не можете протестировать этот код.

Хотя вы все еще можете использовать Reflection для изменения Auth.tokenExpiration для тестирования, вы не можете установить локальную переменную currentTimestamp.

Измените свой код следующим образом:

public class Auth 
{
  private static Clock clock = Clock.systemUTC(); // NOT final!
  private static Instant tokenExpiration = Instant.now( clock );  // NOT final!

  public boolean shouldCreateNewToken() 
  {
    Instant currentTimestamp = Instant.now( clock ); 
    Duration differenceInTime = Duration.between( currentTimestamp, tokenExpiration );

    if( currentTimestamp.isAfter( tokenExpiration ) || differenceInTime.getSeconds() < 120) 
    {
      return true;
    }   
    return false;
  }
}

Это позволяет вам манипулировать временем: с помощью отражения вы можете установить Auth.tokenExpiration на произвольный момент времени, а затем изменить Auth.clock перед каждым вызовом Auth.shouldCreateNewToken().

Взгляните на Javadoc for Clock для получения подробной информации.

Я предполагаю, что вам не нужна помощь в том, как использовать Reflection или как написать сам тест?

0
tquadrat 4 Фев 2022 в 12:46