SlideShare une entreprise Scribd logo
1  sur  46
Télécharger pour lire hors ligne
Writing testable code
Lessons learned from writing tests for The Incredible Circus (Web)
How do you feel...
When you are in a huge project and need to change a class that
is part of the core of the application?
How I felt...
Testing in Circus
Testing in Circus
• Manual tests
• Each developer had its own testing approach
• New elements sometimes broke old elements’ behavior
• Inconsistent behavior across platforms
Testing in Circus-HTML5
• Should we use a framework?
• Write them first, while or after writing the code under testing?
• Are there rules, best-practices?
Traditional testing approach
Manual tests
through UI
Automation
suites
Unit
tests
Source: The Agile Testing Pyramid
Agile testing approach
Unit tests
Acceptance
tests
UI
Tests
Exploratory
tests
Source: The Agile Testing Pyramid
Writing unit tests
• Test only one thing
• Only one assert; no and nor or in its name
• Enforce isolation
• 3As: arrange, act, assert
Source: Understanding Test Driven Development
Writing unit tests
How would you write the Burn
hits the player test using 3A
(arrange, act, assert)?
PlayerEntity.prototype.burn =
function (damage, ignoreDiversion) {
this.hit(damage, ignoreDiversion);
this.isBurning = true;
this._burningTimer.start(
PlayerEntity.BURNING_TIMEOUT
);
};
Writing unit tests
PlayerEntity.prototype.burn =
function (damage, ignoreDiversion) {
this.hit(damage, ignoreDiversion);
this.isBurning = true;
this._burningTimer.start(
PlayerEntity.BURNING_TIMEOUT
);
};
function burnHitsThePlayer() {
// arrange
var player = new PlayerEntity();
this.stub(player, “hit”);
// act
player.burn(1);
// assert
assert.calledOnce(player.hit);
}
Writing unit tests
OK, now write the Program
sums two numbers correctly
test
function printSum(num1, num2) {
console.log(num1 + num2);
}
There is no secret to writing tests, there are only secrets to
writing testable code!
Misko Hevery in Mr. Testable vs. Mr. Untestable
Writing testable code
Usual flaws that hardens testing
1. Do work in constructors
2. Dig into collaborators
3. Having global state and singletons
4. Having classes that do too much
Source: Writing testable code
Do work in constructors
How would you create
the GameWorld passes
the correct time value
to the physics
simulation engine test?
var GameWorld = function (entities) {
...
this._gravity = new Vector2D(0.0,-21.2);
this._world = new World(this._gravity);
...
}
GameWorld.prototype.step =
function (delta) {
...
this._world.step(delta / 1000);
...
}
Do work in constructors
var GameWorld = function (entities) {
...
this._gravity = new Vector2D(0.0,-21.2);
this._world = new World(this._gravity);
...
}
GameWorld.prototype.step =
function (delta) {
...
this._world.step(delta / 1000);
...
}
function gameWorldPassesTimeCorrectly() {
// arrange
var entities = [];
var gameWorld = new GameWorld(entities);
// act
gameWorld.step(1000);
// assert
... ?
}
Do work in constructors
• Have a test specific World
that you could mock?
• Have an accessor to the time
variable in World?
• What if it doesn’t store the
time?
There isn’t a clean
way!
function gameWorldPassesTimeCorrectly() {
// arrange
var entities = [];
var gameWorld = new GameWorld(entities);
// act
gameWorld.step(1000);
// assert
... ?
}
Do work in constructors
GameWorld’s constructor has two
responsibilities:
1. Initializing GameWorld
2. Initializing and wiring its dependencies
Solution:
• Split the responsibilities
• Constructor initializes GameWorld
• Factories/builders initializes dependencies
• Inject the dependencies (DI)
var GameWorld = function (entities) {
...
this._finished = false;
this._entities = [];
this._player = null;
this._stage = null;
this._gravity = new Vector2D(0.0, -21.2);
this._world = new World(this._gravity);
...
}
Do work in constructors
Before After
var GameWorld = function (entities) {
...
this._finished = false;
this._entities = [];
this._player = null;
this._stage = null;
this._gravity = new Vector2D(0.0, -21.2);
this._world = new World(this._gravity);
...
}
var GameWorld =
function (entities, gravity, world) {
...
this._finished = false;
this._entities = [];
this._player = null;
this._stage = null;
this._gravity = gravity;
this._world = world;
...
}
Do work in constructors
var GameWorld =
function (entities, gravity, world) {
...
this._gravity = gravity;
this._world = world;
...
}
GameWorld.prototype.step =
function (delta) {
...
this._world.step(delta / 1000);
...
}
function gameWorldPassesTimeCorrectly() {
// arrange
var entities = [];
var gravity = new Vector2D(0.0, -21.2);
var world = new World(gravity);
var stepSpy = this.spy(world, “step”);
var gameWorld = new GameWorld(entities,
gravity, world);
// act
gameWorld.step(1000);
// assert
assert(stepSpy.calledWith(1));
}
Do work in constructors
• Collaborators can’t be mocked
• Unit test has to replicate the work done in the constructor
• Complexity of collaborators is brought to the test
• If one collaborator accesses the network, the test must be executed
with access to the network
• Violates the Single Responsibility Principle (S in SOLID)
• Constructing the object graph is a full-fledged responsibility
Do work in constructors
Some signs of the existence of this problem
• The new keyword (except for value objects)
• Static method calls
• Conditional or loop logic
Dig into collaborators
How would you create the No
plots are generated if force is
a null vector test?
var TrajectoryPathEntity =
function (gameWorld, referenceEntity) {
Entity.call(this, gameWorld);
this.force = null;
this._gravity = gameWorld.gravity();
this.plots = [];
this._refEntity = referenceEntity;
this._entityMass = this._refEntity.mass();
};
TrajectoryPathEntity.prototype.update =
function (delta) {
this.plots = [];
// Math that computes the points in the
// trajectory and updates this.plots if
// force is valid or non-zero
};
Dig into collaborators
var TrajectoryPathEntity =
function (gameWorld, referenceEntity) {
Entity.call(this, gameWorld);
this.force = null;
this._gravity = gameWorld.gravity();
this.plots = [];
this._refEntity = referenceEntity;
this._entityMass = this._refEntity.mass();
};
TrajectoryPathEntity.prototype.update =
function (delta) {
this.plots = [];
// Math that computes the points in the
// trajectory and updates this.plots if
// force is valid or non-zero
};
function
noPlotsAreGeneratedIfForceIsANullVector () {
// arrange
var gravity = new Vector2D(0, -20);
var world = new World(gravity);
var entity = new Entity();
var trajectoryPath = new
TrajectoryPathEntity(world, entity);
trajectory.force = new Vector(0, 0);
// act
trajectory.update(33);
// assert
assert.equals(trajectory.plots.length, 0);
}
Dig into collaborators
Trajectory path needs GameWorld only to get
gravity! (also because of Entity’s restriction)
Must create a world when it only need the
gravity
var TrajectoryPathEntity =
function (gameWorld, referenceEntity) {
Entity.call(this, gameWorld);
this.force = null;
this._gravity = gameWorld.gravity();
this.plots = [];
this._refEntity = referenceEntity;
this._entityMass = this._refEntity.mass();
};
TrajectoryPathEntity.prototype.update =
function (delta) {
this.plots = [];
// Math that computes the points in the
// trajectory and updates this.plots if
// force is valid or non-zero
};
function
noPlotsAreGeneratedIfForceIsANullVector () {
// arrange
var gravity = new Vector2D(0, -20);
var world = new World(gravity);
var entity = new Entity();
var trajectoryPath = new
TrajectoryPathEntity(world, entity);
trajectory.force = new Vector(0, 0);
// act
trajectory.update(33);
// assert
assert.equals(trajectory.plots.length, 0);
}
Dig into collaborators
Complexity of World is brought to
the test
• Construction of World is made in
TrajectoryPath’s test
• Change in World -> change in
TrajectoryPath’s test
• Resources used by World must
exist in TrajectoryPath’s test
Solution:
• “Don’t look for things, ask for
things”
• Inject the dependency (DI)
function
noPlotsAreGeneratedIfForceIsANullVector () {
// arrange
var gravity = new Vector2D(0, -20);
var world = new World(gravity);
var entity = new Entity();
var trajectoryPath = new
TrajectoryPathEntity(world, entity);
trajectory.force = new Vector(0, 0);
// act
trajectory.update(33);
// assert
assert.equals(trajectory.plots.length, 0);
}
Dig into collaborators
Before After
var TrajectoryPathEntity =
function (gameWorld, referenceEntity) {
Entity.call(this, gameWorld);
this.force = null;
this._gravity = gameWorld.gravity();
this.plots = [];
this._refEntity = referenceEntity;
this._entityMass = this._refEntity.mass();
};
var TrajectoryPathEntity =
function (gravity, referenceEntity) {
Entity.call(this);
this.force = null;
this._gravity = gravity;
this.plots = [];
this._refEntity = referenceEntity;
this._entityMass = this._refEntity.mass();
};
Dig into collaborators
Before After
function
noPlotsAreGeneratedIfForceIsANullVector () {
// arrange
var gravity = new Vector2D(0, -20);
var world = new World(gravity);
var entity = new Entity();
var trajectoryPath = new
TrajectoryPathEntity(world, entity);
trajectory.force = new Vector(0, 0);
// act
trajectory.update(33);
// assert
assert.equals(trajectory.plots.length, 0);
}
function
noPlotsAreGeneratedIfForceIsANullVector () {
// arrange
var gravity = new Vector2D(0, -20);
var world = new World(gravity);
var entity = new Entity();
var trajectoryPath = new
TrajectoryPathEntity(gravity, entity);
trajectory.force = new Vector(0, 0);
// act
trajectory.update(33);
// assert
assert.equals(trajectory.plots.length, 0);
}
Dig into collaborators
• Violates the Law of Demeter
• Violates the Single Responsibility Principle
• Object becomes a service locator
• Creates a deceitful API
• You say that you need A, when actually you need B that is held by A
Dig into collaborators
Some signs of the existence of this problem
• An object named context
• More than one “.” in a call chain
• Having to create mocks that returns mocks in tests
Global state & singletons
RopeEntity.prototype.onCollision =
function (collision) {
if (this.hasPlayer())
return;
var player = collision.collidedEntity;
var pivotPoint = player.pivotPoint();
player.stand();
var bottomCenter = player.boundingRect()
.bottomCenter();
this._updateContactPoint(bottomCenter);
this.setFocus(true);
this.grabPlayer(player);
this.setState(RopeEntity.State.Bouncing);
var rect = this.boundingRect();
this.testCombo({ x: rect.center().x, y: 0 },
{ x: pivotPoint.x, y: 0 },
0, rect.width / 2);
};
Global state & singletons
This test failed randomly. Can you spot the error?
RopeEntity.prototype.onCollision =
function (collision) {
if (this.hasPlayer())
return;
var player = collision.collidedEntity;
var pivotPoint = player.pivotPoint();
player.stand();
var bottomCenter = player.boundingRect()
.bottomCenter();
this._updateContactPoint(bottomCenter);
this.setFocus(true);
this.grabPlayer(player);
this.setState(RopeEntity.State.Bouncing);
var rect = this.boundingRect();
this.testCombo({ x: rect.center().x, y: 0 },
{ x: pivotPoint.x, y: 0 },
0, rect.width / 2);
};
function playerMakes150PointsWhenCollidesAtCenter() {
// arrange
var builder = new WorldBuilder();
var rope = builder.buildRopeEntity();
var player = builder.buildPlayerEntity();
var event = { collidedEntity: player };
var bonusSpy = this.spy(
builder.getWorld().scoreBoard,
"increaseBonus“
);
var pivotPoint = {
x: this.rope.boundingRect().center().x,
y: this.rope.boundingRect().top
};
this.stub(player, "pivotPoint")
.returns(pivotPoint);
// act
rope.onCollision(this.event);
// assert
assert(this.bonusSpy.calledWith(1.5));
}
Global state & singletons
RopeEntity
PlatformEntity
GrabberEntity
-shouldAcceptBonus
+testCombo()
GrabberEntity.prototype.testCombo =
function (base, center, subDistance,
maxDistance) {
if (!GrabberEntity._shouldAcceptBonus) {
GrabberEntity._shouldAcceptBonus = true;
this._comboCounted = true;
return;
}
// do combo computation
};
Global state & singletons
RopeEntity
PlatformEntity
GrabberEntity
-shouldAcceptBonus
+testCombo()
Cause
• A global variable that isn’t visible in the
method being tested
• So I’ll have to look at all code paths and
see every possible interaction with every
global variable?
Yeap... ☹
Global state & singletons
Our solution: use a setup/
teardown method
A better solution:
• Rewrite in order to remove
the global variable
• These tests can’t be run in
parallel ☹
function
playerMakes150PointsWhenCollidesAtCenter() {
// arrange
...
// act
...
// assert
...
}
function setUp() {
GrabberEntity._shouldAcceptBonus = true;
}
function tearDown() {
GrabberEntity._shouldAcceptBonus = false;
}
Global state & singletons
• Requires setup/teardown methods to restore the state
• Forces the developer to know every possible interaction with the
global state
• Makes it impossible to run tests in parallel
• Global state is non-mockable
• Global state creates a deceitful API
• Tells that the class or method that accesses the global state has no
dependencies
• Estabilishes hidden channels between objects
Global state & singletons
What about singletons?
A singleton is global state in sheep’s clothing
It feels like a plain class, but it is the same as global state:
• Can be acessed anywhere
• Every variable it holds is no different than a static variable
Global state & singletons
Can’t I have a class that must have only a single instance?
Yes, you can, but the singleness should be controlled by the
programmer or framework.
This forces the dependencies to be explicit!
Global state & singletons
Some signs of the existence of this problem
• Static fields
• Singletons
• Tests failling randomly
• Tests failling when the order of execution changes
Class that does too much
• Similar problem of a constructor that does work
• Becomes harder to test features in isolation
• Often, methods have mixed reponsibilities
• Hard to understand and hard to hand-off
Class that does too much
Some signs of the existence of this problem
• Excessive scrolling
• When asked what it does, contains too many “and” in the
answer
• A manager might be a sign that it is a class that does more
than it should
• The God class
Summary
Constructor doing work
Why is it negative?
• Testing directly is difficult
• Breaks SRP
How to fix it?
• Breaks responsibilities
• Create factories/builders
• Inject dependencies
Dig into collaborators
Why is it negative?
• Deceipts the user of the API
• Breaks SRP and LoD
How to fix it?
• “Don’t look for things, ask for
things”
• Inject dependencies
Summary
Global state & singletons
Why is it negative?
• Deceipts user of the API
• Requires setup/teardown
methods
• Forbis tests to be run in
parallel
How to fix it?
• Inject dependencies
Class that does too much
Why is it negative?
• Hardens testing
• Breaks SRP
How to fix it?
• Break responsibilities
What I learned
• Tests gave me confidence that my code works
• Brought me tranquility when I needed to make changes
• It is often uncomfortable to write tests
What I learned
• Tests are executable documentation
If somehow all your production code got deleted, but you had a backup
of your tests, then you'd be able to recreate the production system
with a little work. (…) If, however, it was your tests that got deleted,
then you'd have no tests to keep the production code clean. The
production code would inevitably rot, slowing you down.
Test First
• There is a relation between testable code and good quality
code
Takeaways
• The Factory Pattern makes sense: dependency graph creation
is a full-fledged responsibility
• Dependency Injection and Law of Demeter are basic
buildings blocks of good software design
• Writing tests can help designing software

Contenu connexe

Tendances

Tendances (18)

Test driven development
Test driven developmentTest driven development
Test driven development
 
Unit Testing, TDD and ATDD
Unit Testing, TDD and ATDDUnit Testing, TDD and ATDD
Unit Testing, TDD and ATDD
 
Testing in-groovy
Testing in-groovyTesting in-groovy
Testing in-groovy
 
Junit, mockito, etc
Junit, mockito, etcJunit, mockito, etc
Junit, mockito, etc
 
Java 101 Intro to Java Programming - Exercises
Java 101   Intro to Java Programming - ExercisesJava 101   Intro to Java Programming - Exercises
Java 101 Intro to Java Programming - Exercises
 
Quickly Testing Qt Desktop Applications
Quickly Testing Qt Desktop ApplicationsQuickly Testing Qt Desktop Applications
Quickly Testing Qt Desktop Applications
 
Transferring Software Testing and Analytics Tools to Practice
Transferring Software Testing and Analytics Tools to PracticeTransferring Software Testing and Analytics Tools to Practice
Transferring Software Testing and Analytics Tools to Practice
 
Angular Optimization Web Performance Meetup
Angular Optimization Web Performance MeetupAngular Optimization Web Performance Meetup
Angular Optimization Web Performance Meetup
 
[Webinar] Qt Test-Driven Development Using Google Test and Google Mock
[Webinar] Qt Test-Driven Development Using Google Test and Google Mock[Webinar] Qt Test-Driven Development Using Google Test and Google Mock
[Webinar] Qt Test-Driven Development Using Google Test and Google Mock
 
ORM vs GraphQL - Python fwdays 2019
ORM vs GraphQL - Python fwdays 2019ORM vs GraphQL - Python fwdays 2019
ORM vs GraphQL - Python fwdays 2019
 
Guide to the jungle of testing frameworks
Guide to the jungle of testing frameworksGuide to the jungle of testing frameworks
Guide to the jungle of testing frameworks
 
Next Generation Developer Testing: Parameterized Testing
Next Generation Developer Testing: Parameterized TestingNext Generation Developer Testing: Parameterized Testing
Next Generation Developer Testing: Parameterized Testing
 
Easy mock
Easy mockEasy mock
Easy mock
 
Static analysis: Around Java in 60 minutes
Static analysis: Around Java in 60 minutesStatic analysis: Around Java in 60 minutes
Static analysis: Around Java in 60 minutes
 
.NET Database Toolkit
.NET Database Toolkit.NET Database Toolkit
.NET Database Toolkit
 
Qtp - Introduction to synchronization
Qtp -  Introduction to synchronizationQtp -  Introduction to synchronization
Qtp - Introduction to synchronization
 
Introduction to TDD with FlexUnit
Introduction to TDD with FlexUnitIntroduction to TDD with FlexUnit
Introduction to TDD with FlexUnit
 
Mini training - Moving to xUnit.net
Mini training - Moving to xUnit.netMini training - Moving to xUnit.net
Mini training - Moving to xUnit.net
 

Similaire à Writing testable code

33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests
Tomek Kaczanowski
 
Advance unittest
Advance unittestAdvance unittest
Advance unittest
Reza Arbabi
 
Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)
Mobile Developer Day
 

Similaire à Writing testable code (20)

Quick tour to front end unit testing using jasmine
Quick tour to front end unit testing using jasmineQuick tour to front end unit testing using jasmine
Quick tour to front end unit testing using jasmine
 
33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests33rd Degree 2013, Bad Tests, Good Tests
33rd Degree 2013, Bad Tests, Good Tests
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testing
 
Testing Spring Applications
Testing Spring ApplicationsTesting Spring Applications
Testing Spring Applications
 
Build Widgets
Build WidgetsBuild Widgets
Build Widgets
 
Testing in FrontEnd World by Nikita Galkin
Testing in FrontEnd World by Nikita GalkinTesting in FrontEnd World by Nikita Galkin
Testing in FrontEnd World by Nikita Galkin
 
Js tacktalk team dev js testing performance
Js tacktalk team dev js testing performanceJs tacktalk team dev js testing performance
Js tacktalk team dev js testing performance
 
Javascript Everywhere
Javascript EverywhereJavascript Everywhere
Javascript Everywhere
 
Testing ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NETTesting ASP.NET - Progressive.NET
Testing ASP.NET - Progressive.NET
 
2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests2012 JDays Bad Tests Good Tests
2012 JDays Bad Tests Good Tests
 
Java tutorials
Java tutorialsJava tutorials
Java tutorials
 
Java concurrency
Java concurrencyJava concurrency
Java concurrency
 
Advance unittest
Advance unittestAdvance unittest
Advance unittest
 
Good Practices On Test Automation
Good Practices On Test AutomationGood Practices On Test Automation
Good Practices On Test Automation
 
JDK Power Tools
JDK Power ToolsJDK Power Tools
JDK Power Tools
 
Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)
 
Testing AngularJS
Testing AngularJSTesting AngularJS
Testing AngularJS
 
Android Building, Testing and reversing
Android Building, Testing and reversingAndroid Building, Testing and reversing
Android Building, Testing and reversing
 
Android Automated Testing
Android Automated TestingAndroid Automated Testing
Android Automated Testing
 
Surviving javascript.pptx
Surviving javascript.pptxSurviving javascript.pptx
Surviving javascript.pptx
 

Dernier

%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
masabamasaba
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
masabamasaba
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
masabamasaba
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
VictoriaMetrics
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
masabamasaba
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is inside
shinachiaurasa2
 
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Medical / Health Care (+971588192166) Mifepristone and Misoprostol tablets 200mg
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
VictorSzoltysek
 

Dernier (20)

OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
OpenChain - The Ramifications of ISO/IEC 5230 and ISO/IEC 18974 for Legal Pro...
 
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
%+27788225528 love spells in Atlanta Psychic Readings, Attraction spells,Brin...
 
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
%+27788225528 love spells in Knoxville Psychic Readings, Attraction spells,Br...
 
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
%in Bahrain+277-882-255-28 abortion pills for sale in Bahrain
 
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) SolutionIntroducing Microsoft’s new Enterprise Work Management (EWM) Solution
Introducing Microsoft’s new Enterprise Work Management (EWM) Solution
 
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
%+27788225528 love spells in Colorado Springs Psychic Readings, Attraction sp...
 
Architecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the pastArchitecture decision records - How not to get lost in the past
Architecture decision records - How not to get lost in the past
 
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
WSO2CON 2024 - Cloud Native Middleware: Domain-Driven Design, Cell-Based Arch...
 
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park %in ivory park+277-882-255-28 abortion pills for sale in ivory park
%in ivory park+277-882-255-28 abortion pills for sale in ivory park
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Announcing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK SoftwareAnnouncing Codolex 2.0 from GDK Software
Announcing Codolex 2.0 from GDK Software
 
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
Large-scale Logging Made Easy: Meetup at Deutsche Bank 2024
 
8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students8257 interfacing 2 in microprocessor for btech students
8257 interfacing 2 in microprocessor for btech students
 
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
%+27788225528 love spells in new york Psychic Readings, Attraction spells,Bri...
 
The title is not connected to what is inside
The title is not connected to what is insideThe title is not connected to what is inside
The title is not connected to what is inside
 
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
Abortion Pill Prices Tembisa [(+27832195400*)] 🏥 Women's Abortion Clinic in T...
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM TechniquesAI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
AI Mastery 201: Elevating Your Workflow with Advanced LLM Techniques
 

Writing testable code

  • 1. Writing testable code Lessons learned from writing tests for The Incredible Circus (Web)
  • 2. How do you feel... When you are in a huge project and need to change a class that is part of the core of the application?
  • 5. Testing in Circus • Manual tests • Each developer had its own testing approach • New elements sometimes broke old elements’ behavior • Inconsistent behavior across platforms
  • 6. Testing in Circus-HTML5 • Should we use a framework? • Write them first, while or after writing the code under testing? • Are there rules, best-practices?
  • 7. Traditional testing approach Manual tests through UI Automation suites Unit tests Source: The Agile Testing Pyramid
  • 8. Agile testing approach Unit tests Acceptance tests UI Tests Exploratory tests Source: The Agile Testing Pyramid
  • 9. Writing unit tests • Test only one thing • Only one assert; no and nor or in its name • Enforce isolation • 3As: arrange, act, assert Source: Understanding Test Driven Development
  • 10. Writing unit tests How would you write the Burn hits the player test using 3A (arrange, act, assert)? PlayerEntity.prototype.burn = function (damage, ignoreDiversion) { this.hit(damage, ignoreDiversion); this.isBurning = true; this._burningTimer.start( PlayerEntity.BURNING_TIMEOUT ); };
  • 11. Writing unit tests PlayerEntity.prototype.burn = function (damage, ignoreDiversion) { this.hit(damage, ignoreDiversion); this.isBurning = true; this._burningTimer.start( PlayerEntity.BURNING_TIMEOUT ); }; function burnHitsThePlayer() { // arrange var player = new PlayerEntity(); this.stub(player, “hit”); // act player.burn(1); // assert assert.calledOnce(player.hit); }
  • 12. Writing unit tests OK, now write the Program sums two numbers correctly test function printSum(num1, num2) { console.log(num1 + num2); }
  • 13. There is no secret to writing tests, there are only secrets to writing testable code! Misko Hevery in Mr. Testable vs. Mr. Untestable
  • 14. Writing testable code Usual flaws that hardens testing 1. Do work in constructors 2. Dig into collaborators 3. Having global state and singletons 4. Having classes that do too much Source: Writing testable code
  • 15. Do work in constructors How would you create the GameWorld passes the correct time value to the physics simulation engine test? var GameWorld = function (entities) { ... this._gravity = new Vector2D(0.0,-21.2); this._world = new World(this._gravity); ... } GameWorld.prototype.step = function (delta) { ... this._world.step(delta / 1000); ... }
  • 16. Do work in constructors var GameWorld = function (entities) { ... this._gravity = new Vector2D(0.0,-21.2); this._world = new World(this._gravity); ... } GameWorld.prototype.step = function (delta) { ... this._world.step(delta / 1000); ... } function gameWorldPassesTimeCorrectly() { // arrange var entities = []; var gameWorld = new GameWorld(entities); // act gameWorld.step(1000); // assert ... ? }
  • 17. Do work in constructors • Have a test specific World that you could mock? • Have an accessor to the time variable in World? • What if it doesn’t store the time? There isn’t a clean way! function gameWorldPassesTimeCorrectly() { // arrange var entities = []; var gameWorld = new GameWorld(entities); // act gameWorld.step(1000); // assert ... ? }
  • 18. Do work in constructors GameWorld’s constructor has two responsibilities: 1. Initializing GameWorld 2. Initializing and wiring its dependencies Solution: • Split the responsibilities • Constructor initializes GameWorld • Factories/builders initializes dependencies • Inject the dependencies (DI) var GameWorld = function (entities) { ... this._finished = false; this._entities = []; this._player = null; this._stage = null; this._gravity = new Vector2D(0.0, -21.2); this._world = new World(this._gravity); ... }
  • 19. Do work in constructors Before After var GameWorld = function (entities) { ... this._finished = false; this._entities = []; this._player = null; this._stage = null; this._gravity = new Vector2D(0.0, -21.2); this._world = new World(this._gravity); ... } var GameWorld = function (entities, gravity, world) { ... this._finished = false; this._entities = []; this._player = null; this._stage = null; this._gravity = gravity; this._world = world; ... }
  • 20. Do work in constructors var GameWorld = function (entities, gravity, world) { ... this._gravity = gravity; this._world = world; ... } GameWorld.prototype.step = function (delta) { ... this._world.step(delta / 1000); ... } function gameWorldPassesTimeCorrectly() { // arrange var entities = []; var gravity = new Vector2D(0.0, -21.2); var world = new World(gravity); var stepSpy = this.spy(world, “step”); var gameWorld = new GameWorld(entities, gravity, world); // act gameWorld.step(1000); // assert assert(stepSpy.calledWith(1)); }
  • 21. Do work in constructors • Collaborators can’t be mocked • Unit test has to replicate the work done in the constructor • Complexity of collaborators is brought to the test • If one collaborator accesses the network, the test must be executed with access to the network • Violates the Single Responsibility Principle (S in SOLID) • Constructing the object graph is a full-fledged responsibility
  • 22. Do work in constructors Some signs of the existence of this problem • The new keyword (except for value objects) • Static method calls • Conditional or loop logic
  • 23. Dig into collaborators How would you create the No plots are generated if force is a null vector test? var TrajectoryPathEntity = function (gameWorld, referenceEntity) { Entity.call(this, gameWorld); this.force = null; this._gravity = gameWorld.gravity(); this.plots = []; this._refEntity = referenceEntity; this._entityMass = this._refEntity.mass(); }; TrajectoryPathEntity.prototype.update = function (delta) { this.plots = []; // Math that computes the points in the // trajectory and updates this.plots if // force is valid or non-zero };
  • 24. Dig into collaborators var TrajectoryPathEntity = function (gameWorld, referenceEntity) { Entity.call(this, gameWorld); this.force = null; this._gravity = gameWorld.gravity(); this.plots = []; this._refEntity = referenceEntity; this._entityMass = this._refEntity.mass(); }; TrajectoryPathEntity.prototype.update = function (delta) { this.plots = []; // Math that computes the points in the // trajectory and updates this.plots if // force is valid or non-zero }; function noPlotsAreGeneratedIfForceIsANullVector () { // arrange var gravity = new Vector2D(0, -20); var world = new World(gravity); var entity = new Entity(); var trajectoryPath = new TrajectoryPathEntity(world, entity); trajectory.force = new Vector(0, 0); // act trajectory.update(33); // assert assert.equals(trajectory.plots.length, 0); }
  • 25. Dig into collaborators Trajectory path needs GameWorld only to get gravity! (also because of Entity’s restriction) Must create a world when it only need the gravity var TrajectoryPathEntity = function (gameWorld, referenceEntity) { Entity.call(this, gameWorld); this.force = null; this._gravity = gameWorld.gravity(); this.plots = []; this._refEntity = referenceEntity; this._entityMass = this._refEntity.mass(); }; TrajectoryPathEntity.prototype.update = function (delta) { this.plots = []; // Math that computes the points in the // trajectory and updates this.plots if // force is valid or non-zero }; function noPlotsAreGeneratedIfForceIsANullVector () { // arrange var gravity = new Vector2D(0, -20); var world = new World(gravity); var entity = new Entity(); var trajectoryPath = new TrajectoryPathEntity(world, entity); trajectory.force = new Vector(0, 0); // act trajectory.update(33); // assert assert.equals(trajectory.plots.length, 0); }
  • 26. Dig into collaborators Complexity of World is brought to the test • Construction of World is made in TrajectoryPath’s test • Change in World -> change in TrajectoryPath’s test • Resources used by World must exist in TrajectoryPath’s test Solution: • “Don’t look for things, ask for things” • Inject the dependency (DI) function noPlotsAreGeneratedIfForceIsANullVector () { // arrange var gravity = new Vector2D(0, -20); var world = new World(gravity); var entity = new Entity(); var trajectoryPath = new TrajectoryPathEntity(world, entity); trajectory.force = new Vector(0, 0); // act trajectory.update(33); // assert assert.equals(trajectory.plots.length, 0); }
  • 27. Dig into collaborators Before After var TrajectoryPathEntity = function (gameWorld, referenceEntity) { Entity.call(this, gameWorld); this.force = null; this._gravity = gameWorld.gravity(); this.plots = []; this._refEntity = referenceEntity; this._entityMass = this._refEntity.mass(); }; var TrajectoryPathEntity = function (gravity, referenceEntity) { Entity.call(this); this.force = null; this._gravity = gravity; this.plots = []; this._refEntity = referenceEntity; this._entityMass = this._refEntity.mass(); };
  • 28. Dig into collaborators Before After function noPlotsAreGeneratedIfForceIsANullVector () { // arrange var gravity = new Vector2D(0, -20); var world = new World(gravity); var entity = new Entity(); var trajectoryPath = new TrajectoryPathEntity(world, entity); trajectory.force = new Vector(0, 0); // act trajectory.update(33); // assert assert.equals(trajectory.plots.length, 0); } function noPlotsAreGeneratedIfForceIsANullVector () { // arrange var gravity = new Vector2D(0, -20); var world = new World(gravity); var entity = new Entity(); var trajectoryPath = new TrajectoryPathEntity(gravity, entity); trajectory.force = new Vector(0, 0); // act trajectory.update(33); // assert assert.equals(trajectory.plots.length, 0); }
  • 29. Dig into collaborators • Violates the Law of Demeter • Violates the Single Responsibility Principle • Object becomes a service locator • Creates a deceitful API • You say that you need A, when actually you need B that is held by A
  • 30. Dig into collaborators Some signs of the existence of this problem • An object named context • More than one “.” in a call chain • Having to create mocks that returns mocks in tests
  • 31. Global state & singletons RopeEntity.prototype.onCollision = function (collision) { if (this.hasPlayer()) return; var player = collision.collidedEntity; var pivotPoint = player.pivotPoint(); player.stand(); var bottomCenter = player.boundingRect() .bottomCenter(); this._updateContactPoint(bottomCenter); this.setFocus(true); this.grabPlayer(player); this.setState(RopeEntity.State.Bouncing); var rect = this.boundingRect(); this.testCombo({ x: rect.center().x, y: 0 }, { x: pivotPoint.x, y: 0 }, 0, rect.width / 2); };
  • 32. Global state & singletons This test failed randomly. Can you spot the error? RopeEntity.prototype.onCollision = function (collision) { if (this.hasPlayer()) return; var player = collision.collidedEntity; var pivotPoint = player.pivotPoint(); player.stand(); var bottomCenter = player.boundingRect() .bottomCenter(); this._updateContactPoint(bottomCenter); this.setFocus(true); this.grabPlayer(player); this.setState(RopeEntity.State.Bouncing); var rect = this.boundingRect(); this.testCombo({ x: rect.center().x, y: 0 }, { x: pivotPoint.x, y: 0 }, 0, rect.width / 2); }; function playerMakes150PointsWhenCollidesAtCenter() { // arrange var builder = new WorldBuilder(); var rope = builder.buildRopeEntity(); var player = builder.buildPlayerEntity(); var event = { collidedEntity: player }; var bonusSpy = this.spy( builder.getWorld().scoreBoard, "increaseBonus“ ); var pivotPoint = { x: this.rope.boundingRect().center().x, y: this.rope.boundingRect().top }; this.stub(player, "pivotPoint") .returns(pivotPoint); // act rope.onCollision(this.event); // assert assert(this.bonusSpy.calledWith(1.5)); }
  • 33. Global state & singletons RopeEntity PlatformEntity GrabberEntity -shouldAcceptBonus +testCombo() GrabberEntity.prototype.testCombo = function (base, center, subDistance, maxDistance) { if (!GrabberEntity._shouldAcceptBonus) { GrabberEntity._shouldAcceptBonus = true; this._comboCounted = true; return; } // do combo computation };
  • 34. Global state & singletons RopeEntity PlatformEntity GrabberEntity -shouldAcceptBonus +testCombo() Cause • A global variable that isn’t visible in the method being tested • So I’ll have to look at all code paths and see every possible interaction with every global variable? Yeap... ☹
  • 35. Global state & singletons Our solution: use a setup/ teardown method A better solution: • Rewrite in order to remove the global variable • These tests can’t be run in parallel ☹ function playerMakes150PointsWhenCollidesAtCenter() { // arrange ... // act ... // assert ... } function setUp() { GrabberEntity._shouldAcceptBonus = true; } function tearDown() { GrabberEntity._shouldAcceptBonus = false; }
  • 36. Global state & singletons • Requires setup/teardown methods to restore the state • Forces the developer to know every possible interaction with the global state • Makes it impossible to run tests in parallel • Global state is non-mockable • Global state creates a deceitful API • Tells that the class or method that accesses the global state has no dependencies • Estabilishes hidden channels between objects
  • 37. Global state & singletons What about singletons? A singleton is global state in sheep’s clothing It feels like a plain class, but it is the same as global state: • Can be acessed anywhere • Every variable it holds is no different than a static variable
  • 38. Global state & singletons Can’t I have a class that must have only a single instance? Yes, you can, but the singleness should be controlled by the programmer or framework. This forces the dependencies to be explicit!
  • 39. Global state & singletons Some signs of the existence of this problem • Static fields • Singletons • Tests failling randomly • Tests failling when the order of execution changes
  • 40. Class that does too much • Similar problem of a constructor that does work • Becomes harder to test features in isolation • Often, methods have mixed reponsibilities • Hard to understand and hard to hand-off
  • 41. Class that does too much Some signs of the existence of this problem • Excessive scrolling • When asked what it does, contains too many “and” in the answer • A manager might be a sign that it is a class that does more than it should • The God class
  • 42. Summary Constructor doing work Why is it negative? • Testing directly is difficult • Breaks SRP How to fix it? • Breaks responsibilities • Create factories/builders • Inject dependencies Dig into collaborators Why is it negative? • Deceipts the user of the API • Breaks SRP and LoD How to fix it? • “Don’t look for things, ask for things” • Inject dependencies
  • 43. Summary Global state & singletons Why is it negative? • Deceipts user of the API • Requires setup/teardown methods • Forbis tests to be run in parallel How to fix it? • Inject dependencies Class that does too much Why is it negative? • Hardens testing • Breaks SRP How to fix it? • Break responsibilities
  • 44. What I learned • Tests gave me confidence that my code works • Brought me tranquility when I needed to make changes • It is often uncomfortable to write tests
  • 45. What I learned • Tests are executable documentation If somehow all your production code got deleted, but you had a backup of your tests, then you'd be able to recreate the production system with a little work. (…) If, however, it was your tests that got deleted, then you'd have no tests to keep the production code clean. The production code would inevitably rot, slowing you down. Test First • There is a relation between testable code and good quality code
  • 46. Takeaways • The Factory Pattern makes sense: dependency graph creation is a full-fledged responsibility • Dependency Injection and Law of Demeter are basic buildings blocks of good software design • Writing tests can help designing software