Модульное тестирование с JUnit
Пишите быстрые и надёжные модульные тесты с JUnit 4. Используйте @Test, @Before, @After и проверки, тестируйте LiveData с InstantTaskExecutorRule, а корутины — с runTest.
«Модульное тестирование с JUnit» — бесплатный урок Android Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Android Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Android Academy содержит 4 уроков всего.
Зачем писать тесты?
Тесты — не роскошь, а ваша страховочная сетка:
- Обнаруживайте ошибки раньше пользователей
- Уверенно проводите рефакторинг (если тесты проходят, ничего не сломалось)
- Документируйте предполагаемое поведение
- Предотвращайте регрессии при добавлении функций
Хорошо протестированный код обычно лучше спроектирован: если его трудно тестировать, вероятно, его компоненты слишком тесно связаны.
Виды тестов Android
Две категории:
- Модульные тесты (папка
test/) — выполняются на JVM, устройство не требуется. Быстрые. Проверяют обычную логику Kotlin/Java. - Инструментальные тесты (папка
androidTest/) — выполняются на устройстве или эмуляторе. Медленнее. Проверяют код, специфичный для Android (базу данных Room, интерфейс).
Предпочитайте модульные тесты для бизнес-логики. Используйте инструментальные тесты только при необходимости взаимодействовать с API Android.
Настройка JUnit 4
JUnit 4 по умолчанию включён в проекты Android. Добавьте эти зависимости в app/build.gradle:
// app/build.gradle:
dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
testImplementation 'androidx.arch.core:core-testing:2.2.0' // for LiveData
}Ваш первый тест JUnit
Создайте класс в src/test/java/ и пометьте методы тестирования аннотацией @Test:
import org.junit.Test
import org.junit.Assert.*
class CalculatorTest {
private val calc = Calculator()
@Test
fun `addition returns correct sum`() {
val result = calc.add(2, 3)
assertEquals(5, result)
}
@Test
fun `division by zero throws exception`() {
assertThrows(ArithmeticException::class.java) {
calc.divide(10, 0)
}
}
}@Before и @After
Используйте аннотации жизненного цикла, чтобы настраивать и очищать состояние вокруг каждого теста:
class UserRepositoryTest {
private lateinit var repo: UserRepository
private lateinit var fakeDao: FakeUserDao
@Before
fun setUp() {
fakeDao = FakeUserDao() // fresh instance before each test
repo = UserRepository(fakeDao)
}
@After
fun tearDown() {
fakeDao.clear() // clean up after each test
}
@Test
fun `getUsers returns all users`() {
fakeDao.insert(User(1, "Alice"))
fakeDao.insert(User(2, "Bob"))
assertEquals(2, repo.getUsers().size)
}
}Распространённые проверки
JUnit предоставляет методы проверок в Assert.*:
// Equality:
assertEquals(expected, actual)
assertNotEquals(unexpected, actual)
// Null:
assertNull(value)
assertNotNull(value)
// Boolean:
assertTrue(condition)
assertFalse(condition)
// Same object reference:
assertSame(expected, actual)
// Custom message on failure:
assertEquals("User ID should be 1", 1, user.id)
// Exception:
assertThrows(IllegalArgumentException::class.java) {
User(id = -1, name = "")
}Тестирование ViewModel
Чтобы тестировать LiveData, добавьте InstantTaskExecutorRule, чтобы LiveData публиковала значения синхронно:
class UserViewModelTest {
@get:Rule
val instantTaskRule = InstantTaskExecutorRule()
private val fakeRepo = FakeUserRepository()
private lateinit var viewModel: UserViewModel
@Before
fun setUp() {
viewModel = UserViewModel(fakeRepo)
}
@Test
fun `users are loaded on init`() {
fakeRepo.usersToReturn = listOf(User(1, "Alice"), User(2, "Bob"))
viewModel.loadUsers()
val result = viewModel.users.value
assertNotNull(result)
assertEquals(2, result?.size)
}
}Тестирование сопрограмм
Используйте TestCoroutineDispatcher / StandardTestDispatcher, чтобы управлять выполнением сопрограмм в тестах:
class SyncWorkerTest {
@get:Rule
val instantTaskRule = InstantTaskExecutorRule()
private val testDispatcher = StandardTestDispatcher()
@Before
fun setUp() {
Dispatchers.setMain(testDispatcher)
}
@After
fun tearDown() {
Dispatchers.resetMain()
}
@Test
fun `sync completes successfully`() = runTest {
val repo = FakeRepository()
val worker = SyncUseCase(repo, testDispatcher)
val result = worker.run()
assertTrue(result.isSuccess)
}
}Фиктивная реализация и имитация
Две стратегии замены настоящих зависимостей в тестах:
- Фиктивная реализация — рабочая реализация, созданная для тестирования (например, список в памяти вместо настоящей базы данных). Её легко написать и читать, библиотека не нужна.
- Имитация — автоматически созданная заглушка, которая записывает вызовы и позволяет проверять взаимодействия (Mockito, MockK). Она мощнее, но при чрезмерном использовании тесты могут стать хрупкими.
Для простых случаев предпочитайте фиктивные реализации; используйте имитации, когда нужно проверить вызовы методов.
Создание фиктивной реализации
Фиктивная реализация реализует тот же интерфейс, что и настоящий класс, но хранит данные в памяти:
interface UserDao {
fun getAll(): List<User>
fun insert(user: User)
}
class FakeUserDao : UserDao {
private val storage = mutableListOf<User>()
override fun getAll(): List<User> = storage.toList()
override fun insert(user: User) { storage.add(user) }
fun clear() { storage.clear() }
}Соглашение об именовании тестов
Хорошие имена тестов описывают, что проверяется, и ожидаемый результат. Для понятных описаний в Kotlin используйте имена функций в обратных кавычках:
`getUsers returns empty list when no users exist``login throws exception when password is blank``add returns correct sum for negative numbers`
Шаблон: `subject does X when Y`
Быстрая проверка
Каково назначение @Before в классе тестов JUnit?
Итоги: модульное тестирование с JUnit
Надёжные приложения — это протестированные приложения:
- Модульные тесты находятся в
src/test/и выполняются на JVM — быстро - Помечайте их аннотацией
@Test, используйтеassertEquals,assertTrue,assertThrows @Before/@After— для настройки и очисткиInstantTaskExecutorRule— для синхронного тестирования LiveDatarunTest+StandardTestDispatcher— для тестирования сопрограмм- Для простых замен предпочитайте фиктивные реализации имитациям
Далее: имитация с помощью Mockito для проверки взаимодействий.
Часто задаваемые вопросы
Урок «Модульное тестирование с JUnit» бесплатный?
Да — полный текст урока «Модульное тестирование с JUnit» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Android Academy, подпишись на CoddyKit PRO. Курс Android Academy содержит 4 уроков всего.
Чему я научусь в уроке «Модульное тестирование с JUnit»?
Пишите быстрые и надёжные модульные тесты с JUnit 4. Используйте @Test, @Before, @After и проверки, тестируйте LiveData с InstantTaskExecutorRule, а корутины — с runTest. Ты практикуешь Android Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Android Academy?
Предыдущий опыт не требуется. Android Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.
Сколько времени занимает урок «Модульное тестирование с JUnit»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Android Academy?
Да. Каждый урок Android Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Модульное тестирование с JUnit
- Имитация объектов с Mockito
- Тестирование интерфейса с Espresso
- Отладка и профилирование