Writing useful automated tests for
the Single Page Applications you
build
Andrei Sebastian Cîmpean, 2017
@Andrei_Cimpean
A talk about
things I do to
improve
feedback
Big JS Frameworks ~2017
➔ Work with a CLI
➔ Have a preferred testing
solution
➔ Maximize render performance
➔ Minimize boot time
➔ Components and
unidirectional data flow
➔ 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
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
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
Every programmer knows
they should write tests for
their code. Few do.
Noel Rappin Journal 2
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
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
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)
);
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.
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());
➔Behavior verification tests with minimal
collaborators can effectively verify
interactions without sacrificing
maintainability.
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
The canonical
testing tools:
test doubles,
unit and
acceptance
tests
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
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
Acceptance tests
➔ A test conducted to determine
if the requirements of a
specification or contract are
met
from http://gregbabiars.com/acceptance-testing-a-react-app-using-react-test-utils-pretender-and-rxjs/
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
Unit tests
➔ Low-level, focusing on a small
part of the software system
From http://learnrubythehardway.org/book/ex47.html
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
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.
Enter
components
Components and unidirectional dataflow
➔ Data down - Actions up
➔ Presentational – Container
– Business vs. Toolkit
➔ Clear state management
Components make it obvious
what’s public and what’s private
➔ Props/Attributes are public
➔ Everything else is private
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.
“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
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
Integration tests are cheap today
➔ Modern JS frameworks
– Very fast rendering
– Fast boot
– “start fast, remain fast”
➔ Powerful machines
From https://www.mutuallyhuman.com/blog/2016/01/22/component-integration-testing-in-ember
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.
Templates change a lot
➔ Design changes
➔ Business
➔ Mistake
➔ Bug fixes
This will crash
even though the
logic didn’t.
Do
Avoid matching for class names in
integration tests.
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
Template file
Test file
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
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
Do
Write integration tests that the
component is rendered correctly in all
the contexts.
Do
Write integration tests that all actions
are emitted correctly.
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 …
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
Writing tests is slow
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.
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/
You don't have
enough tests (or
good enough tests)
if you can't
confidently change
the code.
Do
Evaluate what you and your team get
out of tests and don’t be afraid to
tweak things around.
Do
Tests are part of the system and they
must be maintained to the same
standards as any other part of the
system.
TESTS
GIVE
CONFIDENYou don't have enough tests (or good enough tests)
if you can't confidently change the code.
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
SOLID PRINCIPLES
A one-to-one
correspondence
between
production and
test code implies
extremely tight
coupling.
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
A test suite that isn’t
run regularly doesn’t
have many
opportunities to
provide positive
ROI.
Do
Write a small test, make it pass.
Do
Write in small test/code cycles
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
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?
“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”
Writing useful automated tests for the single page applications you build

Writing useful automated tests for the single page applications you build

  • 2.
    Writing useful automatedtests for the Single Page Applications you build Andrei Sebastian Cîmpean, 2017 @Andrei_Cimpean
  • 3.
    A talk about thingsI do to improve feedback
  • 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 aredirectly 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 comewith 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’talways 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
  • 8.
    Every programmer knows theyshould write tests for their code. Few do. Noel Rappin Journal 2
  • 9.
    1. Writing automatedtests 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 astyle 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 testsspecify 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 astyle 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 testswith minimal collaborators can effectively verify interactions without sacrificing maintainability.
  • 15.
    Law of Demeter Onlytalk 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
  • 16.
    The canonical testing tools: testdoubles, unit and acceptance tests
  • 17.
    Test doubles ➔ Fake objectshave 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 toverify 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 ➔ Atest conducted to determine if the requirements of a specification or contract are met
  • 20.
  • 22.
    Acceptance tests ➔ Atest 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
  • 23.
    Unit tests ➔ Low-level,focusing on a small part of the software system
  • 24.
  • 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 area contract. APIs should be tested. Do It’s OK to write temporary tests for private methods. Implementation details is subject to change.
  • 27.
  • 28.
    Components and unidirectionaldataflow ➔ Data down - Actions up ➔ Presentational – Container – Business vs. Toolkit ➔ Clear state management
  • 29.
    Components make itobvious what’s public and what’s private ➔ Props/Attributes are public ➔ Everything else is private
  • 30.
    Unit test According tothe 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 unitand 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 ➔ Howcomponents 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 arecheap today ➔ Modern JS frameworks – Very fast rendering – Fast boot – “start fast, remain fast” ➔ Powerful machines
  • 34.
  • 35.
    Do Focus on writingintegration tests for your components. Do Unit tests (for methods) are the exception. Write them only when they add substantially to confidence.
  • 36.
    Templates change alot ➔ Design changes ➔ Business ➔ Mistake ➔ Bug fixes This will crash even though the logic didn’t.
  • 37.
    Do Avoid matching forclass names in integration tests.
  • 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
  • 39.
  • 40.
  • 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 forrapid 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 teststhat 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 worthit 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 worthit 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
  • 46.
  • 47.
    Not paid towrite 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 towrite 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 enoughtests (or good enough tests) if you can't confidently change the code.
  • 50.
    Do Evaluate what youand your team get out of tests and don’t be afraid to tweak things around.
  • 51.
    Do Tests are partof the system and they must be maintained to the same standards as any other part of the system.
  • 52.
    TESTS GIVE CONFIDENYou don't haveenough tests (or good enough tests) if you can't confidently change the code.
  • 53.
    If you havea 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
  • 54.
  • 55.
    A one-to-one correspondence between production and testcode implies extremely tight coupling.
  • 56.
    Test and production code evolvein 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 suitethat isn’t run regularly doesn’t have many opportunities to provide positive ROI.
  • 58.
    Do Write a smalltest, make it pass.
  • 59.
    Do Write in smalltest/code cycles
  • 60.
    Recap ➔ Write insmall 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 Wouldyou 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”

Editor's Notes

  • #2 Intreabă cu ridicare de mână câţi sunt developer şi câţi sunt QA
  • #16 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.
  • #54 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 )
  • #56 If the structure of the tests follows the structure of the production code, then the tests are inextricably coupled to the production code
  • #57 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