Join Chiu-Ki Chan for an in-depth discussion in this video UI-less on-device test: RecipeStore, part of Effective Android Testing for Mobile Developers.
- [Instructor] The recipe class parses a single recipe into a Java object. Let's use that to build a recipe store with a list of recipes. I have shown you how to do strict test-driven development, always writing the test code before the implementation. However, I often write the implementation first but write the test immediately afterwards. This flows better for me, and I'm going to use that style for the rest of this course.
Just remember not to write the implementation and then skip the test. To create recipe store, right-click on data.model, New > Java class. Name it RecipeStore, and change the package to data.local. Press OK. Create a constructor which takes a context and a string directory. We will need to import the context, but it'll be annoying to do is every single time.
So I'm going to turn on auto import. Go to Preferences and search for Auto Import. Click on the box that says Add unambiguous imports on the fly, and press OK. And now, Android Studio imported context automatically for me, and it will do it from now on. In the constructor, we will take the context and the directory so that we can read from the asset folder.
We will create a helper method to do that. Create a private static helper method called getAssetStreams. Private static. We want it to return a list of inputStream. The method name is called getAssetStreams, and it will take an AssetManager, we will call manager, and then also the string of the directory.
This is getting long, so let me close the project tab. In this method, we will need to find all the files that contain the recipes. Once again, we will add a helper method. Private static String array, getFilenames. It takes the same parameter as getAssetStreams, so I'll copy and paste that from line 14. First of all, if the directory is null, we will just return an array that contains nothing.
We do that instead of null so that it will not crash. You use new String and then zero to make an empty array. Next, we will call the asset manager.list and then pass it the string directory. This will return the list of strings that contains all the files. Press Alt + Enter so that we can surround it with try catch. Instead of printing stack trays on line 27, once again, we are going to return an empty list.
Okay. Now that we have the getFilenames helper function, we can use that in getAssetStreams. We will take the return value off that helper function and assign it to a variable. String array filenames equal getFilenames, pass in the asset manager, and the directory. And then, we will try to loop through these filenames and create recipes from each of them.
For (String filename : Filenames). First, we need to make a file from the filename plus the directory. File, file, new File, directory, comma, filename. Next, we will need to pass this file to the asset manager so that we can get an input stream and use that in the return value. InputStream stream = manager.open, and then file.getPath.
Press Alt + Enter to surround it with try catch. If we encounter any error, we will do nothing. Go back to the try block in line 22 and afterwards, let's check that the stream is not null. And in that case, we will want to add it to our return value. Streams.add(stream). Streams is the list of input streams that we're returning, but we have not defined it yet. We are going to jump back to line 18 to define this variable.
List<InputStream> streams, and let's initialize it to an empty ArrayList. Finally, go to the end of this function in line 30 and return streams. Okay, we spent all this time just writing helper functions. Let's actually parse some recipes. Go back to the constructor to line 14. In the constructor, we will call getAssetStreams and assign it to a local variable.
List<InputStream> Streams = getAssetStreams, and then we will make an asset manager by calling context.getAssets. The second parameter will be the string directory. After we have a list of streams, we can iterate in it and create recipes. For (InputStream, stream : streams), we are going to generate a recipe by calling readFromStream.
Recipe recipe = Recipe.readFromStream, and give it the input stream. If the recipe is not null, we will go ahead and add this to a list of recipes. Once again, we will need to create the variable recipes. We need to store recipes in a member variable so that later, we can retrieve it. Go to line 15 and add a public final variable called recipes.
Public final List<Recipe>, and then recipes. We will initialize it with an empty ArrayList, and that's it. We have our basic implementation of the recipe store, which parses a list of recipes out of an asset directory.
- Why test?
- Local vs. on-device
- Code coverage
- UI testing
- Hermetic environment
- Dependency injection
- Testing with MVP