Testing on Android
- Instrumented Test
- Local Test
Local Test Class
Instrumented Test Class
Naming Conventions & Best Practices
TDD: Dos & Donts
LiveData, ViewModel & AndroidX Test in Action
The Testing Pyramid
Mockito In Action
Writing Tests: Why and When
In Android Studio, if you open any Android project, you'll find these 3 default Source Sets :
- Runs on real or emulated Android devices
- Hence, reflect what will happen in the real world
- Hence, slower
- Runs locally on your development machine's JVM - emulator or physical device not required
- Hence, they act less like they would in the real world
- Hence, faster
Instrumented VS Local
||reflects real world
||doesn't reflect real world
Let's Start Digging!
- A test class is just a normal class
- In the class, each function is a single test
- Each test is annotated with @Test
- Tests contain assertion statements
Let's Keep Digging!
- An assertion is the core of your test. It's a code statement that checks that your code or app behaved as expected
- @Test is a JUnit annotation, assertions are also from JUnit
- JUnit is a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.
Things to note!
- A single failed assertion fails the entire test
- You are directed to the line of the failed assertion directly from console's Run tab so no worries
Let's test locally, first
- Right click on any method (say, MyMethod) and select Generate > Test
- Change the Class name: to MyMethodTest (instead of MyMethodKtTest)
- Keep the rest of the defaults. JUnit 4 is the appropriate testing library
- The destination package is correct (it mirrors the location of the StatisticsUtils class) and you don't need to check any of the check boxes
Let's test locally (contd.)
- You'll be making a local test because your function is doing math calculations and won't include any Android specific code
- So, there's no need to run it on a real or emulated device
- Select the test directory (NOT androidTest) because you'll be writing local tests
Generate a Test Class
Let's right click on that method & hit Generate > Test
Choose test, not androidTest! Remember why?
Naming Conventions & Best Practices!
- How do you think you should name a test method?
- What should I EXACTLY do in my method?
- How to do the ACTUAL testing?
- Is there a all-time-go-to format?
- Or, even better - a mnemonic or something?!
Tip 01: Naming Conventions
- Subject under test is the method or class that is being tested (maybe, testItems).
- Next is the action or input (maybe, _itemsNotNull).
- Finally you have the expected result (maybe, _returnsCount).
Tip 02: A Testing Mnemonic
"Given, When, Then" (GWT)
- Given: a list
- When: the method
- Then: an expected output
"Arrange, Act, Assert" (AAA)
Let's run our Test Class
All Tests are Passed!
But what if this error comes up?
Do this. Thank me later.
TDD: The True Essense
- Test Driven Development (TDD) is a school of programming thought that says instead of writing your feature code first, you write your tests first.
- ONLY then you write your feature code with the goal of passing your tests.
- TDD follows these steps:
- Write the GIVEN-WHEN-THEN test
- Confirm the test fails
- Write the minimal code to get the test to pass
- Repeat for all tests!
TDD in Action
Tip 03: Dr Jekyll & Mr Hide
- Be honest, Doctor & Kill Mr Hyde!
- The test that always passes without you writing any code to make it pass/fail is PROBABLY a USELESS test
- Wear the hat of a QA, not just a DEV
- A helpful method for me is actually take that function as a competitive programming small problem only with numbers, then try to use that skill to generate the edge test cases
Don't be this guy, please!
Also, not this guy too!
Writing tests can be tough. True.
But there's always a QA
... to ruin every feature we ship ...
A bit on Gradle Configs
- implementation—The dependency is available in all source sets, including the test source sets.
- testImplementation—The dependency is only available in the test source set.
- androidTestImplementation—The dependency is only available in the androidTest source set.
LiveData, ViewModel & AndroidX Test
- Open the ViewModel class you wish to test, right-click on the class name, then click Generate -> Test
- On the Create Test screen, click OK to accept (no need to change any of the default settings)
- On the Choose Destination Directory dialog, choose the test directory
LiveData, ViewModel & AndroidX Test
But wait, is it a local or an instrumented test?
The clue is in the last slide, and in your memory
Okay but why is it Local though?
- Pure view model tests usually go in the test source set because the view model code shouldn't rely on the Android framework or OS
- As a local test, it will also run faster because you run the tests on your local machine and not on an emulator or device.
What if I need to test a constructor with an Application Context?
- You need AndroidX Test libraries
- AndroidX Test library includes the method ApplicationProvider.getApplicationContext (Click here to know more)
- The AndroidX Test libraries include classes and methods that provide you with versions of components like Applications and Activities that are meant for tests
LiveData, ViewModel & AndroidX Test (Contd.)
- We need to do two things to test LiveData:
- Use InstantTaskExecutorRule
- Ensure LiveData observation
- InstantTaskExecutorRule is a JUnit Rule
- This rule runs all Architecture Components-related background jobs in the same thread so that the test results happen synchronously, and in a repeatable order
- Unit tests: Testing single methods in view models and repositories.
- Integration tests: Testing all the functionality of a single fragment and view model pair.
- End to end tests (E2E): Starting up the entire app and testing a few features together.
The Testing Pyramid
- A test double replaces the real version of a class in tests.
- It's similar to how a stunt double is an actor who specializes in stunts, and replaces the real actor for dangerous actions.
Types of Test Doubles
- Mockito is a mocking framework
- It's a framework for making test doubles. It can also make stubs and spies.
- Readable & clean verification errors
- There's a very popular library with helper functions in Kotlin: Mockito-Kotlin
Mockito in Action
- Mock any class using @Mock annotation
- Use @Before to setup/initialize the dependency/viewmodel before performing any test
- Use verify() method mto check if a method of a mock object has been called or not
I'm tired already! What's next?
Happy New year! Let's welcome 2021