A recent conversation developed on twitter when I posted a link to a Caster.IO lesson where I talk about using TDD to drive your UI development.
The problem?
Some folks feel that you cannot TDD your UI layer with a functional testing framework like Espresso.
I disagree.
Why? Well, before I dive into details, I thinks it’s only pertinent to re-establish what TDD is.
What is TDD?
TDD by definition is: Test Driven Development. TDD is a software development process.
Key word: process.
The process of TDD is comprised of 5 steps:
- Add a test – each new feature, update, fix, etc starts with writing a test to cover that new code/change/etc.
- Run all tests and see if the new tests fails (typically to save time, I’ll run the new test in isolation to speed things up, then loop back later to run all of them to check for regressions).
- Write the code (this is what you orignally needed/wanted to do anyway – implement a feature/etc).
- Run the test(s) – Again, typically I’ll run my single new test in isolate to speed up this process a bit. If it passes, I run all the tests to see if I broke anything.
- Refactor – After all tests are passing, you now have a safety net to catch you inΒ case you make a mistake. At this time you can now start refactoring to change the desired implementation of the code.
Then … repeat the steps for each new feature/update/fix/etc. You may need to do this many times over. One test for the “happy path” (when everything works as you hope/expect it would), one for all the edge cases, one for null value paths, one for catastrophic failures, etc etc. You usually end up with may tests covering all the different expected (and unexpected outcomes) for that given code path.
So, how do you TDD your UI Layer?
Let us keep the full TDD process in mind when walking through this example …
This means writing your test first, even before particular UI elements exist. In this case I’ll do it with Espresso, the defacto Android UI testing framework along with jUnit as the driver.
Step 1: Create the Test
I’ll create a test like so:
As you can see, the buttonx id does not exist yet (this is why it is red). At this point I’d go implement the button and we’d then see something like this:
The id, buttonx, is now found (it changed colors and we can navigate to its definition now). How I implemented it is not important as it’s an implementation detail and we’re worried about the TDD process here. I can compile successfully. Ok, now I can move onto step 2 of the TDD process:
Step 2: Run The Test
I run the test and I expect it to fail because the text “Hello from buttonx” does not exist. If it does not fail (meaning that it passes) I have a problem and I need to dig into that. Let’s assume it fails and at that point I’m ready for Step 3 …
Step 3: Write The Code
At this point I’m able to write the code that is needed in order to get this code to pass (whatever that may be – adding a click listener to buttonx and having it output “Hello from buttonx” somewhere on the screen). Then move onto step 4 …
Step 4: Run the test(s)
We run the test(s) to make sure that the tests passes and to make sure that other tests still pass. This is integral to the TDD process. You will want to run all the tests (usually at least all the tests that interact with this component/screen/etc) to catch any regressions that may popup because of this new code you have added. After everything is green (passing) I’ll move onto Step 5 …
Step 5: Refactor
This is where I’d come in and clean up the code to make it more proper. Maybe make things more private, final, or extract methods, etc. Maybe I find an area of code that I can improve the design on/etc. If thats the case; for example maybe I’ve refactored some logic into a new class I’d introduce a new TDD test process for a new refactoring/etc. When would that happen or why? Maybe I see that I’ve duplicated code in a few places and I can extract this into another class. At that point I’d probably want some tests around that class so I’d go through the TDD process with that class.
I’ve followed TDD to implement a feature/change a chunk of code/fix a bug/etc that is on the UI using Espresso.
Most importantly – I used TDD a process to implment it.
Arugments Against TDD in this Context
Sure, this example is fairly contrived and it’s super simple, and it’s that way for a reason – I’m trying to demonstrate a point. TDD is a software development process. Alas, there are some folks who feel that TDD represents something different. Let’s chat about those opositional points of view.
TDD should not use a UI Testing Framework
I completely disagree with this because TDD is a software development process. The UI is part of the software and if you want to develop it using TDD then you can. Step 1 states that we need to write a test. Does it matter what framework we use to write it in? No. IMO, any test is better than no test (but thats another topic for another day). Looking at the Wikipedia entry for TDD I found the following under the definition of Part 1 – Add a test:
The developer […] can write the test in whatever testing framework is appropriate to the software environment.
We’re writing the UI, therefore Espresso is an appropriate testeing framework to use as it is a UI testing framework. Right tool. Right job.
The reason most folks oppose following TDD with UI testing is because it’s slow, which brings me to the next item …
When doing TDD, all tests should be fast
I totally can relate to this one. I’ve been part of teams that have HUGE Espresso/UI test suites and they can take hours upon hours to complete. Running the full test suite can be a pain and it’s simply not feasible. In this case I rely on CI to run full test suite. During development though, for steps 2 and 4 (where we run the tests) I’ll run a small subset of tests. Usually the test(s) that I’m writing or the small suite of tests in that test file/package that is pertinent. This allows me to remain nimble. Typically this is anywhere from one to twenty tests (give or take).
I agree, this is slow compared to JVM unit tests that can run at hundreds (if not thousands) per second. However, I’m doing UI development here so given the state that UI testing is in (for Android) we’re unfortunately stuck with some slower tests. It is what it is, but the TDD process can still be used.
Doing TDD on the UI is an Anti-Pattern
I’ve seen this mentioned a few times, and it’s mainly because having slow running tests is an anti-pattern … but as I stated above, that’s the situation were in. How do you get around that though? Extract your logic out into something like the MVP pattern. You can then test the majority of your code in fast jvm tests. You’ll still need a thin layer of UI tests to make sure your UI works as you (and your customers) expect it to.
I say this because you need to verify what your customer is going to see … because …
If your apps UI fails/crashes/doesnt do what it is supposed and it does not do what the customer wants, they’ll think its garbage. It doesnt matter if your app is beautifully architected with fancy patterns and so forth. If the UI doesnt work and do its job as expected, the customer is unhappy and will most likely not use your app. At that point all your work is moot.
How many times have you used an app and initially it looked great, but then when you started using it … well … it just felt like things were wrong. What was your reaction then? Most likely a negative one. You probably didnt use the app or even uninstalled it right away. I definitely don’t want that and I’m sure you don’t either. #uitestsmatter
What I’m trying to say is, UI tests are extremely useful, even if its a thin layer and that layer can be test driven thorugh TDD.
… but, I heard TDD is Dead
This has been going around for the last few years. I’ve been through the TDD inception/rediscovery in 2003 by Kent Beck and seen it rise and fall and rise again and so forth. It’s cyclical. One moment it’s in favor, the next its not. The hard part is … TDD is hard. With some languages TDD is seen as a way to help improve the overall design of the app (which is usually the case with statically typed languages like Java). Dynamic languages have some other benefits when it comes to testing and mocking that statics do not (but they have their own downfalls too, which I wont get into here).
Back in 2014, Martin Fowler, Kent Beck and DHH hosted a series of online videos where they discussed if TDD was dead or not. You can find them here – Is TDD Dead? I’ll let you decide if TDD is dead. Each person in the group had great points and at times I agreed separately with each one of them on different topics.
I’m not here to argue if TDD is dead. I’ll leave that up to you to determine.
However, I do hope that this article does prove the point that TDD can be possible with UI development.
So … Is TDD with UI Development Possible?
Yes.
Remember, TDD is a software development process that can be applied anywhere in software.
As always, please leave comments below. Thank you for reading. π
Anvith Bhat says
Hey Don,
Just wanted to know what’s your argument against using Roboelectric for unit testing UI (if any)?, I see this unsaid notion that everyone is hesitant in using it, though I can’t substantiate why. I find it very painful to run unit tests with espresso while developing, although for a true integration test I would end up using it nonetheless.
Litrik De Roy says
Kudos for writing this post. Twitter is simply not suitable for such a detailed argument.
Donn Felker says
Hey Litrik, I completely agree. π
Donn Felker says
I don’t have an argument against using it except that sometimes you run into things that Robolectric has not implemented and it can be kind of cumbersome to figure out how to implement those missing features. That in itself can burn up a ton of time. Other than that, if it works for you, then use it. However, please be aware that this is not really full UI testing as you do have a layer that is intercepting the calls and it’s not actually running the code on the Android system. Just something to be aware of. I have known folks who have used its successfully with a large number of tests. If it works for you, and your app is under test, then use it. π
I’m a consultant so when I leave I like to leave the project in a state where all developers entering the project will find a field of familiarity and will feel comfortable, hence why I default to the defacto tools recommended by Google.
Rakesh Patel says
I think its ironic that your blog is subtitled “Lessons Learned From the Software Industry”, because in this case, you haven’t.
No one said its impossible to write a test TDD style against the UI. The problem is there is so much more to the UI than the existence of a widget. So much goes untested leaving simple things tested, thats its not worth the time and effort to write and maintain.
sebaslogen says
Well explained, I appreciate the point you made about writting UI tests to ensure you are writting the right application, it’s something a lot of people tend to forget while being very focused on writting code.
IMHO, the real advantage of test driven development starts when you design the test before writting the code, even if the test scenario doesn’t end up being automated or if you write the test code after writing the app code, the most important point is that the tests lead the implementation of the app.
PS: Thanks for the link to the “TDD is dead” thread!
sebaslogen says
The biggest problem of using Robolectric like Donn said is that you’re not really testing that the actual layout and UI are working fine so you could create a false sense of confidence. In contrast, if you have some logic that depends on Android framework (e.g. Bundle class), I think Robolectric is the perfect tool for the job.
If you have a really large set of UI states that you have to test I guess it’s fine to use Robolectric with most of them and leave some in Espresso but I suggest to leave always some real UI tests because like the article says you want to ensure the app you ship is what the customer asked for, not just what the code says.
Mohsen Mirhoseini Argi says
Hi Don, once again another perfect article. good luck
Said Tahsin Dane says
I like it. Thanks for the article.
Although running only 1 UI test is much faster than running all, it is still lot slower than a unit test.
Also we run our UI tests with proguard to be as close as the release builds. This makes UI tests much slower than necessary and almost impossible to do TDD. But I think we can make this proguarding optional.
Zeyad Gasser says
Great article. Just a question. How do you feel about roboelectric, shouldn’t that fix the slow tests problem ?
Donn Felker says
This is definitely an evergreen debate. In my opinion, there should be a decent UI level suite and then a fully comprehensive Unit testing suite under that layer (that is much faster). Finding the proper balance is unique to each application, no doubt about it.
Donn Felker says
It can, somewhat, but then you’re relying on an abstraction on top of Android. If you’re comfortable with that, by all means go for it! π
Donn Felker says
Thank you
Donn Felker says
Agreed. It definitely is slower. I recently developed a feature through some UI tests and its been cumbersome, but when I was done I KNEW I had implemented it correctly. Under that was a suite of unit tests as well. Again, its all a balancing act. π Thanks for the comment!
Donn Felker says
@sebaslogen:disqus I totally agree. TDD helps you drive your design of your app because you’ll realize how poorly you’ve possibly implemented something and TDD will help you improve that design (given the proper knowledge). Cheers!
Rakesh Patel says
Actually, this problem predates Android, thats why its only you that thinks its a debate.
Assuming you follow MVP on Android (you do right?), then imagine what the activity you create looks like with TDD and not doing TDD. They shouldn’t be any different, correct?
Same applies to the XML, how will it be any different done with TDD than done without?
Those are just philosophical reasons. The practical ones, which you have highlighted put the final nail in the coffin!
Why don’t you invite me onto your podcast to discuss this further?
Donn Felker says
This is a blog post about how you can, in fact, do TDD on the UI layer. It seems to me that you’re reading into this too deeply. I’ve been developing software for well over 15 years and have implemented full testing suites in various languages and implemented MVP in many other languages and frameworks. I’m well aware that this problem predates Android. It predates most developers who are developing software nowadays. History in software (and many other aspects of life) is cyclical in nature.
The term ‘evergreen debate’ means that folks are going to feel one way or another depending upon their own persona beliefs and will voice those opinions for a very long time. My intent is not to debate this topic. In fact, I was wrong for even using such a word (debate). This is not a debate in any sense of the sort. It’s simple facts, backed up with evidence. No more, no less. The side effects of performing proper TDD is well documented elsewhere, I don’t need to repeat those bits on my site. Proper TDD encourages (and sometimes based up on the programming language enforces a better design – but even that is up for discussion and is discussed all over the place). I’m not here to argue any of that.
This blog post is a statement. A re-iteration of facts. Orthogonal benefits and aspects of said TDD practice are not the goal of this article.