Learn Android testing fundamentals from basic to interview level: software testing, manual vs automated testing, AAA test structure, test boundaries, testing pyramid, unit/integration/UI/E2E testing, coverage, risk-based testing, regression testing, and deciding what should or should not be tested.
1. What is Software Testing?
Software testing is the process of checking whether an application:
- Works according to requirements
- Produces the expected result
- Handles errors correctly
- Does not break existing functionality
- Provides a reliable user experience
A test normally provides an input, performs an action, and verifies the output.
Simple example
Requirement:
The login button should remain disabled when the password is empty.
Test:
@Test
fun emptyPassword_loginIsNotAllowed() {
val result = LoginValidator.isValid(
email = "user@example.com",
password = ""
)
assertFalse(result)
}Testing does not prove that an application has zero defects. It reduces risk and increases confidence.
2. Why Android Applications Need Testing
Android applications run in many different conditions:
- Different Android versions
- Different screen sizes
- Different manufacturers
- Different memory and processor capabilities
- Slow or unavailable networks
- Activity recreation
- Configuration changes
- Background and foreground transitions
- Process death
- Permission changes
Banking application use case
Suppose a user presses Pay ₹1,000, but the network times out.
Testing must verify that:
- The payment is not submitted twice
- The app shows a pending state
- The app checks transaction status
- Rotation does not lose the transaction ID
- A delayed success response updates the screen correctly
Without tests, small code changes can produce serious financial or user-experience problems.
3. Manual Testing vs Automated Testing
Manual testing
A person opens the application and performs test steps.
Example:
1. Open the login screen.
2. Enter an invalid password.
3. Tap Login.
4. Verify the error message.
Automated testing
A test program performs actions and verifies results automatically.
@Test
fun invalidPassword_returnsError() {
val result = validator.validatePassword("123")
assertEquals(
ValidationResult.Error("Password is too short"),
result
)
}Comparison
| Point | Manual testing | Automated testing |
|---|---|---|
| Performed by | Human tester | Test code |
| Initial effort | Low | Higher |
| Repeated execution | Slow | Fast |
| Human observation | Strong | Limited |
| Regression testing | Expensive | Effective |
| Exploratory testing | Excellent | Not suitable |
| Complex visual issues | Useful | Sometimes difficult |
| CI/CD execution | Not possible | Possible |
| Consistency | Can vary | Consistent |
| Maintenance | Test documents | Test code |
When to use manual testing
- Exploratory testing
- Usability evaluation
- Visual appearance
- New or frequently changing feature
- One-time verification
- Accessibility checks requiring human evaluation
When to automate
- Business logic
- Repeated regression scenarios
- Validation rules
- State transitions
- API and database integration
- Critical user journeys
- Tests executed on every pull request
A good strategy uses both manual and automated testing.
4. Arrange–Act–Assert Test Structure
Arrange–Act–Assert, or AAA, divides a test into three parts.
flowchart LR
A["Arrange<br/>Prepare data and dependencies"]
B["Act<br/>Execute the behaviour"]
C["Assert<br/>Verify the result"]
A --> B --> CExample
@Test
fun validCredentials_loginReturnsSuccess() {
// Arrange
val repository = FakeLoginRepository()
val useCase = LoginUseCase(repository)
// Act
val result = useCase(
email = "user@example.com",
password = "Password@123"
)
// Assert
assertEquals(LoginResult.Success, result)
}Arrange
Prepare:
- Input data
- Object under test
- Fake or mock dependencies
- Expected result
Act
Execute the behaviour being tested.
Assert
Compare the actual result with the expected result.
Best practice
Ideally, a test should contain one meaningful action and verify one behaviour. Multiple assertions are acceptable when they verify the same behaviour.
5. Test Naming Conventions
A test name should explain:
1. What is being tested
2. Under which condition
3. What result is expected
Recommended format
methodOrFeature_condition_expectedResultExamples:
@Test
fun login_emptyPassword_returnsValidationError()
@Test
fun loadProfile_repositoryFails_emitsErrorState()
@Test
fun transfer_amountExceedsBalance_isRejected()Kotlin backtick style can also be used:
@Test
fun `empty password returns validation error`() {
// Test
}Avoid unclear names
@Test
fun testLogin() // Too broad
@Test
fun test1() // No meaning
@Test
fun checkData() // Expected result is unknownA good test name acts like documentation.
6. Test Scope and Test Boundaries
Test scope
Test scope defines how much of the system a test covers.
Examples:
- One validation function
- One ViewModel
- Repository and database
- Complete payment journey
Test boundary
A boundary defines which components are included as real components and which are replaced.
ViewModel unit-test boundary
flowchart LR
UI["UI<br/>Outside boundary"]
VM["Real ViewModel"]
FR["Fake repository"]
API["Real API<br/>Outside boundary"]
UI -.-> VM
VM --> FR
FR -.-> APIIn this test:
- ViewModel is real.
- Repository is fake.
- UI, network and database are excluded.
Repository integration-test boundary
flowchart LR
VM["ViewModel<br/>Excluded"]
R["Real repository"]
DB["Real in-memory database"]
API["Fake HTTP server"]
VM -.-> R
R --> DB
R --> APIClear boundaries help answer:
- What failure does this test locate?
- Which dependencies should be real?
- Which dependencies should be controlled?
- How fast should the test run?
7. Behaviour Testing vs Implementation Testing
Behaviour testing
Checks the result visible through the public API.
@Test
fun validTask_saveTask_returnsSuccess() {
val result = useCase.save("Learn testing")
assertEquals(SaveResult.Success, result)
}It does not care how the result was internally produced.
Implementation testing
Checks internal details such as:
- Private methods
- Exact internal call sequence
- Internal variables
- Number of calls that are not part of a requirement
verify(exactly = 1) {
repository.validate()
}This verification is useful only if calling validate() exactly once is an actual requirement.
Comparison
| Behaviour testing | Implementation testing |
|---|---|
| Tests observable results | Tests internal details |
| Survives refactoring | Often breaks during refactoring |
| Describes requirements | Describes current implementation |
| Usually preferred | Use only when interaction matters |
Important rule
Test:
What must the component do?
Avoid testing:
How has the developer currently written it?
8. Testing Strategy
A testing strategy is a planned approach that explains:
- What will be tested
- What will not be tested
- Which test layer will be used
- Which tools will be used
- When tests will run
- Who owns failures
- What quality level is required
Example Android testing strategy
| Component | Test type | Tool |
|---|---|---|
| Validation | Unit | JUnit |
| Use case | Unit | JUnit + fake |
| ViewModel state | Unit | JUnit + coroutine test |
| Repository mapping | Integration | JUnit |
| Room queries | Integration | Room in-memory database |
| Retrofit requests | Integration | MockWebServer |
| Compose screen | UI | Compose Test |
| XML screen | UI | Espresso |
| Critical payment journey | E2E | Instrumented test |
Strategy creation steps
1. Identify important features.
2. Assess business and technical risks.
3. Define test boundaries.
4. Select the cheapest reliable test layer.
5. Automate repeatable scenarios.
6. Keep a small number of critical E2E tests.
7. Execute tests in CI.
8. Monitor failures and flaky tests.
9. Update the strategy as the application changes.
9. Testing Pyramid
The testing pyramid recommends:
- Many unit tests
- A reasonable number of integration tests
- Fewer UI tests
- Very few end-to-end tests
flowchart BT
U["Unit tests<br/>Many • Fast • Low cost"]
I["Integration tests<br/>Some • Medium speed"]
UI["UI tests<br/>Few • Slower"]
E["End-to-end tests<br/>Very few • Highest cost"]
U --> I --> UI --> EIt is a guideline, not a fixed percentage.
Example distribution:
- Unit tests: 60–70%
- Integration tests: 20–30%
- UI/E2E tests: 10–15%
The correct distribution depends on:
- Application architecture
- Business risk
- Team size
- Legacy code
- Frameworks
- Device-dependent behaviour
10. Unit-Test Layer
A unit test verifies a small unit in isolation.
Possible units:
- Function
- Validator
- Mapper
- Use case
- ViewModel
- Reducer
- State holder
Use case
Test whether a transfer is rejected when its amount exceeds the available balance.
class TransferValidator {
fun canTransfer(
amount: Double,
balance: Double
): Boolean {
return amount > 0 && amount <= balance
}
}@Test
fun amountExceedsBalance_transferIsRejected() {
val validator = TransferValidator()
val result = validator.canTransfer(
amount = 6_000.0,
balance = 5_000.0
)
assertFalse(result)
}Characteristics
- Fast
- Isolated
- Deterministic
- Easy to debug
- Inexpensive to maintain
Limitation
A unit test cannot prove that different components work correctly together.
11. Integration-Test Layer
An integration test verifies that multiple components communicate correctly.
Examples:
- Repository + DAO
- Repository + API
- Retrofit + JSON converter
- Room + database migration
- Use case + repository
Use case
Verify that API data is mapped and saved in the database:
Fake API response
↓
Real repository
↓
Real mapper
↓
In-memory Room database
↓
Verify stored entity
Important integration scenarios
- Correct API endpoint
- JSON parsing
- DTO-to-domain mapping
- Database queries
- Cache behaviour
- Error mapping
- Offline behaviour
- Migration correctness
Integration tests are slower than unit tests but provide confidence at component boundaries.
12. UI-Test Layer
A UI test interacts with a screen and verifies visible behaviour.
It can test:
- Text fields
- Buttons
- Error messages
- Lists
- Dialogs
- Navigation
- Loading indicators
- Accessibility semantics
Compose example
@Test
fun emptyTitle_saveClicked_displaysError() {
composeRule
.onNodeWithText("Save")
.performClick()
composeRule
.onNodeWithText("Title is required")
.assertIsDisplayed()
}Espresso example
@Test
fun emptyEmail_loginClicked_displaysError() {
onView(withId(R.id.loginButton))
.perform(click())
onView(withText("Email is required"))
.check(matches(isDisplayed()))
}Limitation
UI tests are generally:
- Slower
- More environment-dependent
- More expensive to maintain
- Harder to diagnose
Use them for important screen behaviour, not every business rule.
13. End-to-End Test Layer
An E2E test verifies a complete user journey through multiple layers.
Login journey
flowchart TD
L["Open login screen"]
C["Enter credentials"]
A["Call authentication API"]
S["Store authenticated session"]
H["Display home screen"]
L --> C --> A --> S --> HPossible E2E scenarios
- Registration → login → home
- Search product → add to cart → checkout
- Add beneficiary → transfer money → confirmation
- Create task → restart app → task remains available
Advantages
- High confidence in a complete journey
- Detects integration and configuration failures
- Represents real user behaviour
Disadvantages
- Slow
- Expensive
- Difficult to debug
- More likely to become flaky
- Requires complex test data and environment setup
Automate only the most critical journeys.
14. Comparing Testing Layers
| Factor | Unit | Integration | UI | E2E |
|---|---|---|---|---|
| Scope | Single unit | Multiple components | Screen behaviour | Complete journey |
| Speed | Very fast | Fast/medium | Slow | Slowest |
| Isolation | High | Medium | Low | Very low |
| Confidence | Logic confidence | Boundary confidence | UI confidence | Journey confidence |
| Failure diagnosis | Easy | Moderate | Harder | Hardest |
| Maintenance cost | Low | Medium | High | Very high |
| Typical quantity | Many | Some | Few | Very few |
| Android device needed | Usually no | Sometimes | Usually yes | Usually yes |
| Examples | Validator | Repository + Room | Login screen | Login → Home |
15. Test Confidence
Test confidence means how strongly the tests indicate that the application works correctly.
Confidence depends on:
- Test relevance
- Quality of assertions
- Realistic test data
- Correct boundaries
- Coverage of important scenarios
- Test reliability
- Production-like integration
Important observation
A single E2E test offers broad confidence but poor failure localization.
Many unit tests offer excellent logic confidence but cannot prove that the complete application works.
Therefore, confidence comes from a balanced test suite.
16. Test Execution Speed
Execution speed affects how frequently developers run tests.
| Test | Approximate relative speed |
|---|---|
| Pure unit test | Fastest |
| JVM integration test | Fast |
| Robolectric test | Medium |
| Instrumented integration test | Slower |
| UI test | Slow |
| Complete E2E suite | Slowest |
These are relative classifications, not guaranteed timings.
Why speed matters
Fast tests:
- Run on every change
- Provide early feedback
- Reduce debugging time
- Encourage developers to test locally
- Keep CI pipelines efficient
- Place most business logic in fast JVM-testable components.
17. Test Maintenance Cost
Maintenance cost includes the effort required to:
- Write tests
- Understand failures
- Update tests after changes
- Manage test data
- Maintain devices and emulators
- Repair flaky tests
- Maintain backend test environments
Cost generally increases upward
Unit < Integration < UI < End-to-end
Example
Changing a private ViewModel implementation should not break a behaviour-based unit test.
Changing a screen layout may break several UI selectors even when the feature still works.
Good test architecture reduces maintenance by using:
- Stable public APIs
- Stable semantics or test tags
- Fake dependencies
- Test fixtures
- Clear test boundaries
- Behaviour-focused assertions
18. Test Coverage
Test coverage measures which code was executed during tests.
Common forms:
- Line coverage
- Branch coverage
- Function coverage
- Instruction coverage
Example
fun discount(isPremium: Boolean): Int {
return if (isPremium) 20 else 5
}Testing only isPremium = true might execute most lines but fail to test the non-premium branch.
Coverage limitations
High coverage does not guarantee good tests.
A test can execute code without verifying the correct result:
@Test
fun weakTest() {
calculator.add(2, 3) // No assertion
}Coverage should be used to find missing areas—not as the only quality measurement.
Focus particularly on:
- Business-critical branches
- Error paths
- State transitions
- Complex logic
- Security-sensitive logic
19. Risk-Based Testing
Risk-based testing gives more testing effort to features with greater risk.
A simple model is:
[
\text{Risk} = \text{Probability of failure} \times \text{Impact of failure}
]Example risk table
| Feature | Failure probability | Impact | Priority |
|---|---|---|---|
| Payment submission | Medium | Critical | Very high |
| Login authentication | Medium | High | High |
| Profile image | Medium | Low | Medium |
| Button colour | Low | Low | Low |
High-risk Android areas
- Authentication
- Payments
- Personal information
- Database migrations
- Offline synchronization
- Security
- Deep links
- Permissions
- Process-death restoration
- Release configuration
Payment example
For payment submission, test:- Successful payment
- Insufficient balance
- Network timeout
- Duplicate button tap
- Duplicate API request
- Pending transaction
- Server failure
- Process death
- Retry and status check
20. Deciding What Should Be Tested
Ask the following questions:
1. Is the behaviour business-critical?
2. Is the logic complex?
3. Has it failed before?
4. Does it change frequently?
5. Does it connect two components?
6. Can failure cause data or money loss?
7. Is the error difficult to detect manually?
8. Does it behave differently across Android states?
9. Will the scenario be tested repeatedly?
10. Is automation cheaper over time?
Strong candidates
- Validation rules
- Calculations
- Use cases
- ViewModel state transitions
- Error mapping
- Repository caching
- API parsing
- Database queries and migrations
- Authentication
- Payment flows
- Critical navigation
- Offline and recovery behaviour
Selecting the layer
Use the lowest-cost layer that provides sufficient confidence.
Example:
| Requirement | Appropriate layer |
|---|---|
| Reject negative amount | Unit |
| API result is cached | Integration |
| Error appears on screen | UI |
| Complete payment succeeds | E2E |
21. Deciding What Should Not Be Tested
Generally avoid testing:
- Kotlin or Java language behaviour
- Android framework implementation
- Third-party library internals
- Simple data-class getters
- Private functions directly
- Trivial constant assignments
- Internal implementation with no behavioural value
- Every possible UI combination
- Exact animations unless they are requirements
- Generated code
- Logging statements
Example
Do not test that Kotlin stores a constructor property:
data class User(val name: String)A test like this provides little value:
@Test
fun userStoresName() {
assertEquals("Mahesh", User("Mahesh").name)
}However, test custom behaviour implemented around that model.
Exception
Even simple code may need tests when it is:
- Safety-critical
- Financially critical
- Security-sensitive
- Required by regulation
- Historically defective
22. Happy-Path Testing
A happy-path test verifies the expected successful scenario.
Login use case
Given:
- Email is valid
- Password is correct
- Network is available
- Server responds successfully
Expected:
- Login succeeds
- Session is stored
- Home screen opens
@Test
fun validCredentials_repositorySucceeds_returnsSuccess() {
repository.loginResult = LoginResult.Success
val result = loginUseCase(
"user@example.com",
"Password@123"
)
assertEquals(LoginResult.Success, result)
}Happy-path testing is necessary, but it is not enough.
23. Negative-Path Testing
Negative-path testing checks invalid input and failure conditions.
Examples
- Incorrect password
- Missing email
- API returns
401 - Server returns
500 - Database write fails
- User denies permission
- Network disconnects
- Invalid deep link
- Payment is rejected
@Test
fun repositoryThrowsException_returnsNetworkError() {
repository.shouldFail = true
val result = loginUseCase(
"user@example.com",
"Password@123"
)
assertEquals(LoginResult.NetworkError, result)
}Good negative-path tests verify both:
- Correct error result
- No incorrect side effect
For example, a failed payment must not be stored as successful.
24. Edge and Boundary-Value Testing
Boundary testing checks values around important limits.
Suppose the allowed transfer range is ₹1–₹1,00,000.
Test:
| Amount | Expected |
|---|---|
| ₹0 | Reject |
| ₹1 | Accept |
| ₹2 | Accept |
| ₹99,999 | Accept |
| ₹1,00,000 | Accept |
| ₹1,00,001 | Reject |
@Test
fun maximumAllowedAmount_isAccepted() {
assertTrue(validator.isValidAmount(100_000))
}
@Test
fun amountAboveMaximum_isRejected() {
assertFalse(validator.isValidAmount(100_001))
}Other boundaries:
- Empty string
- One character
- Maximum allowed length
- Maximum length plus one
- Zero items
- One item
- First and last list position
- Date before/on/after expiry
- API page size
- File-size limit
- Integer overflow
- Null and missing values
Defects commonly appear at boundaries rather than typical values.
25. Regression Testing
Regression testing checks whether existing functionality still works after a change.
Example
A developer changes login code to support biometric authentication. Regression tests should verify that:
- Password login still works
- Incorrect password still fails
- Session storage still works
- Logout clears the session
- Token-expiry handling still works
Regression flow
flowchart LR
C["Code change"]
U["Run unit tests"]
I["Run integration tests"]
UI["Run selected UI tests"]
R["Release confidence"]
C --> U --> I --> UI --> RTypes
Full regression
Run the entire relevant test suite.
Use before:
- Major production release
- Significant architecture change
- Large dependency upgrade
Selective regression
Run tests affected by the change.
Use during:
- Pull requests
- Small bug fixes
- Regular development
Bug regression test
When a bug is found:
1. Write a test that reproduces it.
2. Confirm that the test fails.
3. Fix the bug.
4. Confirm that the test passes.
5. Keep the test to prevent recurrence.
Complete Use Case: Testing a Money Transfer Feature
Requirement
A user can transfer between ₹1 and ₹1,00,000 when sufficient balance is available.
Testing plan
| Scenario | Test layer | Test type |
|---|---|---|
| Validate minimum amount | Unit | Boundary |
| Reject amount above balance | Unit | Negative |
| Successful transfer result | Unit | Happy path |
| Repository maps API response | Integration | Contract |
| Timeout becomes pending state | Integration | Negative |
| Error message appears | UI | Behaviour |
| Double tap creates one request | Integration/UI | Risk-based |
| Beneficiary → payment → confirmation | E2E | Happy path |
| Previous transfer flow after refactoring | Multiple | Regression |
Feature test pyramid
flowchart BT
U["Unit<br/>Amount, balance and state rules"]
I["Integration<br/>Repository, API and database"]
UI["UI<br/>Input, message and navigation"]
E["E2E<br/>Complete transfer journey"]
U --> I --> UI --> ECommon Mistakes
1. Writing only happy-path tests
2. Testing private methods directly
3. Depending heavily on mocks
4. Writing too many E2E tests
5. Ignoring integration boundaries
6. Using coverage as the only quality goal
7. Giving tests unclear names
8. Mixing multiple unrelated behaviours in one test
9. Not controlling network, clock or random data
10. Using fixed delays such as Thread.sleep()11. Writing tests after bugs without keeping regression tests
12. Expecting unit tests to prove the complete app works
13. Testing framework code instead of application behaviour
14. Ignoring boundary values
15. Allowing tests to depend on execution order
Interview Questions and Answers
1. What is software testing?
Software testing verifies that an application meets its requirements, handles failures correctly and does not break existing behaviour.
2. Why is testing important in Android?
Android apps run across different devices, API levels, lifecycle states and network conditions. Testing reduces compatibility issues, crashes and regressions.
3. Does testing prove that an application is bug-free?
No. Testing increases confidence and reduces risk, but it cannot prove that every possible defect has been removed.
4. What is the difference between manual and automated testing?
Manual testing is performed by a person. Automated testing uses code to execute actions and verify results repeatedly.
5. Can automated testing completely replace manual testing?
No. Automation is effective for repeatable checks, while manual testing remains valuable for exploration, usability and visual evaluation.
6. What is Arrange–Act–Assert?
* Arrange: prepare input and dependencies.
* Act: execute the behaviour.
* Assert: verify the result.
7. How should a test be named?
A useful format is:
method_condition_expectedResultExample:
login_emptyPassword_returnsError8. What is test scope?
Test scope defines how much of the application is covered by a test, such as one function, one screen or a complete user journey.
9. What is a test boundary?
A test boundary defines which components are real and which are excluded or replaced with test doubles.
10. What is behaviour testing?
Behaviour testing checks observable inputs and outputs through public APIs without depending on internal implementation.
11. Why should implementation-detail testing be avoided?
It creates fragile tests that break during refactoring even when application behaviour remains correct.
12. What is a testing strategy?
It is a plan defining what to test, which layers and tools to use, when tests run and what quality criteria must be met.
13. What is the testing pyramid?
It recommends many fast unit tests, some integration tests, fewer UI tests and very few E2E tests.
14. Is the testing pyramid percentage fixed?
No. The distribution depends on the application’s architecture, complexity, risks and team requirements.
15. What is a unit test?
A unit test verifies a small component, such as a function, use case or ViewModel, in isolation.
16. What is an integration test?
An integration test verifies that multiple components, such as a repository and database, work correctly together.
17. What is a UI test?
A UI test interacts with screen elements and verifies visible behaviour such as messages, lists and navigation.
18. What is an end-to-end test?
An E2E test verifies a complete user journey across UI, business logic, data and external integrations.
19. Unit test vs integration test?
A unit test isolates one component using controlled dependencies. An integration test uses multiple collaborating components.
20. UI test vs E2E test?
A UI test can verify one screen with fake dependencies. An E2E test verifies a complete journey through multiple real layers.
21. Which test layer is fastest?
Pure local unit tests are normally the fastest.
22. Which test layer provides the broadest confidence?
E2E tests provide broad journey confidence, but they are slower, costlier and harder to debug.
23. Why not test everything through the UI?
UI tests are slow, fragile and expensive. Most business rules can be verified more efficiently with unit tests.
24. What is test confidence?
It is the level of trust that the test suite provides about application correctness.
25. How can test execution speed be improved?
Use more JVM tests, inject dependencies, avoid real networks and delays, parallelize suitable tests and keep E2E tests limited.
26. What is test maintenance cost?
It is the effort needed to write, update, run and repair tests and their supporting environments.
27. What is test coverage?
Coverage measures which lines, branches, instructions or functions execute during tests.
28. Does 100% coverage mean the application is correct?
No. Code may execute without meaningful assertions, and missing requirements cannot be detected by coverage alone.
29. What is branch coverage?
Branch coverage measures whether different decision outcomes, such as both if and else, have been executed.
30. What is risk-based testing?
It prioritizes testing based on the probability and impact of failure.
31. Which features should receive the most testing?
Business-critical, complex, frequently changed, security-sensitive and historically defective features.
32. What should generally not be tested?
Language behaviour, simple getters, third-party internals, framework implementation and private methods directly.
33. What is happy-path testing?
It verifies the normal successful scenario with valid input and expected conditions.
34. What is negative-path testing?
It verifies invalid inputs, errors, exceptions and unavailable dependencies.
35. What is boundary-value testing?
It verifies values at, immediately below and immediately above defined limits.
36. Why are boundary tests important?
Many programming errors occur at limits due to incorrect comparison operators, indexing or missing validation.
37. What is regression testing?
Regression testing verifies that existing functionality still works after changes.
38. What is a regression test for a bug?
It is a permanent automated test that first reproduces a defect and later prevents the same defect from returning.
39. What is selective regression testing?
It runs the tests most relevant to the changed components instead of executing the entire suite.
40. How do you decide the correct test layer?
Use the lowest-cost and fastest layer that provides sufficient confidence for the requirement.
41. What is the ideal Android test strategy?
Use fast unit tests for logic, integration tests for boundaries, UI tests for important screen behaviour and a small number of E2E tests for critical journeys.
Quick Revision
Unit test → Is one component correct?
Integration → Do components work together?
UI test → Does the screen behave correctly?
End-to-end → Does the complete user journey work?
Happy path → Expected success
Negative path → Failure or invalid condition
Boundary test → Values around limits
Regression test → Existing feature after a change
Coverage → Code executed
Confidence → Trust provided
Strategy → Overall testing plan
Boundary → What is included and replaced