How to approach testing if you are building a modern single page application. I try to emphasize that integration testing is the way to go and that developers should consider the tests as part of the system and spend time to maintain them.
4. Big JS Frameworks ~2017
➔ Work with a CLI
➔ Have a preferred testing
solution
➔ Maximize render performance
➔ Minimize boot time
➔ Components and
unidirectional data flow
5. ➔ Things are directly coupled to
a multitude of things
➔ Any change to one member,
regardless of how trivial, will
likely cause many others to
require change
➔ Fragile codebase
Where gut feeling works
✔ Shitty code
6. Benefits that come with tests
➔Easier ramp-up for newcomers on the
project or feature
➔Code reuse is encouraged
➔Developer uses the code as a client would
7. Automated testing
➔ Isn’t always simple
➔ It's not always clear how to
break a problem into small,
testable units
➔ Code that is more dependent
on interaction than logic can
be hard to write tests for
➔ Testing is hard when tests are
slow
9. 1. Writing automated tests
2. Writing automated tests for the ~2017 Single Page Apps
3. Writing useful automated tests for the ~2017 Single Page Apps
Writing useful automated tests for the Single Page Applications
you build
10. Types of tests ➔ That check state
➔ That check behaviorThere are other ways of separating tests
by type, but we’ll focus only on this one
11. State verification
Describes 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)
);
12. 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.
13. Behavior verification
Describes 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());
14. ➔Behavior verification tests with minimal
collaborators can effectively verify
interactions without sacrificing
maintainability.
15. Law of Demeter
Only talk to your immediate friends
Tell, Don’t Ask
Rather than asking an object for data
and acting on that data, we should
instead tell an object what to do
17. Test doubles
➔
Fake objects have incomplete/wrong working implementations
➔
Stubs provide canned answers to calls made during the test
➔
Spies are stubs that also record some information based on how they were
called
➔
Mocks are pre-programmed with expectations which form a specification of
the calls they are expected to receive.
https://martinfowler.com/bliki/TestDouble.html
18. 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
19. Acceptance tests
➔ A test conducted to determine
if the requirements of a
specification or contract are
met
22. Acceptance tests
➔ A test conducted to determine
if the requirements of a
specification or contract are
met
➔ Usually very slow to run
➔ There’s usually a strategy
put in place for when to
run them
25. Unit tests
➔ Low-level, focusing on a small
part of the software system
➔ Significantly faster than
other kinds of tests
➔ Can be run very frequently
when programming
26. Do
Public methods are a contract.
APIs should be tested.
Do
It’s OK to write temporary tests for
private methods. Implementation
details is subject to change.
28. Components and unidirectional dataflow
➔ Data down - Actions up
➔ Presentational – Container
– Business vs. Toolkit
➔ Clear state management
29. Components make it obvious
what’s public and what’s private
➔ Props/Attributes are public
➔ Everything else is private
30. Unit test
According 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.
31. “Integration testing”
➔
between unit and acceptance testing
➔
test the rendered state of a component (or nested group of components) in
isolation from the rest of the app
Enter - Integration Testing
32. Integration tests
➔ How components behave in regard to
– DOM Changes
– Events
– Async
➔ Can be run very frequently when
programming
➔ Testing at the right level of
abstraction for SPAs
33. Integration tests are cheap today
➔ Modern JS frameworks
– Very fast rendering
– Fast boot
– “start fast, remain fast”
➔ Powerful machines
35. Do
Focus on writing integration tests for
your components.
Do
Unit tests (for methods) are the
exception. Write them only when they
add substantially to confidence.
36. Templates change a lot
➔ Design changes
➔ Business
➔ Mistake
➔ Bug fixes
This will crash
even though the
logic didn’t.
38. Improve your setup!
➔ Automatically cleans the HTML of test
tags
➔ Ideally not only used in templates
– If you need to check private
attributes
➔ ember-test-selectors
– Or alternative for your framework
of choice
41. What about methods?
➔ Stop writing method tests that focus
on parameters
– If you can, and want to, use
Typescript or Flow
– Alternative, use inline assertions
➔ Stop writing method tests that focus
on parameters
– If you can, and want to, use
Typescript or Flow
– Alternative, use inline assertions
Ember.assert('Test for truthiness', obj);
Ember.assert('Fail unconditionally');
* In a production build, this method is defined as an empty
function (NOP). Uses of this method in Ember itself are
stripped from the ember.prod.js build
42. Inline asserts for rapid feedback
➔ Ember.assert
➔ Use it in appropriate places
– Lifecycle hooks when new
required properties are set
– Critical / fragile places
➔ “throw new Error”
– Clean up when building for
production
43. Do
Write integration tests that the
component is rendered correctly in all
the contexts.
Do
Write integration tests that all actions
are emitted correctly.
44. Things
developers
say
It’s not worth it and I’m testing my
code before I push to ...
It’s slowing me down...
It wasn’t done from the start of the
project...
The client doesn’t pay for it…
We have QA …
45. Things
developers
say
It’s not worth it and I’m
testing my code before I push
to ...
It’s slowing me down...
It wasn’t done from the start of the
project...
The client doesn’t pay for it…
We have QA ...
IT’S HARD
NOT SOMETHING I USUALLY
DO
Gut feeling and bias
47. Not paid to write tests
You’re not paid to write tests, you just
write enough to be confident that
your code works as required.
Refactoring without having tests
is not refactoring, it’s moving code
around.
48. Not paid to write tests
“If I don’t typically make a kind of mistake (like setting the
wrong variables in a constructor), I don’t test for it. I do
tend to make sense of test errors, so I’m extra careful
when I have logic with complicated conditionals.
When coding on a team, I modify my strategy to carefully
test code that we, collectively, tend to get wrong.”
https://istacee.wordpress.com/2013/09/18/kent-beck-i-get-paid-for-code-that-works-not-for-tests/
49. You don't have
enough tests (or
good enough tests)
if you can't
confidently change
the code.
50. Do
Evaluate what you and your team get
out of tests and don’t be afraid to
tweak things around.
51. Do
Tests are part of the system and they
must be maintained to the same
standards as any other part of the
system.
53. If you have a lot of tests, a single
change to the production code can
cause hundreds of tests to require
corresponding changes.
For example, if you add an argument
to a method, every test that calls that
method must be changed to add the
new argument.
Fragile Test
Problem
56. Test and
production code
evolve in opposite
directions.
As the tests get
more specific, the
production code
gets more generic.
http://blog.cleancoder.com/uncle-bob/2017/03/03/TDD-Harms-Architecture.html?__s=5sgof5whdhfhhnfby3e4
57. A test suite that isn’t
run regularly doesn’t
have many
opportunities to
provide positive
ROI.
60. Recap
➔ Write in small cycles
➔ Write integration tests for components
➔ Minimize DOM dependency in tests
➔ Only test what makes sense
➔ Refactor tests
➔ Don’t pretend you can refactor without
tests
61. Q & A
Would you rather throw away the code and keep the tests or vice-versa?
How do you know if your code-base is healthy?
62. “Mocks Aren’t Stubs”, Martin Fowler
“UnitTest”, Martin Fowler
“Is TDD dead?”, Kent Beck, David Heinemeier Hansson, Martin Fowler
“TDD Harms Architecture”, Rober C. Martin
"Working Effectively With Unit Tests", Jay Fields
“Test Driven Development”
Notas do Editor
Intreabă cu ridicare de mână câţi sunt developer şi câţi sunt QA
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.
Fragile Test Problem:
As the number of tests grows, a single change to the production code can cause hundreds of tests to require corresponding changes (add an argument to a method, every test must be changed )
If the structure of the tests follows the structure of the production code, then the tests are inextricably coupled to the production code
These two streams of code evolve in opposite directions. Programmers refactor tests to become more and more concrete and specific. They refactor the production code to become more and more abstract and general.
highly specific code cannot have a one-to-one correspondence with highly generic code