building maintainable android apps (droidcon nyc 2014)
Post on 14-Nov-2014
250 Views
Preview:
DESCRIPTION
TRANSCRIPT
Building Maintainable Android Applications!
Kevin Schultz (@kevinrschultz) DroidCon NYC 2014 !kevinrschultz.com github.com/krschultz/AndroidWeatherBuoyDemo
Apps vs Applications
Treading WaterTime spent on features dwindles
Infrequent releases due to high QA costs
Heisenbug whack-a-mole
Fear of change
Bugs
Features
Infrastructure
Drowning
Platform Changes
Business Pivot
Competitors
Panic
How many regressions will you cause when
implementing Material Design?
Refactor! Rewrite!
The codebase is a mess!
Unlikely to be better next time
Clean code is necessary but insufficient for maintainability
We are state of the art today, what about next year?
Why do Android apps always seem to become unmaintainable?
Android was initially for Apps
Documentation & samples are small scale
Old build system was deficient
Testing tools are still sub-par
Framework was not designed for testability
Performance trumped clean code
You must build a system to counter software entropy
Software Entropy
“Legacy code is code without tests” - Michael Feathers
“As a system is modified, its disorder, or entropy, always increases. This is known as software entropy.” - Ivar Jacobson
System
CI Server
Build
UI Tests
Functional Tests
Unit Tests App
Espresso
System Thinking
How will each component be tested over its lifetime?
What leads to the minimum cost of development including QA time?
If a library makes it easy to add a feature but harder to test, is it worth it?
If a tool breaks CI, is it worth it?
Testing Strategy
Customers in Production
Manual QA by Team
Unit Tests
Integration tests
Automated UI Tests
Manual Testing
Low initial cost
Need to acceptance test every feature at least once
Never can be completely eliminated
Does not scale with complexity
Extremely expensive long term
QA Options
Helps make manual testing cheaper & more effective
Network connectivity
Environment
Mock Data
QA Specs
Look at Gherkin / Cucumber for examples
Helps to make tests more repeatable
Can use to outsource testing to other firms
Painful & expensive
Automated UI Testing
Appium / UIAutomator type tests
Usually leveraging the accessibility IDs
Expensive, flakey
Can be layered onto most apps after the fact
Functional Testing
Most Android ‘unit testing’ examples are functional
Robolectric & InstrumentationTests
Generally scoped to an Activity / Fragment / Service
Time consuming to run and nearly as flakey as UI tests
Useful but should be minimized
Functional Testing ChallengesGeneric Problems
Asynchronous
Network based
!
!
Android Specific
Lifecycle
Context
Platform singletons
Functional Test in Theory
assertEquals(conditions, label.getText());
insertMockDataIntoPreferences(conditions)
waitForActivityAndFragmentToInitialize()
label = findViewById()
waitForDataToLoad()
Actual Test
BoilerplatePreconditions
testWindSpeedLabel_MetricSystem
Functional Test in Practice
Pitfalls for a Functional TestIs the user logged in?
Wait for Activity, Fragment lifecycle to load screen
Wait for network call to finish
Wait for data to load from DB
Find the TextView
Finally, check the TextView’s content & color
Unit Testing
Narrow in scope, each test is 1 case for 1 method
Do not test Android platform, therefore run quickly
Cheap to write if architecture is designed for testing
Flexible for refactoring
Should be 80% of your tests
Unit Test in Theory
assertEquals(conditions, label.getText());
given(conditions)
testWindSpeedLabel_MetricSystem
Unit Tests in Practice
So why is this so hard?
Activity Fragment Adapter
ForecastActivity ForecastFragment ForecastAdapter ForecastModel
Keep Android At Arm’s Length
StrategiesCovered
Dealing with K/V Store
ViewModels, Presenters, Custom Views
Not Covered
Testing network calls
Testing SQLite / ContentProviders / ORMs
Weather Buoy
Master list of buoys
Detail screen with wind & wave forecast
Setting to choose units
github.com/krschultz/AndroidWeatherBuoyDemo
ToolsExamples uses only
Android standard JUnit & InstrumentationTests
Mockito
AssertJ
Better tools exist
Espresso
Robolectric
Dagger
SharedPreferences
How do you catch this?
How do you catch this?
Wrapping Preferences
Testing Preferences Wrapper
Using wrapped Preferences
Using mock Preferences
Wrapping Shared Preferences
+ Only 1 place to test interaction w/ SharedPreferences
+ Handle versioning
+ Keys don’t leak out
+ Can now mock SharedPreferences w/o Context
- More code
UI Unit Testing Strategies
Custom Views
Optional TextView
What happens when we refactor the layout?
How do we know this feature doesn’t break?
Optional TextView
Testing Optional TextView
Instrument View
LabelValue & Units
Custom View
+ Clean API
+ Reusable view logic
- Compose-ability issues
ViewModels
SharedPreferences
Views
ForecastFragment
WaveForecast
ViewsViews
WindForecast
Preferences
API Client
View Models
Add presentation logic to underlying models
Combine multiple models
Do not have a concept of Activity/Fragment lifecycle
Do not have a reference to View
Using a ViewModel
Fragment is responsible for loading models, networking, finding views, threading
ViewModel is responsible for business logic
Fragment should fail in all cases
“Presentation” Logic
Colors, visibility
Interaction between multiple views
Formatting / humanizing properties
i18n
Combining Models
BuoyDetailViewModel
WaveForecast PreferencesWindForecast
SharedPreferences
Views
ForecastFragment WaveForecast
ViewsViews
WindForecast
Preferences
ViewModel
API Client
ViewModel Creation
No reference to view at all
All models can be immutable
Using a ViewModel
Create view model at the end of network call or DB fetch
Pass Views to ViewModel for updating
Logic in a ViewModel
PitfallsCreating a ViewModel getter for every view property
Keeping references to views in the ViewModel
Testing
Use Cases
Great for screens displaying detail about an object
Not ideal for collections of items, but Adapters can be tested similarly
Not a great fit for screens with many interactions
Presenters
Presenters
Same general idea as ViewModel, but includes reference to View
Requires coordinating with Activity/Fragment lifecycle
Potential for Context leaks if not handled correctly
More full featured, can take ownership of fetching data, threading, network calls, etc
Views
ForecastFragment WaveForecast
ViewsViews
WindForecast
Preferences
Presenter
View Interface
Fragment
Presenter
Fragment Continued
User interactions delegated to presenter
Can test using the same methods on the presenter
Presenter Continued
All business logic belongs in presenter
Calls back to view for update, remember the view may be gone
Use Cases
Can handle state changes caused by user interaction very well
Really should use Espresso / InstrumentationTests to ensure that click handlers call appropriate methods in presenter
Summary
You are building a system, not just an app
Cost of testing over long term becomes extremely expensive
Find ways to keep the platform at arms length
Consider how to test each component from the start
Further Reading
www.objc.io/issue-13/viper.html
martinfowler.com/eaaDev/uiArchs.html
Contact
@kevinrschultz
github.com/krschultz/AndroidWeatherBuoyDemo
kevinrschultz.com
Questions?
Backup
Law of Demeter w/ Context
Image Attribution
!
Tent -
House - https://www.flickr.com/photos/18702768@N04/2294709357
Cake - https://www.flickr.com/photos/personalcreations/14888675499
Long Term Cost
0
17.5
35
52.5
70
1 2 3 4 5 6
Manual Unit Tests UI Tests Test Fixtures
Stage 1 -> No desire for tests
Stage 2 -> Desire tests, but can’t build the test suite
Stage 3 -> Have a test suite
Stage 4 -> Nirvana. Bugs find themselves.
But I Don’t Have Business Logic!
“It’s just simple data in the UI!”
Not for long (PDP examples)
Creating a place for the tests to go from the start makes it easy to add them (UUID example)
Every single thing is small, but the sum of testing them all is expensive
top related