Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Stub: Вспомогательный объект для тестирования
Stub (заглушка, имитация) — это упрощенная реализация зависимости объекта, используется в юнит-тестировании для замены реальных компонентов (баз данных, API, файловых систем и т.д.). Stub предоставляет фиксированные ответы на вызовы методов и не содержит реальной логики.
Основное назначение Stub
Stub используется для:
- Изоляции компонентов — отделить тестируемый код от внешних зависимостей
- Контролирования зависимостей — задать предсказуемые возвращаемые значения
- Ускорения тестов — заменить медленные операции (БД, сеть) на быстрые
- Симуляции ошибок — тестировать обработку исключений
Stub vs Mock vs Fake
Stub: Возвращает фиксированные значения, не проверяет вызовы
Mock: Проверяет как были вызваны методы
Fake: Реальная упрощенная реализация
Spy: Обёртка над реальным объектом с логированием вызовов
Пример: Stub для базы данных
Реальный код (без Stub)
public interface UserRepository {
User findById(Long id);
void save(User user);
void delete(Long id);
}
public class UserService {
private UserRepository repository;
public UserService(UserRepository repository) {
this.repository = repository;
}
public String getUserEmail(Long userId) {
User user = repository.findById(userId);
if (user != null) {
return user.getEmail();
}
return null;
}
}
Тест БЕЗ Stub (плохо)
public class UserServiceTest {
@Test
public void testGetUserEmail() {
// Проблема: зависит от реальной БД
UserRepository repository = new PostgresUserRepository();
UserService service = new UserService(repository);
// Тест медленный, зависит от БД, непредсказуемый
String email = service.getUserEmail(1L);
assertEquals("user@example.com", email);
}
}
Тест с Stub (хорошо)
public class UserServiceTest {
// Stub: простая реализация interface
private static class StubUserRepository implements UserRepository {
@Override
public User findById(Long id) {
// Возвращает фиксированное значение
if (id == 1L) {
User user = new User();
user.setId(1L);
user.setEmail("john@example.com");
return user;
}
return null;
}
@Override
public void save(User user) {
// Ничего не делает для Stub
}
@Override
public void delete(Long id) {
// Ничего не делает для Stub
}
}
@Test
public void testGetUserEmail() {
// Используем Stub вместо реальной БД
UserRepository stub = new StubUserRepository();
UserService service = new UserService(stub);
// Тест быстрый, независим от БД, предсказуемый
String email = service.getUserEmail(1L);
assertEquals("john@example.com", email);
}
@Test
public void testGetUserEmailNotFound() {
UserRepository stub = new StubUserRepository();
UserService service = new UserService(stub);
// Stub возвращает null для неизвестного id
String email = service.getUserEmail(999L);
assertNull(email);
}
}
Создание Stub с использованием Mockito
import static org.mockito.Mockito.*;
public class UserServiceMockitoTest {
@Test
public void testGetUserEmailWithMockito() {
// Создать Stub используя Mockito
UserRepository stub = mock(UserRepository.class);
// Задать поведение для Stub
User user = new User();
user.setId(1L);
user.setEmail("jane@example.com");
when(stub.findById(1L)).thenReturn(user);
// Тестируемый код
UserService service = new UserService(stub);
String email = service.getUserEmail(1L);
// Проверка
assertEquals("jane@example.com", email);
}
@Test
public void testGetUserEmailNull() {
UserRepository stub = mock(UserRepository.class);
// Для любого другого id возвращать null
when(stub.findById(anyLong())).thenReturn(null);
UserService service = new UserService(stub);
String email = service.getUserEmail(999L);
assertNull(email);
}
}
Пример: Stub для HTTP клиента
public interface HttpClient {
String get(String url);
int getStatusCode();
}
public class WeatherService {
private HttpClient httpClient;
public WeatherService(HttpClient httpClient) {
this.httpClient = httpClient;
}
public String getWeather(String city) {
String url = "https://api.weather.com/forecast?city=" + city;
String response = httpClient.get(url);
if (httpClient.getStatusCode() == 200) {
return parseWeather(response);
}
return "Weather service unavailable";
}
private String parseWeather(String response) {
// Парсинг JSON
return "Sunny, 25C";
}
}
public class WeatherServiceTest {
private static class StubHttpClient implements HttpClient {
private String responseBody;
private int statusCode;
public StubHttpClient(String responseBody, int statusCode) {
this.responseBody = responseBody;
this.statusCode = statusCode;
}
@Override
public String get(String url) {
return responseBody;
}
@Override
public int getStatusCode() {
return statusCode;
}
}
@Test
public void testGetWeatherSuccess() {
// Stub для успешного ответа
HttpClient stub = new StubHttpClient(
"{\"weather\":\"Sunny\",\"temp\":25}",
200
);
WeatherService service = new WeatherService(stub);
String weather = service.getWeather("London");
assertEquals("Sunny, 25C", weather);
}
@Test
public void testGetWeatherError() {
// Stub для ошибки
HttpClient stub = new StubHttpClient("", 500);
WeatherService service = new WeatherService(stub);
String weather = service.getWeather("London");
assertEquals("Weather service unavailable", weather);
}
}
Пример: Stub для файловой системы
public interface FileSystem {
String readFile(String path);
void writeFile(String path, String content);
boolean fileExists(String path);
}
public class ConfigLoader {
private FileSystem fileSystem;
public ConfigLoader(FileSystem fileSystem) {
this.fileSystem = fileSystem;
}
public Map<String, String> loadConfig(String filePath) {
if (!fileSystem.fileExists(filePath)) {
throw new FileNotFoundException("Config file not found");
}
String content = fileSystem.readFile(filePath);
return parseConfig(content);
}
private Map<String, String> parseConfig(String content) {
Map<String, String> config = new HashMap<>();
for (String line : content.split("\\n")) {
String[] parts = line.split("=");
if (parts.length == 2) {
config.put(parts[0].trim(), parts[1].trim());
}
}
return config;
}
}
public class ConfigLoaderTest {
private static class StubFileSystem implements FileSystem {
private Map<String, String> files;
public StubFileSystem() {
files = new HashMap<>();
files.put("app.properties", "db.host=localhost\\ndb.port=5432");
}
@Override
public String readFile(String path) {
return files.get(path);
}
@Override
public void writeFile(String path, String content) {
// Stub не пишет
}
@Override
public boolean fileExists(String path) {
return files.containsKey(path);
}
}
@Test
public void testLoadConfig() {
FileSystem stub = new StubFileSystem();
ConfigLoader loader = new ConfigLoader(stub);
Map<String, String> config = loader.loadConfig("app.properties");
assertEquals("localhost", config.get("db.host"));
assertEquals("5432", config.get("db.port"));
}
@Test
public void testLoadConfigNotFound() {
FileSystem stub = new StubFileSystem();
ConfigLoader loader = new ConfigLoader(stub);
assertThrows(FileNotFoundException.class, () -> {
loader.loadConfig("nonexistent.properties");
});
}
}
Пример: Stub для платежной системы
public interface PaymentGateway {
boolean processPayment(String cardNumber, BigDecimal amount);
String getTransactionId();
}
public class OrderProcessor {
private PaymentGateway gateway;
public OrderProcessor(PaymentGateway gateway) {
this.gateway = gateway;
}
public Order processOrder(Order order) throws PaymentException {
if (gateway.processPayment(order.getCardNumber(), order.getTotalAmount())) {
order.setTransactionId(gateway.getTransactionId());
order.setStatus(OrderStatus.PAID);
return order;
} else {
throw new PaymentException("Payment failed");
}
}
}
public class OrderProcessorTest {
private static class StubPaymentGateway implements PaymentGateway {
private boolean shouldSucceed;
private String transactionId;
public StubPaymentGateway(boolean shouldSucceed) {
this.shouldSucceed = shouldSucceed;
this.transactionId = "TXN-" + System.currentTimeMillis();
}
@Override
public boolean processPayment(String cardNumber, BigDecimal amount) {
return shouldSucceed;
}
@Override
public String getTransactionId() {
return transactionId;
}
}
@Test
public void testProcessOrderSuccess() {
Order order = new Order();
order.setCardNumber("1234-5678-9012-3456");
order.setTotalAmount(new BigDecimal("99.99"));
PaymentGateway stub = new StubPaymentGateway(true);
OrderProcessor processor = new OrderProcessor(stub);
Order processedOrder = processor.processOrder(order);
assertEquals(OrderStatus.PAID, processedOrder.getStatus());
assertNotNull(processedOrder.getTransactionId());
}
@Test
public void testProcessOrderFailure() {
Order order = new Order();
order.setCardNumber("1234-5678-9012-3456");
order.setTotalAmount(new BigDecimal("99.99"));
PaymentGateway stub = new StubPaymentGateway(false);
OrderProcessor processor = new OrderProcessor(stub);
assertThrows(PaymentException.class, () -> {
processor.processOrder(order);
});
}
}
Встроенные Stub объекты в Java
// Collections.emptyList() — Stub для пустого списка
List<String> emptyList = Collections.emptyList();
// Collections.singletonList() — Stub для одноэлементного списка
List<String> singleList = Collections.singletonList("item");
// Optional — Stub для может-быть значения
Optional<String> optional = Optional.of("value");
Optional<String> empty = Optional.empty();
Лучшие практики использования Stub
- Используйте для чистого уровня изоляции — тестируйте только нужный код
- Создавайте предсказуемые Stubs — известные входные/выходные данные
- Избегайте сложной логики в Stubs — они должны быть простыми
- Используйте dependency injection — легче подменять зависимости
- Предпочитайте интерфейсы — проще создавать Stubs для интерфейсов
- Используйте фреймворки — Mockito, JMock, EasyMock
- Не переусложняйте — Stub должны быть просты для понимания
Когда НЕ использовать Stub
// ПЛОХО: Stub для бизнес-логики
private static class StubCalculator implements Calculator {
public int add(int a, int b) {
return 5; // Всегда возвращает 5 — бессмысленно
}
}
// ХОРОШО: Stub только для внешних зависимостей
private static class StubDatabase implements Database {
public User getUser(Long id) {
return new User(id, "John"); // Фиксированные данные
}
}
Stub — это неотъемлемый инструмент для написания быстрых, надежных и независимых юнит-тестов в Java.