← Назад к вопросам

Что такое Stub?

2.0 Middle🔥 111 комментариев
#Тестирование

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Stub: Вспомогательный объект для тестирования

Stub (заглушка, имитация) — это упрощенная реализация зависимости объекта, используется в юнит-тестировании для замены реальных компонентов (баз данных, API, файловых систем и т.д.). Stub предоставляет фиксированные ответы на вызовы методов и не содержит реальной логики.

Основное назначение Stub

Stub используется для:

  1. Изоляции компонентов — отделить тестируемый код от внешних зависимостей
  2. Контролирования зависимостей — задать предсказуемые возвращаемые значения
  3. Ускорения тестов — заменить медленные операции (БД, сеть) на быстрые
  4. Симуляции ошибок — тестировать обработку исключений

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

  1. Используйте для чистого уровня изоляции — тестируйте только нужный код
  2. Создавайте предсказуемые Stubs — известные входные/выходные данные
  3. Избегайте сложной логики в Stubs — они должны быть простыми
  4. Используйте dependency injection — легче подменять зависимости
  5. Предпочитайте интерфейсы — проще создавать Stubs для интерфейсов
  6. Используйте фреймворки — Mockito, JMock, EasyMock
  7. Не переусложняйте — 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.

Что такое Stub? | PrepBro