#java #unit-testing #static #mockito
Вопрос:
Итак, я пишу тест junit и, похоже, не могу понять, почему он терпит неудачу. Я использую Mockito.mockStatic для того, чтобы издеваться InetAddres.class. Запуск модульных тестов сразу завершается неудачей. Запуск их по отдельности завершается успешно. Я понимаю, что статические блоки инициализируются один раз. Чего я, кажется, не могу понять, так это почему хост класса не инициализируется заново с каждым модульным тестом. Любая помощь будет признательна
J
Вот мой код:
import org.junit.jupiter.api.Test;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import java.net.InetAddress;
import java.net.UnknownHostException;
import static org.assertj.core.api.Assertions.assertThat;
class HostTest {
@Test
void testLocalhost() {
try (MockedStatic<InetAddress> inetAddressMockedStatic = Mockito.mockStatic(InetAddress.class)) {
InetAddress inetAddress = Mockito.mock(InetAddress.class);
Mockito.when(inetAddress.getHostName()).thenReturn("LOCALHOST");
inetAddressMockedStatic.when(InetAddress::getLocalHost).thenReturn(inetAddress);
assertThat(Host.getLOCALHOST()).isEqualTo("LOCALHOST");
Mockito.reset(inetAddress);
}
}
@Test
void testIP() {
try (MockedStatic<InetAddress> inetAddressMockedStatic = Mockito.mockStatic(InetAddress.class)) {
InetAddress inetAddress = Mockito.mock(InetAddress.class);
Mockito.when(inetAddress.getHostAddress()).thenReturn("127.0.0.1");
inetAddressMockedStatic.when(InetAddress::getLocalHost).thenReturn(inetAddress);
assertThat(Host.getIP()).isEqualTo("127.0.0.1");
}
}
@Test
void testUnkownHostExceptionIP() {
try (MockedStatic<InetAddress> inetAddressMockedStatic = Mockito.mockStatic(InetAddress.class)) {
inetAddressMockedStatic.when(InetAddress::getLocalHost).thenThrow(UnknownHostException.class);
assertThat(Host.getIP()).isEqualTo("Unkown ip");
}
}
@Test
void testUnkownHostExceptionLocalhost() {
try (MockedStatic<InetAddress> inetAddressMockedStatic = Mockito.mockStatic(InetAddress.class)) {
inetAddressMockedStatic.when(InetAddress::getLocalHost).thenThrow(UnknownHostException.class);
assertThat(Host.getLOCALHOST()).isEqualTo("Unkown hostname");
}
}
}
import java.net.InetAddress;
import java.net.UnknownHostException;
public class Host {
private static String LOCALHOST;
private static String IP;
static {
try {
InetAddress localhost = InetAddress.getLocalHost();
LOCALHOST = localhost.getHostName();
IP = localhost.getHostAddress();
} catch (UnknownHostException e) {
LOCALHOST = "Unkown hostname";
IP = "Unkown ip";
}
}
public static String getLOCALHOST() {
return LOCALHOST;
}
public static String getIP() {
return IP;
}
}
Ответ №1:
Статический инициализатор выполняется только один раз, когда класс загружен. Это означает, что он будет выполняться только для первого тестового случая с использованием Host
класса.
В вашем примере после testLocalhost
запуска класс используется в строке Host.getLOCALHOST()
, к этому моменту был выполнен его инициализатор. Он никогда не запускается снова на протяжении всех остальных модульных тестов.
Если вы измените порядок этих тестовых случаев, вы получите другой результат.
Судя по вашим тестовым случаям, вы могли бы сделать несколько вещей, чтобы код соответствовал вашим ожиданиям. Поскольку IP-адрес и имя хоста будут меняться на протяжении всего выполнения вашей программы, они не должны быть статическими элементами, заданными в блоке статического инициализатора.
- Избавьтесь от общего состояния. Если не учитывать параллелизм и видимость памяти,
static
члены будут видны всем экземплярам класса. Опуститеstatic
ключевое слово и превратите их в обычные поляpublic class Host { private final String hostName; private final String ip; // Constructor, use this to build new instances public Host(String hostName, String ip) { this.hostName = hostName; this.ip = ip; } // No longer static, this is now an instance method public getHostName() { return this.hostName; } public getIp() { return this.ip; } }
- Создавайте экземпляры своего класса, передавая аргументы конструктору, чтобы настроить его поведение.
// Host.getIp(); // If IP and host name can vary, don't make them static InetAddress localhost = InetAddress.getLocalHost(); // build a new instance of Host, provide the relevant data at construction time Host testedHost = new Host(localhost.getHostName(), localhost.getHostAddress()); // call the instance method, this doesn't affect your other tests assertThat(testedHost.getIp()).is(someIp); // at this point, the Host instance you created may be garbage-collected to free memory (you don't need to do that yourself)
Теперь каждый тестовый случай будет независим от других. Просто создавайте новый экземпляр
Host
каждый раз, когда он вам понадобится. - Избавьтесь от статических насмешек. обратите внимание, как
InetAddress
вызовы методов были перемещены за пределыHost
класса. Передавая их через конструктор, вы облегчаете тестирование кода. Достигается инверсия управления.
Вместо общедоступного конструктора можно использовать заводской метод. Суть в том, что если вы хотите, чтобы класс изменил свое поведение, обычно лучше создавать новые экземпляры и инкапсулировать любое состояние.
Статические классы и члены лучше подходят для таких вещей, как неизменяемые компоненты, которые не будут изменяться в ходе выполнения вашей программы, или служебные методы, которые не зависят от какого-либо внутреннего состояния, т. е. чистые функции.