24. @Test
public void insertBuilder_withTable_withColumn_doesNotThrow() {
new Builder().table(TABLE)
.addColumn(COLUMN1)
.build();
}
@Test(expected = IllegalStateException.class)
public void insertBuilder_withoutColumns_throwsException() {
new Builder().table(TABLE)
.build();
}
Similar tests
25. Test setup
@Test
public void test() {
List<Language> list = Arrays.asList(GERMAN, ENGLISH);
Mockito.when(mDataModel.getSupportedLanguages())
.thenReturn(list);
Mockito.when(mDataModel.getGreetingByLanguageCode(LanguageCode.EN))
.thenReturn("Hi!");
// The rest of the test
}
27. @Test
public void test() {
List<Language> list = Arrays.asList(GERMAN, ENGLISH);
Mockito.when(mDataModel.getSupportedLanguages())
.thenReturn(list);
Mockito.when(mDataModel.getGreetingByLanguageCode(LanguageCode.EN))
.thenReturn("Hi!");
// The rest of the test
}
@Test
public void test() {
new ArrangeBuilder()
.withLanguages(GERMAN, ENGLISH)
.withGreetings(LanguageCode.EN, "Hi!");
// The rest of the test
}
Arrange builder
40. “Everyone knows that debugging is twice as
hard as writing a program in the first place.
So if you're as clever as you can be when
you write it, how will you ever debug it?”
Brian Kernighan
Welcome everybody,
My name is Tomasz Polanski and I’ve been a mobile software developer for the past 10 years. During this time I made plenty of mistakes, but also I’ve learned from them.
That’s why I am excited to have this opportunity to share my knowledge with you about testing and why I think it's important.
Don’t want to break it
Surprising amount of bugs
Sit with a person
Feedback loop
Focus on UT
Shiny
Why don’t write
Whenever we join a project or inherit one form other team, the last thing you want to do is to break it.
As we know, changing a single line of code can fix one bug, but at the same time, it can create a surprising amount of new ones.
It would be nice to sit with a person that has done it, to tell you, what you can or cannot be doing.
That’s why we need some sort of an instant feedback loop to let us know if we broke anything with our changes.
That’s why tests were introduces.
Automated testing is a really big subject to talk about, that’s why I will mainly focus on Unit Tests. When I will say tests, I will mostly mean Unit Tests, unless said otherwise.
(Add new slide)
Getting a shiny new project with a whole suite of tests, nice architecture and documentation, does not happens so often.
That’s why I was interested why people not always write tests.
I’ve surveyed other developers asking them in what cases they wouldn’t be creating tests for the project.
Not surprisingly, the answers were similar.
Here are the most frequent.
And please rise your hand if you thought at some point the same.
Deliver within time & budget
Customer value is synonym of visual content
Goosebumps when refactoring
Refactoring does not change behavior
Enjoy PO
Refactoring part of process
Hard refactoring without test
Every team is pressured to deliver value to the customer within given time and budget.
For the project stake holders, usually value is the synonym of visual content, therefor some product owners focus to deliver as much UI changes as possible.
On the other hand, there are managers who get goosebumps when mentioning the word “refactoring”. That’s because refactoring by definition does not bring change to behavior of the product.
Developers and PO, that we enjoy working with, know that refactoring is part of the process and without it, project would be unmaintainable in the future.
Only verity behavior – no instant value
Invest in future
Being thankful for the test
If you write tests to verify proper behavior of your code, you don’t get instant value.
With tests, you invest in the future. Next time you change that code, you will be thankful that the test prevented you from making a mistake.
Speed important factor
Talk about distance
Sprinter vs marathoner
Same for project
Speed of development is a really important factor.
But when talking about speed we need to talk also about “distance”.
We know that in running, a sprinter is faster then a marathoner and will win almost all 100m dashes.
But if he or she would compete with a marathoner on 42km, then the marathoner would have the edge.
The same goes with projects.
Duration is crucial
Short project – no tests?
The longer- the more validation
The duration of the project is crucial, if the project is small and will take really short amount of time, sometimes tests might be unnecessary.
The longer the project takes, the more crucial is it’s constant validation.
Prototypes and MVPs not in the same bucket
Validate theory
Learn
Quality does not matter
Crashes, pink rectangles
Throwaway
Sometimes MVPs - bad
So when talking about prototypes and minimal viable products, I wouldn’t put them in the same basket.
Prototypes should be used to validate a theory and learn from the outcome, the quality of prototypes usually does not matter.
You do not care if it crashes or instead of images you have pink rectangles.
Prototype should be a throwaway code.
Sometimes people are reusing that code in production because “it works”, but that’s entirely different story.
Production ready
Limited scope
Quick customer value
MVPs are production ready applications that have as limited scope as possible, the reason behind it, is to deliver as quickly as possible a product to the customer, that he or she will find useful.
Volatile and chaotic sector
Industry where only constant is change
Ways of handling chaos
The more spec change -> the more code changes
Tests are not a problem, they sre a solution
Tests – coherent and predictable state
The more changes – the more errors
IT is one of the most volatile and chaotic sector .
We are in an industry where the only constant is the change! That’s why we are always looking for ways to adjust to handle all this chaos.
TODO: Saying that we shouldn’t write test because requirements changes so often is like saying …
The more changes in the specs, the more changes in the code. Tests will keep your code in coherent and predictable state.
Remember, the more code you change, the bigger chance to make an error.
Test because writing software is hard
The bigger -> the more complex -> the more things go wrong
Window metaphore
The reason why we test software is because writing software is hard! The bigger it is, the more complex it is, the more things could go wrong.
Window
Two approaches
Test verify only codes behavior
Test used as specification and documentation
Check behavior and tell dev hot to use them
Documentation gets outdated, tests not
Test are consumer of code
Hard to write test = hard to use by devs
More love for the code
You can approach writing tests in two ways:
- you can think that test should only verify code’s behavior
or
- think of tests as documentation and specification of your software - not only it will let you know when the behavior changed, additionally it can tell other developers how they should use it
It’s really useful approach, as documentation can get outdated if people forget to change it, test on the other hand will fail if they won't be updated
So keep in mind that tests are yet another consumers of your code, the same as any other developer you work with. If it’s hard to write a test for code, most likely it will be hard for others to use it as well.
Difficulty in writing test might mean that the code could use some more love
Messy code- urge to rewrite
Takes months
Behavior will be different
It’s not refactoring
Refactoring is also wrong behavior
Missing tests = too coupled architecture
Try something else
Extract piece -> test it -> refactor
Whenever we take over a messy code base, there is an urge to rewrite everything from scratch. This approach can take months. If there were no tests before, it is highly possible that the product will behave differently. People sometimes call it refactoring, but there is no such a thing as refactoring without tests!
Refactoring is a change of the code without changing the behavior - even if it was wrong!
The reason for missing tests usually is that the architecture was not decoupled enough.
Instead of dumping everything and starting anew try something different.
You should try to extract the smallest part of the code that could be extracted, write tests for it – and then refactor.
Fast or wont be run
Avoid heavy frameworks
Mock everything IO
< 50ms
They should be fast, otherwise people won’t even run them.
Try to avoid frameworks that do magic behind scenes, but at the same time increase time for tests to run.
Try mock everything related to IO, accessing disk or network will always take long time.
For me, unit test should take less then 50ms to run.
Random fails = frustration and doubt
No it testing
They should be reliable.
If there are tests that randomly fail, try to find and fix them, otherwise they will bring plenty of frustration and doubt in your test suite.
This is another reason why you should not use IO in your Unit Test, as there is too much random behavior to it.
Simple at a glance
1 min of reading – too much
They should be simple to understand with a single glance.
If you need to read them for 1 minute to understand what they are doing, then something went wrong.
Simple but long
Cut them
Use assertion helpers
There are tests that look simple enough but still are doing too much stuff.
(Animation)
You can always cut them into smaller tests, use helper assertions or use plain equals from those objects, to check if your code is correct.
2011 Bill Wake Arrange Act Assert
Don’t mix
Separate
To create simple test, in 2011 Bill Wake suggested triple A approach: Arrange Act Assert.
Keep your test code confindent to those 3 blocks. Do not mix them together.
If you see need for intwining Act and Assert, that could mean that you can do it in separate test.
Similar test with one difference
Easier to find issue
Compare with the test above
If you are writing more complex tests, make sure that there is only one difference between them.
If one test is failing, it should be enough to compare it with the one above to know which part makes it to fail.
Troublesome setup
Simplify
Sometimes you could have more complex setup of the test, for example like here with mockito.
Try to simplify it.
Declarative code is usually easier to read
One thing that we tend to use is something that I call an Arrange Builder.
If you are fan of more declarative code, you can put all the messy setup code into such a method
Its reusable
After that you can omit all the messy code and have clear, reusable test setup.
How to start?
How to end!
Another thing is that tests should be easy to write.
But when you start writing your test, thinking what variable you need and what should be order of acting, you can forget what you wanted to assert.
That’s why you can start from the end!
When asking this question, the phrase “code coverage” seems to obvious.
Mention jacoco
So how many tests should we write?
A colleague of mine used to say to “write tests until you’re confident about your code”.
Unfortunately I am not as smart as he is so I need to have another way to be confident about code I write.
So can you guess the coverage for this test with Android Studio? 100%.
But now let’s remove assertion completely. Guess shat is the coverage? 100%!
As you can see that usually way of measuring how much code is tested has big gaps.
We can possibly agree that too little tests is not a good thing.
BUt is it ok to have too many tests?
I do not mean now having a tests for a single piece of code. I mean accidentally writing a new tests, without knowing that this kind of test already exists.
Unit tests are additional code base to maintain. Making sure that it is as big as needed, not bigger, is also important for entire code base maintainability.
In the project we had a leftover model object.
I wanted to cleaned it up because of multiple reasons: it was mutable, didn’t have any tests, didn’t override stuff like toString, equals, hashCode.
Basically it was a mess.
Now in project we create our immutable model classes with AutoValue, really usefull extension that during compile time generates all the boilerplate code that models should have. If anyone used kotlin, it’s similar to data class.
After I was done with testing and writing the basic functionality, I wanted to add parcering.
Now I will tell you what would have happened if I would have written the code and then the tests in the normal way:
I would have copied the two methods and the Creator code to the autovalue abstract class.
Next I would have written tests to check if the parceling works. And it would work.
The last thing that I would do is give myself a pad on the back for the job well done.
The issue is that I wouldn’t deserve that pat on the back.
What accutally happened, is that I’ve added empty implementation for Parcalable.
Next I’ve written the parceling tests, with expectation that the tests will fail, right?
To my surprise, they’ve passed successfully.
I did what any Android developer would do, synced gradle and cleaned cuple of times, still with the same result.
In the end I;ve learned that AutoValue is so nice (and powerfull) that it also gererated all the code I needed.
Thanks to it I didn’t need to write any more code and from 90 something lines of code I’ve came to 9 (counting empty lines)
All those issues I’ve solved using Test Driven Development.
In the beginning you might think that you slow down when using TDD, but after short time, writhing tests first will make you even faster.
You will need to write less code, because you will be able to refactor without worrying about breaking things.
You won’t need to think if you need more tests, because there is no code without tests.
And not longer you will say: “The story is almost done, but I need to write tests” because you will have all the needed test even before you have the code!