TDD isn't news, and most everyone agrees that writing tests (before, during or after implementation) is valuable. Hardly anyone agrees on what good, clean tests look like, though; if your tests are a live specification of your codebase, don't they deserve the same care and attention as your production code?
This talk focuses on how to keep your tests readable, simple and maintainable. Specifically we'll discuss how the "given-when-then" pattern affects the way you factor your code, and showcase the remarkable differences between a sloppy specification and a well-factored one.
Moderate experience with testing is expected; knowledge Scala might be helpful but is not required.
10. describe("Color Code Converter", function() {
describe("RGB to Hex conversion", function() {
it("converts the basic colors", function() {
….
});
});
@Test
void succeedingTest() {
...
}
"AppPublishRequestService" should {
"return publish request if request matcher publish request id" in new ctx {
...
}
Description
JavaScript - Mocha
Java - JUnit
Scala - Specs2
11. ● Use Neutral Language - English
Description
it("Given animal with black color will fail validation", function() {
…
});
“Given animal with black color will fail validation” in new ctx { ... }
12. ● Java Hint: camel case is less readable
Description
void givenAnimalWithBlackColorWillFailValidation()
void Given_animal_with_black_color_will_fail_validation()
13. ● Notice: It Does NOT compile !!!
Description
“Given animal with black color will fail validation” in new ctx {
validate(animal.copy(color = “Black”)) must beValid
}
15. ● Describe What and not How
Given
cachePolicyAnnotationExtractor.cachePolicyFrom(method) returns None
def givenExtractorWithoutAnnotationFor(method: Method) { }
16. ● Reduce number of moving parts
Given
givenDaoWith(Seq(user1, user2, user3))
givenDaoWith(user1, user2, user3) // varargs
givenDaoWith(Some(name), description)
givenDaoWith(name, description) // useless wrappers
17. ● Don’t use concrete values if you don’t have to
Given
def givenUserWith("some@one.com", forUserId = 500) { ... }
def givenUserWith(email, forUserId = userId) { ... }
18. ● Introduce only needed data
Given
def givenSiteWith(url, name, andDescription = description, forSiteId = siteId)
siteChecker.siteFor(siteId).name must_=== name
19. Given
def givenPublishDaoWith(appPublishStatus.copy(landingPageUrl = None), appInstanceId)
def givenLandingPageServiceWith(landingPageUrl, forAppInstanceId = appInstanceId)
def givenDaoContainsPublishStatusWithoutLandingPageUrlFor(appInstanceId)
def givenLandingPageServiceWith(landingPageUrl, forAppInstanceId = appInstanceId)
● One api does not serve all
24. ● Do NOT assert against anything you didn’t
define
Then
WixBiRecordingStudio.record {
logEvent()
} andThenPlay { recording =>
recording.play must contain(exactly(
aBiEventWithEntries("bar" -> "baz", "method" -> "foo")))
}
25. ● Describe what you are matching and not how
Then
person.age must be_>=(13)
person.age must be_<=(18)
person.location must_=== "USA"
person must beAmericanTeenager
30. "given no annotation return default cache headers" in new ctx {
cachePolicyAnnotationExtractor.cachePolicyFrom(method) returns None
cachePolicyManager.cachePolicyHeadersFor(method) must
containTheSameElementsAs(Seq("Cache-Control" -> "no-cache",
"Pragma" -> "no-cache"))
}
Demo - Before
31. "given no annotation return default cache headers" in new ctx {
givenExtractorWithoutCachePolicyFor(method)
cachePolicyManager.cachePolicyHeadersFor(method) must
containTheSameElementsAs(Seq("Cache-Control" -> "no-cache",
"Pragma" -> "no-cache"))
}
Demo - After 1
32. "given no annotation return default cache headers" in new ctx {
givenExtractorWithoutCachePolicyFor(method)
cachePolicyManager.cachePolicyHeadersFor(method) must
haveNoCacheControlHeaders
}
Demo - After 2
34. ● Test should stand on it’s own
● Reduce number of moving parts
● Don’t use anything you didn’t define
● Don’t define anything you won’t use
● Describe What and not How
● Use Neutral Language - English
Conclusion
35. This is where you are going to present your final words.
This slide is not meant to have a lot of text.Thank You!
Any Questions?
Noam Almog
noam-almog github.com/noam-
almog
@NoamSonnoama@wix.com
Editor's Notes
having dirty tests is equivalent to, if not worse than, having no tests.
I’ll start with a quote from uncle bob which shows the importance of testing, cleaning code is important but cleaning the tests is even more.
Usually we write a failing test, write the code, pass the test then refactor the code to be clean and nice and then we move on to the next feature. But what about the test ?
We need to spend some time cleaning it up and then continue to the next feature.
In this talk I will demonstrate some techniques on how to do that.
Some of the slides are in scala but the principles are the same for all languages.
- Readability, some people claim that it's the documentation for the system.
what is a single test?, how to properly scope it to allow an outsider understand
can the test standout on it's own ?
- Maintainable, if for each change you need to change the test it usually implies that you have a badly written test.
If you didn't change the behavior you should touch the test.
Informative, if the test fails, can someone understand what went wrong ?
- Trustworthy - when refactoring your tests make sure you don't break them
don't be over obsessive about testing everything, test what needs to be tested.
What should you ask yourself when you look at a test ?
- can your test standalone by itself ?
- did I use data that i didn't define in this scope ?
- Am I not hiding too much
- Talk about what you test and not How you test it.
- is the test expressing what I want to do ?
---- The memento method, you forget what you wrote and you look at it with new eyes
A well written structure is like a map to understanding the code.
Arrange, Act, Assert
So let's run through this one more time
Describe action and result in plain words
Describe action and result in plain words
Describe action and result in plain words
Pack to method and extract driver
Understand the contract
When driver is created it better describes the contract
Randomize to detach test from data, mention property based testing
Pack to method and extract driver
Understand the contract
When driver is created it better describes the contract
Randomize to detach test from data, mention property based testing
Don’t use variables that you won’t need later on
Don’t use variables that you won’t need later on
Don’t use the same api for all object, decompose object and
Don’t hide what you are testing !
Don’t use variables that you won’t need later on
Don’t use variables that you won’t need later on
Don’t use variables that you won’t need later on
Don't over abstract the test !!!
Before - reset mocks
Before All - create the env
Do you have to be a technical person to read this ?