[Tech talk] testing for the skeptical developer (tdd, bdd & other)
-
Upload
andrei-sebastian-cimpean -
Category
Software
-
view
207 -
download
1
Transcript of [Tech talk] testing for the skeptical developer (tdd, bdd & other)
Testing for the skeptical developerAndrei Sebastian Cîmpean, 2016
Acceptance testing
performed to verify that the product is acceptable to the customer and if it's fulfilling the specified requirements
Unit testing
it tests a unit of the program as a whole
Common excuses
➔ It’s slowing me down...
➔ It wasn’t done from the start of the project...
➔ The client doesn’t pay for it…
➔ It’s not worth it / I’m testing my code before push
➔ We have QA
➔ IT’S HARD
➔ I’M AFRAID
Unit test ➔ What is a unit test➔ Types of unit tests
Unit tests
➔ Low-level, focusing on a small part of the software system
➔ Significantly faster than other kinds of tests
➔ Written by the programmers that implement the features
➔ Can be run very frequently when programming
Unit testAccording to the definition on wikipedia a unit can be an individual method, but it can also be something much larger that likely includes many collaborating classes.
Unit testing is an umbrella name for testing at a certain level of abstraction.
From http://learnrubythehardway.org/book/ex47.html
A test suite that isn’t run regularly doesn’t have many opportunities to provide positive ROI.
Test doubles➔ Dummy➔ Fake➔ Stubs➔ Spies➔ Mocks
“It’s hard”“It’s slowing me down”
Dummy objects are passed around but never actually used.
Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production.
Stubs provide canned answers to calls made during the test. Stubs may also record information about calls.
Mocks are objects pre-programmed with expectations which form a specification of the calls they are expected to receive.
Mocks (and mock expectations) are fake methods (like spies) with pre-programmed behavior (like stubs) as well as pre-programmed expectations.
The rule of thumb is: if you wouldn’t add an assertion for some specific call, don’t mock it.
Types of tests ➔ That check state➔ That check
behaviour
State verificationDescribes a style of testing where you exercise one or many methods of an object and then assert the expected state of the object (and/or collaborators).
Movie movie = a.movie.build();
Rental rental = a.rental.w(movie).build();
Store store = a.store.w(movie).build();
rental.start(store);
assertTrue(rental.isStarted());
assertEquals(0, store.getAvailability(movie));
State verification tests specify the least possible implementation detail, thus they will continue to pass even if the internals of the methods being tested are changed.
Behaviour verificationDescribes a style of testing where the execution of a method is expected to generate specific interactions between objects.
Movie movie = a.movie.build();
Rental rental = a.rental.w(movie).build();
Store store = mock(Store.class);
when(store.getAvailability(movie))
.thenReturn(1);rental.start(store);
assertTrue(rental.isStarted());
➔ Implementation changes that preserve macro behavior can lead to difficult to understand and largely valueless test failures.
➔ Behavior verification tests with minimal collaborators can effectively verify interactions without sacrificing maintainability.
➔ A team that relies on Behavior verification will likely produce a codebase with few Law of Demeter violations and a focus on Tell, Don’t Ask.
Law of Demeter Only talk to your immediate friends
Tell, Don’t AskRather than asking an object for data and acting on that data, we should instead tell an object what to do
Types of tests ➔ Solitary➔ Sociable
Solitary Unit Test Test methods of a class, or
functions of a namespace, to validate that they behave as expected.
Pure functionsA function where the return value is only determined by its input values, without observable side effects.
ConstraintsNever cross boundaries➔ Boundary crossing increases the fragility and execution time of your test
suite➔ Examples:
◆ interacting with a database◆ interacting with the filesystem◆ interacting with time
ConstraintsThe Class Under Test should be the only concrete class found in a test➔ Use a double for dependencies.➔ Collaborators that return objects should return doubles.➔ No statics.➔ Doesn’t apply to primitives or classes with literals
Sociable Unit TestUnit tests that are not solitary, are sociable ...
There are two primary reasons for writing Solitary Unit Tests:
Sociable Unit Tests can be slow and nondeterministic
Sociable Unit Tests are more susceptible to cascading failures
From http://slidedeck.io/josephdpurcell/principles-of-solitary-unit-testing
$stub = $this->getMockBuilder(‘User’)->getMock();$stub->method(‘getFirstName’)->willReturn(‘First’);$stub->method(‘getLastName’)->willReturn(‘Last’);$cut = new ClassUnderTest($stub);
$fullName = $cut->getFullName();
$this->assertEquals(‘First Last’, $fullName’);
Solitary unit test using stubs
Test DrivenDevelopment
➔ Breaking a problem up into smaller pieces
➔ Defining the “simplest thing that could possibly work”
➔ Improved design
It’s not necessary to write unit tests to do TDD.
It’s not necessary to do TDD to write unit tests.
What is TDD according to WikipediaA development process that relies on the repetition of a very short development cycle:
1.The developer writes an (initially failing) automated test case that defines a desired improvement or new function
2.The developer produces the minimum amount of code to pass that test
3.The developer refactors the new code to acceptable standards
Write failing test
from https://www.codementor.io/nodejs/tutorial/unit-testing-nodejs-tdd-mocha-sinon
Write failing test
from https://www.codementor.io/nodejs/tutorial/unit-testing-nodejs-tdd-mocha-sinon
Write just enough code to pass the test
from https://www.codementor.io/nodejs/tutorial/unit-testing-nodejs-tdd-mocha-sinon
Write just enough code to pass the test
from https://www.codementor.io/nodejs/tutorial/unit-testing-nodejs-tdd-mocha-sinon
Write failing test
from https://www.codementor.io/nodejs/tutorial/unit-testing-nodejs-tdd-mocha-sinon
Write failing test
from https://www.codementor.io/nodejs/tutorial/unit-testing-nodejs-tdd-mocha-sinon
Write just enough code to pass the test
from https://www.codementor.io/nodejs/tutorial/unit-testing-nodejs-tdd-mocha-sinon
“As the tests get more
specific, the code gets
more generic”
As a test suite grows, it becomes a more detailed specification of behavior.
Developers should meet this increase in specification by increasing the generality of their code.
Robert C. Martin
Specific tests lead to generic production codeEvery new test case makes the test suite more constrained and more specific.
Just adding if statements ...
Specific tests lead to generic production codeEvery new test case makes the test suite more constrained and more specific.
Just adding if statements will not work in the long run. Developers end up making tests pass by innovating general algorithms.
To make the new test case pass, the programmer strives to make the production code more general, not more specific.
➔ Programmers deserve to feel confident that their code works and TDD is one (not the only) way to reach that
➔ Tackle problems piecemeal, figuring out specific cases without having to solve the general case all at once
➔ Leads to more modularized, flexible, and extensible code
➔ Existing tests provide a safety net
➔ Confusion over the definition of TDD and unit testing
➔ Many people make bad trade-offs, especially with heavy mocking that end up driving architecture
➔ “HARDER”
➔ TDD gives a mechanism to quickly get feedback on an idea and example usage of an API while implementing it one step at a time.
➔ Can be very appealing to developers who can find a large problem overwhelming
➔ Programmers are able to focus exclusively on the task at hand: make the individual test pass
➔ TDD is may leave you uncertain what exactly to do with the Refactor phase.
Behaviour Driven
Development
➔ An executable specification that fails because the feature doesn't exist
➔ Writing the simplest code that can make the spec pass.
➔ Repeat this until a release candidate is ready to ship.
➔ Easier to read TDD?
Behavior-driven development is facilitated by a simple domain-specific language (DSL) using natural language constructs (e.g., English-like sentences) that can express the behavior and the expected outcomes.
BDD suggests that unit test names be whole sentences starting with a conditional verb ("should" in English for example).
BDD suggests that unit tests should be written in order of business value.
from https://en.wikipedia.org/wiki/Behavior-driven_development#Behavioural_specifications
from https://thomassundberg.wordpress.com/2014/05/29/cucumber-jvm-hello-world/
file: Belly.feature file: StepDefinitions.java
So if we write tests in a BDD style ...
1. Customers can read them2. Customers can give us
early feedback3. Customers can write some
of them
So if we write tests in a BDD style ...
1. Customers can read them2. Customers can give us
early feedback3. Customers can write some
of themThis never* happens
Since BDD is basically is feature driven approach to TDD, there is value in doing it if you want to.
NodeJS acceptance test
from http://gregbabiars.com/acceptance-testing-a-react-app-using-react-test-utils-pretender-and-rxjs/
To explore for better tests➔ Stick to one assertion per test (maintainability)➔ The assertion should be the last piece of code found within a test➔ Negative testing➔ Global day of coderetreat
➔ Test only public methods *
Test only public methodsPublic methods are a contract. They do not change.
Private methods are the implementation details. They are subject to change.
It’s OK to write temporary tests for private methods while you’re exploring the implementation but you should remove them before commit.
Common excuses
again...
➔ It’s slowing me down...
➔ It wasn’t done from the start of the project...
➔ The client doesn’t pay for it…
➔ It’s not worth it / I’m testing my code before push
➔ We have QA
➔ IT’S HARD
➔ I’M AFRAID
Writing tests is slowing me down...
Strategies for working with legacy codeAll new code is tested.
Black Box untested code. Don’t work directly with untested code but use specialized objects that know how to do that
Read: http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052
Not paid to write testsYou’re not paid to write tests, you just write enough to be confident that your code works as required.
Refactoring without having unit tests is not refactoring, it’s moving code around.
You don't have enough tests (or good enough tests) if you can't confidently change the code. In the other extreme, not changing code because you
Things to consider when setting up an environment
Frequency - how rapidly do we want our feedback ?
Fidelity - how accurate do we want the red/green signal to be?
Overhead - how much are we prepared to pay?
Lifespan - how long is this software going to be around?
Other benefits to doing testing➔ Easier ramp-up for newcomers on the project or feature
➔ Code reuse is encouraged
➔ Developer uses the code as a client would
“Mocks Aren’t Stubs”, Martin Fowler
“UnitTest”, Martin Fowler
“Is TDD dead?”, Kent Beck, David Heinemeier Hansson, Martin Fowler
"Working Effectively With Unit Tests", Jay Fields
“Test Driven Development”
Q & AWould you rather throw away the code and keep the tests or vice-versa?
How do you know if your code-base is healthy?