By Thomas Endres & Andres Würl both Senior Consultant from TNG Technology Consulting https://www.tngtech.com
Join the Ultracode Munich meetup: http://www.meetup.com/Ultracode-Munich/
3. The speakers
Andreas Würl is an IT consultant for TNG Technology
consulting currently working in Unterföhring. In his free
time, he is contributing to the Blitzortung app available for
Android and in development for iOS.
Thomas Endres is also an IT consultant for TNG
Technology consulting. In his free time, he is developing
software for controlling drones with bare hands, building
apps and contributing to HTML5 frameworks.
4. Our apps
Blitzortung
Simple to use map based application visualizing real time
lightning data provided by blitzortung.org. The current
thunderstorm situation at your fingertips.
Be Quiet - The noise alert
Whether you work in an office or in a class room, Be
Quiet will help you reduce noise. When the volume is too
high, it will blink and play a siren sound.
6. Building Android applications
Old school
Based on Ant
No built-in dependency management
Quite inflexible
Using old built-in library versions
No support for real unit tests
Test project needed for instrumentation tests
9. Building Android applications
The alternative
Based on Maven → Maven plugin
Allows for dependency management
A lot more flexible
→ But still far from being perfect
Real unit tests are possible
Still using a test project for instrumentation tests
11. Building Android applications
The new way
Based on Gradle → Gradle-Plugin
Built-in dependency management
Using common Java patterns
→ But flexible enough to change that
Real unit tests through plugins
Instrumentation tests within the same project
14. Android Studio
The facts
Based on IntelliJ
Ready to use
No additional plugins needed
Brings shortcuts for AVD and SDK manager
Out of the box support for the new build system
Possibility to migrate old projects
17. What the heck is Roboguice?
A dependency injection container
An implementation of JSR 330
A fork of the Guice framework for the JDK
Easy to configure and to use
18. Dependency injection
Instead of taking
public class MainActivity extends Activity{
private LocationManager locationManager;
public void onCreate(Bundle savedInstance) {
// ...
locationManager = (LocationManager)
getSystemService(Activity.LOCATION_SERVICE);
}
}
be given
public class MainActivity extends RoboActivity{
@Inject
private LocationManager locationManager;
public void onCreate(Bundle savedInstance) {
// ...
}
}
19. Principles of DI
Don't let a class create objects on its own
Instead, pass them the objects they need
Then you can exchange them for test purposes
You can pass in test doubles
But you can also exchange the "real" object easily
Loose coupling becomes a reality
20. How can you inject objects?
Through the constructor:
@Inject
public MainActivity(LocationManager locationManager) {
// ...
this.locationManager = locationManager;
}
Into the field itself:
@Inject
private LocationManager locationManager;
Into a property:
@Inject
public void setLocationManager(LocationManager locationManager) {
this.locationManager = locationManager;
}
21. What can be injected?
Arbitrary objects with a zero-arg constructor
Objects with a constructor managed by Roboguice
Views:
@InjectView(R.id.specialButton)
private Button button;
Resources:
@InjectResource(R.drawable.specialPicture)
private Drawable picture;
A lot of standard Android objects:
LocationManager, AssetManager, ...
AlarmManager, NotificationManager, ...
Vibrator
22. Robo* classes
For DI to work, you have to extend the robo classes:
Use them instead of the standard Android classes
RoboActivity instead of Activity
RoboListActivity instead of ListActivity
RoboService instead of Service
RoboFragment instead of Fragment
...
23. Injecting providers:
Sometimes, you need more than one object of a class
public class SomeObjectProvider implements Provider<SomeObject> {
@Inject
private SomeOtherObject someOtherObject;
@Override
public SomeObject get() {
return new SomeObject(someOtherObject);
}
}
private class SomeObjectUser {
@Inject
private Provider<SomeObject> someObjectProvider;
private SomeObject getObject() {
return someObjectProvider.get();
}
}
24. Injecting injectors
You can also inject an injector
Then, you can get arbitrary objects out of the injector
@Inject
private Injector injector;
public <T> T giveMeAnObjectOf(Class<T> clazz) {
return injector.getInstance(clazz);
}
26. Configuration
By defining a module, you can configure the objects injected
public class SomeModule extends AbstractModule {
@Override
public void configure() {
// Bind an interface to a specific class
bind(SomeInterface.class).to(SomeImplementation.class);
// Bind a standard provider to the class
bind(SomeClass.class).toProvider(SomeClassProvider.class);
}
}
Modules are discovered via "roboguice_modules.xml"
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="roboguice_modules">
<item>com.mypackage.SomeModule</item>
</string-array>
</resources>
27. Integrate Roboguice (1)
Add roboguice to the compile dependencies:
// build.gradle
dependencies {
// ...
compile 'roboguice:roboguice:2.+'
}
Extend the Robo* classes in your objects:
public class SomeActivity extends RoboActivity {
// ...
}
Inject your dependencies:
@Inject
private SomeObject someObject;
28. Integrate Roboguice (2)
Configure the module:
public class SomeModule extends AbstractModule {
@Override
protected void configure() {
bind(SomeClass.class).toProvider(SomeClassProvider.class);
// ...
}
}
Register the module:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="roboguice_modules">
<item>com.mypackage.SomeModule</item>
</string-array>
</resources>
Write unit tests:
public class SomeActivityTest {
// How to do that?
}
30. Android testing
in new build system
Based on JUnit3
Requires separate test project
Requires emulator or device for execution
Lacks real mocking
But initial support for some frameworks
32. Can I run tests locally?
No. It's impossible!
Any method of the SDK will throw the following
exception when called:
java.lang.RuntimeException: Stub!
at android.*
Why is that?
Android SDK jars for development only contain method stubs
Is there a solution?
Yes! Use Robolectric
33. What the heck is Robolectric?
Android SDK wrapper/enabler for local test execution
Just another dependency of your project
Sometimes dependency order is important
Uses some magic to enable use of the stubbed SDK jars
Unfortunately not yet complete
34. What do I get?
Tests are running on the dev machine
Current version of JUnit 4 is used
Any Mock- or Match-Framework can be used
Can be used in parallel with instrumentation tests
35. How do I enable Robolectric?
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.6.+'
classpath 'com.squareup.gradle:gradle-android-test-plugin:0.9.+'
}
}
apply plugin: 'android'
apply plugin: 'android-test'
...
36. How do I implement a test?
Just use the RobolectricTestRunner
@RunWith(RobolectricTestRunner.class)
class SomeActivityTest {
@Before
public void setUp() {
// Preparation for every test
}
@Test
public void testSomething() {
// Your test code belongs here
assertThat(1, is(not(2));
}
}
37. Basic concepts
Shadows
TextView textView = (TextView) mainActivity.findViewById(R.id.helloWorld);
final ShadowTextView shadowTextView = Robolectric.shadowOf(textView);
assertThat(shadowTextView.innerText(), is("Hello World!"));
Implementation in Robolectric
@Implements(TextView.class)
public class ShadowTextView extends ShadowView {
@RealObject TextView realTextView;
@Override
public String innerText() {
CharSequence text = realTextView.getText();
return (text == null || realTextView.getVisibility() != View.VISIBLE) ? "" : text.toString();
}
@Implementation
public void setPaintFlags(int paintFlags) {
this.paintFlags = paintFlags;
}
}
38. Basic concepts
Robolectric builds up a full application context
Activities can be built
activity = Robolectric.buildActivity(MainActivity.class).create().get();
Testing resource access is possible as well
Resources resources = Robolectric.application.getResources();
assertThat(resources.getColor(R.color.Red), is(0xffff0000));
Modify preferences for tests
SharedPreferences defaultSharedPreferences =
ShadowPreferenceManager.getDefaultSharedPreferences(
Robolectric.application);
defaultSharedPreferences.edit()
.putBoolean("test", true).putFloat("limit", 1.0f).apply();
39. But ...
Android Studio integration is not yet available
Tests can be run via gradle task 'test'
> gradle test
IDE support only through ugly hacks
40. Summary
The new build system is a lot more flexible than the old one
Android Studio is a cool new tool for app development
It comes bundled with the SDK, you can start development
immediately
But there are still some issues with it
Roboguice makes it possbible to decouple your application
Robolectric can be used for local test execution
42. Thank you!
Are there any questions?
andreas.wuerl@tngtech.com, thomas.endres@tngtech.com