12. Agenda
• A Brief Overview Of Test Driven-Development (TDD)
• Test Automation Pyramid
• Picking What To Test
• Events In TDD Environment
• APIs in TDD Environment
• Databases in TDD Environment
• Bugs in TDD Environment
37. Manual Testing
• Tests how all components work together
• “Sanity” Checking
• Does everything look “right"
• Does it flow correctly
38. Manual Testing
• Use a checklist or design document
• VERY slow and error prone
• Can never get completely away from this
• Ideally this is smallest part of test suite
40. End-to-End Tests
• Generally involves headless browser or full API requests
• Multi-step processes
• Checkout process
• Sign-up process
41. End-to-End Tests
• Automated using testing framework like PHPUnit/Pest/Cypress
• Faster than manual testing AND less error prone
• Brittle due to large number of components
43. Integrations Tests
• Tests how classes work when integrated together
• Controllers + forms + views + models to gether
44. Integrations Tests
• Automated using testing framework like PHPUnit/Pest
• Faster than manual and end-to-end testing AND less error prone
• Less brittle
72. User Logs Into System
User Navigates to Task
User Clicks “Complete” Button
Browser requests /tasks/#/complete
Router routes request
Permissions Are Checked
TaskCompleted Event is Raised
Event is Handled
Listener is Found and Run
Message is Generated To Original
User
Message is Sent to SMS Service
73. User Logs Into System
User Navigates to Task
User Clicks “Complete” Button
Browser requests /tasks/#/complete
Router routes request
Permissions Are Checked
TaskCompleted Event is Raised
Event is Handled
Listener is Found and Run
Message is Generated To Original
User
Message is Sent to SMS Service
74. Router routes request
Permissions Are Checked
TaskCompleted Event is Raised
Event is Handled
Listener is Found and Run
Message is Generated To Original
User
Message is Sent to SMS Service
83. Router routes request
Permissions Are Checked
TaskCompleted Event is Raised
Event is Handled
Listener is Found and Run
Message is Generated To Original
User
Message is Sent to SMS Service
84. Message is Generated To Original
User
Message is Sent to SMS Service
TaskCompleted Event is Raised
85. Message is Generated To Original
User
Message is Sent to SMS Service
TaskCompleted Event is Raised
93. Test Doubles
“[O]bjects or procedures that look
and behave like their release-
intended counterparts, but are
actually simplified versions that
reduce the complexity and
facilitate testing” - wikipedia
102. Stubbing
“[T]est stubs are programs that
simulate the behaviours of
software component … [t]est
stubs provide canned answers to
calls made during the test” -
Wikipedia
116. Dependency
Injection (DI)
“[D]ependency injection is a
programming technique in which
an object or function receives
other objects or functions that it
requires, as opposed to creating
them internally” - wikipedia
156. Using TDD To Learn a 3rd Party Code
• Need to learn API (new to me)
• Going to create own suite of tools for 3rd party API
• Build a service to abstract -> easier to create a test double
157. Using TDD To Learn a 3rd Party Code
• Going to use TDD
• Need to manually check results
• Or build tool to check results
198. Databases
1. Don’t Test It -> missed because it’s not tested
2. Doubles -> missed because it’s not using real data
3. Use the actual database -> caught
199. Databases
1. Once you start doing this hard to stop
2. Other tests may impact each other
3. Need to worry about polluting DB
200. Databases
1. Reset DB at start of every test
1. Can be slow as DB structure changes over time
2. Hard to generate test data because it erases
2. Reset DB at start of test run
1. Generate “test” information to pull
2. Keep everything in known good state
208. 1. Replicate the problem
2. Form a hypothesis
3. Test the hypothesis
4. Repeat until the bug is “fixed”
Bugs
209. Bugs
1. Replicate the problem
2. Form a hypothesis
3. Test the hypothesis
4. Repeat until the bug is “fixed”
5. Revert the change (git stash)
6. TDD to the fix
210. What To Tests?
1. Reported Bug: User::getDisplayName()
2. Root Cause: Str::append()?
3. Both?
218. 3rd Party APIs
1. Can use TDD to learn APIs/libraries
1. Come back to when things break in the future
2. Run separately to avoid costs
219. Databases
1. Other tests may impact each other
2. Need to worry about polluting DB
1. Reset DB
2. Transactions
3. Helper functions
220. Bugs
1. Replicate the problem
2. Form a hypothesis
3. Test the hypothesis
4. Repeat until the bug is “fixed”
5. Revert the change (git stash)
6. TDD to the fix
At it’s core TDD thrives on it’s <slide>
But that has a downside
When we (including me) explain it to people earlier in their journey
Do them a horrible disservice
End up with examples that just add two numbers together
Take this knowledge back to work
Open project management tool and find
Give Up
All that is <slide>
<slide> that TDD I haven’t given them the tools to attack
My goal in the next 50 minutes is to give you some tools, techniques, and know how
Get rid of the complexity and back to <click>
Goal:give you tools to tackle even the hardest TDD problem
Very example heavy
For those of you who haven’t met me my name is …
Professional PHP Developer for 16 years
// team lead/CTO role for 11 of those 16
Currently Director of Technology at WeCare Connect
Survey solutions to improve employee and resident retention at skilled nursing facilities
Use PHP for our backend
Also …
TDD is a test-first software development process that uses short development cycles to write very specific test cases and then modify our code so the tests pass.
TDD consists of five phases that we will repeat as we modify our code. Each of the phases happens very quickly and we might go through all five phases in less than a minute.
The first thing we're going to do is write a failing test. We'll use this failing test to help determine when we've achieved our expected functionality.
In this step, we're going to run the test to make sure our test fails before we move on to the next phase. It's very easy to write a test that doesn't fail so we **always** run our test to verify it's failing before moving to the next phase.
3. Make a little change
Now our goal is to change the smallest amount of code possible to get that test to pass.
We don't want to change any more than is necessary because that extra bit of change wasn't made using TDD and is potentially not tested. We don't need perfect code in this phase we just need code that makes the test pass. It's very easy to get caught up in making sure everything is perfect but that's not the goal here. Perfect comes later.
4. Run all tests and see them all succeed
Now that we've made our change we can run our test and see that it passes new test and any other tests.
If it doesn't then we just jump back to phase #3 and keep making small changes until it does.
Now that we have our tests passing we're going to take a break and inspect both our test code and our code under test to see where we can make changes so it's easier for future developers to read, understand, and maintain.
Could also be red/green/refactor
When I mentor new team members I tell them our application has four types of tests
General cost increases upwards why we want mostly bulk at bottom
If you’re test suite doesn’t look like this don’t stress
This is my idea but You and your team have to pick what works for you
“The Perfect is the Enemy of The Good” It’s better to have some
What you don’t want is
Hard to work in this environment
Burn out is real
My goal is to break this into small enough chunks I can complete it
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
Seems a little complex
Makes you want to throw your hands up and say this is impossible!
This is where TDD “falls” down
What if we simplify this?
First 4 items are browser -> ignore those
Makes it a lot smaller still a lot though right?
Several types of dependencies
Software your team didn’t write
Looking at our chart we have 3rd party api
And maybe events
Software your team did write
Fun thing about internal dependencies -> from your perspective external dependency
When doing TDD on your code you {slide}
Remove all external pieces
Left with just a few pieces easier to handle
Two major pieces left seems like something we could tackle
Both of which we can handle with unit tests
A finished solutions
Problem: hard to test
Goal in unit testing range
Two things we need to discuss to fix
Test doubles are the superheroes of unit testing, allowing us to replace real components with stand-ins during tests that mimic their behavior. This replacement fosters isolation, testing components separately from their dependencies.
Two main branches of test doubles
A mock object is an object that meticulously records method calls and takes pride in asserting whether expected interactions took place. Mocks excel when your mission is to validate the interactions between the real object being under test and its collaborators.
A spy is an implementation to verify a specific behavior.
A stub provides predetermined responses to calls made during a test. It’s ideal for scenarios when you’re more concerned with the behavior of the object under test than its interactions with other components. Think of it as a scripted actor playing a role during testing.
A stub is the simplest implementation with a hardcoded behavior.
A dummy is a just simple implementation that does nothing.
A fake is a simplified implementation to simulate the original behavior.
Mocks -> Useful for seeing what’s coming out of your code
Stubbing -> useful for when you need incoming
Use tool (Mockery) to generate them and have it do the assertions
Write reusable classes and assert results in our tests
Both valid I start out
Creating testable code -> requires architecture decision
We want to use DI
Without DI Example class
Problem: code is written to specific implementation of dependency
Hard to swap out
These are simple but could be complicated
With DI Example class
Pass dependencies as a parameter (either function or class constructor)
Downside to this:
Have to define how things are created
This example uses PHP-DI
Phase 1: Write a little test
Specify explicitly what you are testing
Kinda wordy
// Arrange: Bring the system under test in the desired state. Prepare dependencies, arguments and finally construct the SUT.
// Act: Invoke a tested element.
// Assert: Verify the result, the final state, or the communication with collaborators.
// Arrange: Bring the system under test in the desired state. Prepare dependencies, arguments and finally construct the SUT.
// Act: Invoke a tested element.
// Assert: Verify the result, the final state, or the communication with collaborators.
// Arrange: Bring the system under test in the desired state. Prepare dependencies, arguments and finally construct the SUT.
// Act: Invoke a tested element.
// Assert: Verify the result, the final state, or the communication with collaborators.
Phase 2
Phase 3
Phase 4 and 5 - see how fast that was?
Back to phase 1
That’s how to test events but we can test so much more now
You now have a bunch of tools to create unit tests for any kind of interactions between classes
Leads into our next topic <slide>
Picked SMS messages two reasons
because Vonage is a sponsor
Hard to test because <click>
Goal in unit testing range
Two things we need to discuss to fix
Don’t want it running every time because cost
Don’t want it running every time because cost
You may have noticed I never had a failing test
In this case: didn’t even know what that would be
notice: copy and paste and not cut and paste
notice: copy and paste and not cut and paste
notice: copy and paste and not cut and paste
notice: copy and paste and not cut and paste
Purists say never use the DB but that hides problems
Much less plan and more figure it out
What I expect is to have the getDisplayName() return my name but it returns my last name only
This broke once
Might brake again
Much less plan and more figure it out
Bugs are a different beast
Have to “invert” our process a little
I’m really belt and suspenders here
I do both gives us extra coverage (plus someone must have not done it before)
General cost increases upwards why we want mostly bulk at bottom
Bugs are a different beast
Have to “invert” our process a little