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.
Some folks feel that you cannot TDD your UI layer with a functional testing framework like Espresso.
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 …
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?
Remember, TDD is a software development process that can be applied anywhere in software.
As always, please leave comments below. Thank you for reading. 🙂