Engineering Maintainable Android Apps, which is a 4 week MOOC that shows by example various methods for engineering maintainable Android apps, including test-driven development methods and how to develop/run unit tests using JUnit and Robotium (or equivalent automated testing frameworks for Android), as well as how to successfully apply common Java/Android software patterns to improve the extensibility and clarity of Android apps. Students will work on the appropriate automated unit quizzes, based on the material covered in the lecture videos. These lessons will demonstrate the benefits of good software engineering practices that are targeted at creating maintainable code for mobile apps.
There will be roughly 3-4 hours of student engagement time per week, including video lectures, and quizzes. The ordering of the modules within the course is designed to be flexible. In particular, students can watch the videos in whatever order suits their experience and needs, e.g., they may want to watch the unit testing videos prior to the software pattern videos if they prefer to learn about unit testing first.

Michael Walker

Dr. Jules White

Transcrição

[MUSIC] We've talked about the importance of unit tests and being able to measure the impact of the changes that we're making in our code, and how they effect the overall either functionality or non-functional properties over application. What I want to build to do now is dive in and show you the nuts and bolts of how you actually create unit test using Java's J unit framework. And were going to go through and create a little application and then we're going to create some simple task for using J unit. So what we've got here is a very simple project that's been generated by Android Studio for us, we're calling this Testing Example. And right now what we've got is we're pretending we're building an application that's going to have a log in activity or a log in screen that we start with. So as part of the wizard for Android Studio, we created this login activity or and it's been created, it's just a blank activity at this point. But then one of the things that you should notice is that android studio has automatically generated some testing info structure for us. So if you look here in the project explorer we see underneath this package there’s a version of this package that has android test in parenthesis to the right of it and then a version that has test in parenthesis to the right of it. Now, what these two versions of the package are providing is these are for Android unit tests. So these are tests, the first one are tests, that are going to require different types of services with an Android. For example, if you've got an activity and you want to directly test the activity. You're going to create tests for that within this Android test version of the package. For this for a time we're going to just focus on the test version, which is for all of the other things. So if you're testing functionality that doesn't rely on something in Android. It's just Java based. So for example, if you're testing something that's going to touch the GUI or infect buttons or activities it would go in the first one, the android test. Where is if you have a separate, let's say utility class like what were going to create in a minute. It doesn't touch android then we can put that down here is just the test version. So what we're going to do first is we're going to go create a new class and what we're going to assume is that our login activity is going to allow the user to put in email address and a password and we're going to create, actually we'll just call this LoginUtils and this is going to be a class. This is going to provide some simple validation for us for the login activity. So the first thing we might want to go do with a login utility class is we might want to be able to check if we have a valid email address. So let's say, we'll call this isValidEmailAddress. And we'll have them pass in the email as a string. So what are we going to do to check if this is a valid email address? Well, one thing we might want to do is I want to say, well let's check and see if it has the @ sign in it. So we could say boolean has @ sign and then we could say email.index of art. And this is just checking, that they have the @ sign. If they don't have the @ sign, then this will return negative one, telling us its not there. Now we might want to change this later and improve this. But we'll just do this first. So we're going to make sure it has the @ sign. And then maybe another simple task that we might want to do is, well actually why don't we just start there. So we're going to say that if it has the @ sign it's valid. And we'll just be very simple and methodical in the building of our test. So we've created this is valid email address. Method, since we're trying to be maintainable, we probably want to go ahead and create a comment for this. Now, if you didn't see how I did that, created that comment, or if you're not familiar with it I'll do it again. You just create a starter comment like that, and Android Studio, and the editor with a slash, and then asterisks, and then hit enter, it will automatically Fill in the comment with the parameter that we're going to take. And then we'll just say this method, checks if the provided string represents a valid email address and returns true if it is. So now we gotta comment and we got our basic method. So now what we want to do is we created this method, and we want to make sure that it actually works, that we don't have a problem. So let's go in, and create a unit test. Now, they've already created an example unit test for us right here. And I don't really want to have this unit test hanging out so I'm just going to go ahead and rename this unit test, so that I can co-opt it and I'm going to call this loginutilstest. Now this is something that's good to know, is in general when you're creating unit tests, particularly when they map to specific individual classes, you want the name of the test to be the name of the class with test appended. That just a typical convention for testing and it makes it easy for people to figure out where's the test for this class and if they see a test they go and figure out what class it maps back to. So I'm going to do the refractory. This is renamed our class and I'm going to go ahead and start changing this class. Now before they created a sample test method for us in here already and what a test method is, is it a method that's been declared within the class, the test class? And it has @Test placed at the top of the method. And so when you're creating test, what we can do is we can actually go and run this thing. So, when you have tests that have the @test annotation on the methods, you can just go over here and select them in the Project Explorer and then we can immediately go and run, log-in utils tasks done in here. We run that. What we'll see is that android will run and build which is running down here at the bottom and it'll run our tasks and show us one task passed over here. If we want to drill in this specific test methods we see this test addition is okay, pass. It shows us how long that method took the xq. So what we're doing is when we are going in creating a task we create methods and we annotate them with @task and then to run we can go over here in the package explorer and use the run command to do that. Now I don't really need this addition as correct I trust it. Addition is correct in the system so I'm going to go ahead and test valid a, valid email address, passes. So this is going to be my first test. So I'm going to go and create a test case that is when I checked that our login utils properly assert that a valid e-mail address is valid. So I'm going to say, login utils, I'm going to create a new one. And then I'm going to say, there's lots of different ways to do this, but I'm going to do assertTrue utils.IsValidEmailAddress. And I'm going to pass in a fake email address that should be valid. So what this has done is, is we're creating a new version and then what this assert is doing is we are passing in to assert true a condition that we expect to be true. So we expect the result of this expression that we're passing into the assert true. Call to be true, and if it's not, what assertTrue is going to do is it's going to throw an exception. So the first thing that we're going to do, let's actually go and show you what it looks like when we throw an exception. So we're going to say, we assert that foo@bar.com is not a valid email address. And would see what happens when we do that. So we go ahead and run the LoginUtils test. Now previously we had this addition is correct so we could have gone and if we still have the same method we could have gone down here and hit the selected and hit rerun. If we still have that method or we could go here and hit rerun but I'm just going to up here again and we'll run this again. It's going to run our build, which will take a second, and then we see that we now if failed the test of valid email address passes. And when we go and look over in the restain we can see we had an assertion error, which is what happens. When this fails, it throws an assertion error and if we want to go and look at the stack trace, expand that and we can go and look at it, see where this is happening. But this helps us to go and understand what's going on with our code. And we see that, okay, well our test is not passing. So we don't have a valid email address. Like well, our test is failing, and that's because a valid email address but when we went over here we had not, so it was saying it wasn't a valid email address when it passed this expression. So let's go back and save this. Now we're going to expect this test to pass. We run this again, what we'll see is that we should get a passed test, and we do. So now we've passed our test. So that was a simple test. Let's go and add an additional test. Let's say we would like to test some other property and invalid. Address fails, and you don't have to say throws exception, and I'll talk about what this means. You don't have to say throws exception in your test methods. But this is a nice handy little thing to do because if you put throws exception in here, than you don't have to try and catch inside of your test method, unless you really need to. So this gives you a blanket ability to go and call methods without worrying about if they throw exceptions because typically they throw exceptions during the test, you consider that a test failure. The only time you typically want to catch exceptions within your test methods is if you don't necessarily care if the method throws an exception or you want to do something about it, or test differently, or have different exceptions. So for this one I'll just go in and create a LoginUtils again. And then I'm going to searchTrueNotUtils.is valid email address and I'll just pass in the string invalid. Now I would expect this to be a valid email address and I got a typo here. Well I'll fix that really quick. I would expect this to be a valid email address, I would not expect this to be one. So now we can go up to LoginUtils again and run it. And what we should see, is now we'll be running two tests. So now we've got two tests. Valid email address passes and invalid email address fails. Now one thing you may notice is we're starting to get a little bit repetitious here. When we create new tests we're having to recreate the LoginUtils every time. And we don't necessarily want to have to do that. So there's different ways that we can go about recreating this thing. Now the way that JUnit runs, is every time it's going to execute one of our test methods. It is going to instantiate a new instance of the class and then it's going to call a method and then when it is done, it's going to throw that instance away. And before it runs the next type of test method it will instantiate a completely new instance of that class in order to run it. So if we want to be able to do the same thing as we're doing here we can actually just take this and move it up here, make it a member variable. And we can simply do this and we're all ready a little bit further ahead than we were before. We're saving ourselves a little bit work, and we'll see that if we run this again, we're still passing our test, we've just moved this up here. Now let's say, for example, that we needed to set some type of properties on the LoginUtils. Maybe it's not just instantiating it but we need to do a little bit more work to configure it. We can also do that with Jinit by creating a method that has the at before annotation. So what we would do in that case to do the exact same thing as we could just go down here and we could do this and we should get exactly the same result. In the set-up we're going to create the Utils method. And what will happen is, now go ahead and throw away these pop ups here. Each time we instantiate this class or method that's annotated with @before is going to then get called on the new instance and then our test method is going to get called. So with this version now, it should do exactly the same thing and we'll still run our two test methods again. So let's run this again, let me see how the test pass. So we know that this setup method is being run before each of our individual tests are being run. One of the things you may have noticed is that we're using this assert true method and our various tests here. Now where does this come from? Well, where the assert method comes from is if we go up to the imports at the top of the test class, what we can see if that we're going to be doing a static import for all methods in the class org.junit.Assert. So what a static import does is it goes and says every static method that is in that class should now just be accessible as if it was declared directly within this class. So for example, if I went and did org.jUnitAssert.AssertTrue we can see that is an equivalent method call to the one I was just making. So basically this is importing all of the methods in that class. Another way you can get the same thing was if you had an import that looked like this, if you had just import or .junit.assert, which we could do then we could go and say Assert.assertTrue. But as you can see if we're typing this over and over, it gets kind of tedious to type, assert.assertTrue ,when we know that all of them are declared in that method. So just using the import static in the test class saves us some typing. And makes it easier for us to read the code. It makes it a little bit more readable. So when you're creating tests, you're almost always going to want to have import static or .junit.assert.star at the top of your test class somewhere. So by doing that we get access to a lot of assert methods. So for example, if you just start typing assert, in Android studio, you'll be able to see there's just a huge number of them. There is assert equals, assert false, assert not null, that all kinds of different methods available to you. And I encourage you to go and explore some of these methods and see what they do. So, for example down here we could have done, Assert false, And remove the not sign. But there are also many many other things that we can do. So let's go and build out our testing a little bit more and see what we can do with this other assert methods. Let's say that we want to be able to go and look at the lengths of the local part of the email address. The local part is everything that comes before the @ sign. So for example, if we have a email address like this, the local part is the foo here. That's the local part, so that's what we're looking at. And we want to see how long is this local part. And this may or may not be useful, but we're going to start off by doing this and then show how we can use some different assert methods. So, let's go through and create that method, so we're going to get local part length. And we're going to pass in an email address. And the first thing I'm going to do is I'm going to fight to figure out where that at sign is and I'm going to say start = email.indexof @ sign. And then I can go through and say, okay well let's get the local part =email.substring (0,_start) which is really at sign is. And then, I'll return localPart and sorry localPart.length. So we're going to get the length of this localPart and return it. So, I create this method, and then I'm going to go ahead and add a comment, just so I know what it does. And I'll say, this method returns the local part of an email address, which is the part that comes before the @ in the address. The length, Of the local part, okay? So I've created my method. Now let's go back here and let's test it. So I add a new test and I'll say public void LocalPartLengthForValidEmailAdress let's say. So we start there, And, oops gotta typo, and we'll fix that. So we've now got our new test method. And what I'm going to say is assertEquals. So I'm going to expect the, [COUGH] size or the length of the local part of a at b dot com, to be one, and so I am going to run this test. See what happens, And we get a problem, it is not. And what we see is that when we, that it's not, it tells us that we expected it to be one. But the actual value return was a@b.com. And what we do is we look up here we say, well, whoops, I just made a silly mistake. And this is again this is exactly why we do unit testing. As many times as we've done something, it's still super easy to make trivial mistakes that can be very hard to track down. In this case I see exactly what the mistake is. I'm a little bit surprised that this test has failed, and I go back. But it tells me exactly why it was failing. And I realized, you know what I should have done is utils.getLocalPartLenght, and pass this in. Now if I go And test this. And I test this. Now I see that it passes. So I just gotten the test was actually written incorrectly. Now this is another thing that can be nice to do. It's often and it's helpful to actually go and write our test in a way that they initially fail. So we expect the test to fail, and this can help us to do a sanity check that our test themselves or doing the correct thing. So in this case I sort of accidentally did that, that building an incorrect test. But sometimes it's helpable to purposely go through, and as you're building your test, the first time you build it, build it so it fails. And then go back and put it in the state that you expect it to be in in order to pass. That way you can just do a little bit more sanity checking that what you're doing is actually correct and going to work.