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

Тестируешь ли весь код

1.8 Middle🔥 171 комментариев
#Опыт и софт-скиллы#Тестирование

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Тестирование кода в Android разработке

Да, я тестирую весь код, но с разной степенью глубины и использованием различных подходов, зависящих от типа компонента, его критичности и этапа разработки. В современной Android разработке тестирование — это не просто проверка работоспособности, а стратегическая дисциплина, обеспечивающая качество, предотвращающая регрессии и позволяющая безопасно рефакторить код.

Стратегии и уровни тестирования

Я использую многоуровневый подход, основанный на Testing Pyramid:

  1. Юнит-тесты (Unit Tests) — фундаментальный уровень.
    • Тестирую отдельные классы, методы, функции в isolation.
    • Использую JUnit 4/5, MockK или Mockito для моков зависимостей.
    • Цель: проверка логики, преобразования данных, условий, исключений.
class CalculatorTest {
    @Test
    fun `add should return sum of two numbers`() {
        val calculator = Calculator()
        val result = calculator.add(2, 3)
        assertEquals(5, result)
    }

    @Test
    fun `divide by zero should throw IllegalArgumentException`() {
        val calculator = Calculator()
        assertThrows(IllegalArgumentException::class.java) {
            calculator.divide(10, 0)
        }
    }
}
  1. Интеграционные тесты (Integration Tests).
    • Тестирую взаимодействие нескольких модулей (например, Repository + DataSource).
    • Часто используют реальные или fake-версии зависимостей (не моки).
    • Проверяю корректность работы цепочек (например, получение данных из сети → преобразование → сохранение в БД).
class UserRepositoryIntegrationTest {
    private lateinit var repository: UserRepository
    private lateinit var fakeApiService: FakeApiService
    private lateinit var database: TestDatabase

    @Before
    fun setup() {
        fakeApiService = FakeApiService()
        database = TestDatabase.createInMemory()
        repository = UserRepository(fakeApiService, database.userDao())
    }

    @Test
    fun `fetchUser should save to database`() {
        repository.fetchUser("userId123")
        val userFromDb = database.userDao().getUser("userId123")
        assertNotNull(userFromDb)
    }
}
  1. UI/Instrumented тесты (Android Tests).
    • Тестирую взаимодействие с Android Framework (Activity, Fragment, View).
    • Использую Espresso для взаимодействия с UI, Jetpack Compose Testing для Composable-функций.
    • Запускаются на реальном устройстве/эмуляторе.
@RunWith(AndroidJUnit4::class)
class LoginActivityTest {
    @Test
    fun `successful login navigates to home screen`() {
        launchActivity<LoginActivity>()
        onView(withId(R.id.emailEditText)).perform(typeText("test@mail.com"))
        onView(withId(R.id.passwordEditText)).perform(typeText("password"))
        onView(withId(R.id.loginButton)).perform(click())
        onView(withId(R.id.homeScreenTitle)).check(matches(isDisplayed()))
    }
}
  1. Сквозные (End-to-End) тесты.
    • Тестирую ключевые пользовательские сценарии от начала до конца (например, регистрация → покупка).
    • Часто автоматизируются с помощью более высокоуровневых инструментов или выполняются QA-специалистами.

Что именно тестируется?

  • Логика бизнес-домена: алгоритмы, расчеты, правила.
  • DataSource слои: сетевые запросы (с помощью MockWebServer), работа с БД (Room), файловой системой.
  • ViewModel/Presenter: состояние, обработка событий, взаимодействие с Use Cases/Repositories.
  • UI компоненты: корректность отображения данных, реакция на пользовательский ввод (особенно в Compose).
  • Навигация: переходы между экранами, передача аргументов.
  • Дифференциальные случаи (Edge Cases): пустые списки, ошибки сети, крайние значения данных.

Практические принципы

  • TDD (Test-Driven Development) не всегда, но для сложной логики — часто. Сначала пишу тест, затем реализацию.
  • Минимизация моков: стараюсь использовать fake-объекты или реальные легковесные реализации (например, in-memory БД), чтобы тесты были более надежными.
  • Тестирование публичного контракта, а не реализации: это позволяет безопасно рефакторить код внутри, не меняя тесты.
  • Параметризованные тесты (JUnit @ParameterizedTest) для проверки множества входных данных.
  • Тесты как документация: хороший набор тестов показывает, как должен использоваться класс или модуль.
  • Автоматизация в CI/CD: все тесты запускаются автоматически при каждом коммите и перед сборкой релиза через GitHub Actions, Jenkins или Bitrise.

Сложности и исключения

Не каждый код тестируется одинаково глубоко:

  • Простой делегирующий код (например, адаптер, который только передает данные) иногда покрывается интеграционными или UI-тестами.
  • Внешние зависимости (нативные библиотеки, сложные сторонние SDK) часто тестируются косвенно или через согласованные контракты (contract tests).
  • Визуальные аспекты (точные оттенки цветов, анимации) могут требовать ручного тестирования или снapshot-тестов.

Вывод: я тестирую весь код, но с рациональным распределением усилий. Ключевая бизнес-логика покрывается плотными юнит-тестами, взаимодействие компонентов — интеграционными, пользовательские сценарии — UI и E2E-тестами. Это формирует многослойную защиту, которая значительно снижает количество багов, увеличивает скорость разработки и дает уверенность в каждом изменении кода.