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

Что такое тестирование ПО?

1.0 Junior🔥 71 комментариев
#Тестирование

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

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

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

Тестирование ПО — полное руководство

Тестирование ПО — это процесс проверки приложения на соответствие требованиям и выявления дефектов перед выпуском. Это критически важная часть разработки, которая обеспечивает качество, надежность и безопасность приложения.

Основное определение

Тестирование ПО — это систематическое исследование приложения с целью:

  • Убедиться что оно работает корректно
  • Найти ошибки (баги)
  • Убедиться в соответствии требованиям
  • Оценить качество и производительность

Зачем тестирование нужно

Выявление дефектов — ловим баги до пользователей

Обеспечение качества — гарантируем что приложение соответствует стандартам

Снижение затрат — баг найденный на этапе разработки стоит дешевле чем в продакшене

Уверенность — возможность рефакторить и добавлять фичи без страха сломать существующий функционал

Документация — тесты документируют как код должен работать

Уровни тестирования

// 1. Unit Testing — тестирование отдельных функций/классов
@Test
fun testUserNameValidation() {
    val validator = UserValidator()
    assertTrue(validator.isValidName("John"))
    assertFalse(validator.isValidName(""))
}

// 2. Integration Testing — тестирование взаимодействия компонентов
@Test
fun testUserRepositoryFetchesFromAPI() = runTest {
    val mockApi = MockUserApi()
    val repository = UserRepository(mockApi)
    
    val users = repository.getUsers()
    
    assertEquals(2, users.size)
}

// 3. System Testing — тестирование всей системы
// Проверяем что User flow работает: вход -> список -> детали -> выход

// 4. Acceptance Testing — проверка требований бизнеса
// Пользователь может создать профиль, загрузить фото, добавить друзей

Типы тестирования

Functional Testing — проверяет функциональность (что работает)

@Test
fun testLoginWithCorrectCredentials() {
    val viewModel = LoginViewModel(mockAuthService)
    viewModel.login("user@example.com", "password123")
    
    assertEquals(LoginState.Success, viewModel.state.value)
}

Performance Testing — проверяет скорость и оптимизацию

@Test
fun testLargeListRenderingPerformance() {
    val items = (1..10000).map { Item(it) }
    val startTime = System.currentTimeMillis()
    
    renderList(items)
    
    val duration = System.currentTimeMillis() - startTime
    assertTrue(duration < 100, "Rendering took too long: $duration ms")
}

Security Testing — проверяет безопасность

@Test
fun testPasswordIsEncrypted() {
    val password = "secret123"
    val encrypted = PasswordUtils.encrypt(password)
    
    assertNotEquals(password, encrypted)
    assertTrue(PasswordUtils.verify(password, encrypted))
}

Usability Testing — проверяет удобство интерфейса (обычно ручное)

Compatibility Testing — проверяет работу на разных устройствах

Android Testing Pyramid

        /\
       /  \ UI Tests (Espresso)
      /    \
     /------\
    / Integration Tests \
   /   (instrumented)    \
  /------------------------\ Unit Tests (JUnit)

Unit Tests (70%) — быстрые, запускаются без устройства

class UserViewModelTest {
    @Test
    fun testFetchUserData() = runTest {
        val mockRepository = mockk<UserRepository>()
        coEvery { mockRepository.getUser(1) } returns User(1, "John")
        
        val viewModel = UserViewModel(mockRepository)
        viewModel.fetchUser(1)
        
        assertEquals("John", viewModel.user.value?.name)
    }
}

Integration Tests (20%) — тестируют взаимодействие нескольких компонентов

@RunWith(AndroidRunner::class)
class UserRepositoryIntegrationTest {
    @get:Rule
    val instantExecutorRule = InstantTaskExecutorRule()
    
    @Test
    fun testDatabasePersistence() = runTest {
        val database = Room.inMemoryDatabaseBuilder(
            InstrumentationRegistry.getInstrumentation().context,
            AppDatabase::class.java
        ).build()
        
        val repository = UserRepository(database.userDao())
        repository.insertUser(User(1, "John"))
        
        val users = repository.getUsers()
        assertEquals(1, users.size)
    }
}

UI Tests (10%) — медленные, запускаются на устройстве

class UserListScreenTest {
    @get:Rule
    val composeTestRule = createComposeRule()
    
    @Test
    fun testUserListIsDisplayed() {
        composeTestRule.setContent {
            UserListScreen()
        }
        
        composeTestRule.onNodeWithText("John").assertIsDisplayed()
        composeTestRule.onNodeWithTag("user_list").onChildren()
            .assertCountEquals(2)
    }
}

Test-Driven Development (TDD)

// Шаг 1: Написать падающий тест (RED)
@Test
fun testCalculateDiscount() {
    val calculator = DiscountCalculator()
    val result = calculator.calculate(100, 20) // 20% скидка
    assertEquals(80, result)
}

// Шаг 2: Написать минимальный код (GREEN)
class DiscountCalculator {
    fun calculate(price: Int, discount: Int): Int {
        return price - (price * discount / 100)
    }
}

// Шаг 3: Рефакторить (REFACTOR)
class DiscountCalculator {
    fun calculate(price: Int, discountPercent: Int): Int {
        val discountAmount = price * discountPercent / 100
        return price - discountAmount
    }
    
    fun calculateWithTax(price: Int, discountPercent: Int, taxPercent: Int): Int {
        val afterDiscount = calculate(price, discountPercent)
        val tax = afterDiscount * taxPercent / 100
        return afterDiscount + tax
    }
}

Best Practices для тестирования

Arrange-Act-Assert паттерн — структурируй тесты ясно

@Test
fun testUserLogin() {
    // Arrange — подготовка
    val mockAuthService = mockk<AuthService>()
    coEvery { mockAuthService.login("user", "pass") } returns User(1, "user")
    val viewModel = LoginViewModel(mockAuthService)
    
    // Act — выполнение
    viewModel.login("user", "pass")
    
    // Assert — проверка
    assertEquals(LoginState.Success, viewModel.state.value)
}

Один assert на тест — тест проверяет одно поведение

// Плохо
@Test
fun testUser() {
    val user = createUser()
    assertEquals("John", user.name)      // Слишком много проверок
    assertEquals(25, user.age)
    assertTrue(user.isActive)
}

// Хорошо
@Test
fun testUserNameIsJohn() {
    val user = createUser()
    assertEquals("John", user.name)
}

Используй мокирование — для изоляции тестов

@Test
fun testRepositoryFetchesFromCache() = runTest {
    val mockApi = mockk<UserApi>()
    val cache = FakeCache()
    val repository = UserRepository(mockApi, cache)
    
    // Пользователь уже в кэше
    cache.put("user_1", User(1, "John"))
    
    val user = repository.getUser(1)
    
    // API не должна быть вызвана
    coVerify(exactly = 0) { mockApi.fetchUser(1) }
    assertEquals("John", user.name)
}

Достаточное покрытие — стремись к 80-90% test coverage

# Проверить coverage
./gradlew test --info
./gradlew jacocoTestReport

Инструменты для тестирования Android

ИнструментНазначение
JUnitUnit тесты
Mockito/MockKМокирование
EspressoUI тесты
RobolectricUnit тесты с Android компонентами
RoomТестирование БД
Coroutines TestТестирование корутин
TurbineТестирование Flow

Типичный workflow

1. Разработчик пишет код
2. Пишет тесты для кода
3. Запускает тесты локально (./gradlew test)
4. Отправляет Pull Request
5. CI/CD запускает все тесты автоматически
6. Если 90%+ pass → merge
7. Если есть падающие → правит разработчик

Заключение

Тестирование ПО — это инвестиция в качество. Да, писать тесты дольше, но они:

  • Экономят время на отладку
  • Предотвращают регрессию
  • Дают уверенность при рефакторинге
  • Служат документацией
  • Снижают затраты на поддержку

Хороший разработчик пишет не только код, но и тесты для него. "Code without tests is legacy code" — Robert C. Martin