Younes Jaaidi
@yjaaidi
🇫🇷 Lyon
eXtreme Programming since 2007
Web Dev & XP Coach
👨🏻🏫 Web Dev, XP & Security Trainings
@yjaaidi
👨🏻🏫 Web Dev, XP & Security Trainings
🎯 Consulting & XP Coaching
@yjaaidi
👨🏻🏫 Web Dev, XP & Security Trainings
💻 Remote Consulting Sessions
🎯 Consulting & XP Coaching
@yjaaidi
Angular Testing Workshop
@yjaaidi
@yjaaidi
Do we really
need tests?
🤖
Automated tests
@yjaaidi
🤖
Automated tests
🐞
Less bugs
@yjaaidi
🤖
Automated tests
✨
Quality
🐞
Less bugs
Not Visible
Visible
@yjaaidi
🤖
Automated tests
✨
Quality
🐞
Less bugs
Not Visible
Visible
🥵
Stop testing
every change
manually
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
✨
Quality
🐞
Less bugs
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
✨
Quality
🐞
Less bugs
🐇
Faster time
to market
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🐇
Faster time
to market
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🐇
Faster time
to market
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🐇
Faster time
to market
⚽️
Collective
ownership
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🐇
Faster time
to market
⚽️
Collective
ownership
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🛠
Refactoring
🐇
Faster time
to market
⚽️
Collective
ownership
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🛠
Refactoring
🐇
Faster time
to market
⚽️
Collective
ownership
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🛠
Refactoring
🐇
Faster time
to market
⚽️
Collective
ownership
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🛠
Refactoring
🐇
Faster time
to market
⚽️
Collective
ownership
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🛠
Refactoring
🐇
Faster time
to market
⚽️
Collective
ownership
♻️
Update
dependencies
automatically
(or frequently)
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🛠
Refactoring
🐇
Faster time
to market
⚽️
Collective
ownership
♻️
Update
dependencies
automatically
(or frequently)
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🛠
Refactoring
🐇
Faster time
to market
⚽️
Collective
ownership
♻️
Update
dependencies
automatically
(or frequently)
Not Visible
Visible
@yjaaidi
🥵
Stop testing
every change
manually
⚡️
Faster
development
🤖
Automated tests
💪
Confidence
✨
Quality
🐞
Less bugs
🛠
Refactoring
🐇
Faster time
to market
⚽️
Collective
ownership
♻️
Update
dependencies
automatically
(or frequently)
🥳
Pleasure
Not Visible
Visible
@yjaaidi
Angular Hello World Dependencies
~1 000 dependencies
@yjaaidi
Angular Hello World Dependencies
🏋️♀️ ~300MB 🏋️♂️
~1 000 dependencies
@yjaaidi
Angular Hello World Dependencies
🏋️♀️ ~300MB 🏋️♂️
~1 000 dependencies
~3.5 million lines of code
@yjaaidi
Tests are the best documentation
@yjaaidi
🚀 Up-to-date
Tests are the best documentation
@yjaaidi
🦋 Symmetric
you get the exact doc of the version you are using
🚀 Up-to-date
Tests are the best documentation
@yjaaidi
🌏 More surface
🦋 Symmetric
you get the exact doc of the version you are using
🚀 Up-to-date
Example: RxJS Count Operator
describe('count operator', () => {
it('should count the values of an observable', () => {
const source = hot('--a--b--c--|');
const expected = '-----------(x|)';
expectObservable(source.pipe(count())).toBe(expected, {x: 3});
});
it('should be never when source is never', () => {
const e1 = cold('-');
const expected = '-';
expectObservable(e1.pipe(count())).toBe(expected);
});
@yjaaidi
asDiagram('count')('should count the values of an observable', () => {
const source = hot('--a--b--c--|');
const expected = '-----------(x|)';
expectObservable(source.pipe(count())).toBe(expected, {x: 3});
});
@yjaaidi
@yjaaidi
☁️
Frontend
Backend
Database
Other Services
Unit Testing
@yjaaidi
☁️
Frontend
Backend
Database
Other Services
Integration Testing
Unit Testing
@yjaaidi
☁️
Frontend
Backend
Database
Other Services
E2E Testing (or Functional Testing)
Integration Testing
Unit Testing
@yjaaidi
☁️
Frontend
Backend
Database
Other Services
🧹 E2E Testing
🚀 Test a feature or set of features
🤖 Browser automation
✅ Happy path or smoke tests
@yjaaidi
🧹 E2E Testing
🚀 Test a feature or set of features
it('should search using emojis', () => {
searchPage.go();
searchPage.search('🍺');
const resultList = searchPage.getResultList();
expect(resultList.length).toBeGreaterThan(0);
expect(resultList[0].title).toContain('Beer');
});
🤖 Browser automation
✅ Happy path or smoke tests
@yjaaidi
🧹 E2E Testing
🚀 Test a feature or set of features
it('should search using emojis', () => {
searchPage.go();
searchPage.search('🍺');
const resultList = searchPage.getResultList();
expect(resultList.length).toBeGreaterThan(0);
expect(resultList[0].title).toContain('Beer');
});
🤖 Browser automation
✅ Happy path or smoke tests
💵 Cheaper
🤝 Test interactions
😊
⛵️ Loosely coupled to implementation
@yjaaidi
🧹 E2E Testing
🚀 Test a feature or set of features
it('should search using emojis', () => {
searchPage.go();
searchPage.search('🍺');
const resultList = searchPage.getResultList();
expect(resultList.length).toBeGreaterThan(0);
expect(resultList[0].title).toContain('Beer');
});
🤖 Browser automation
✅ Happy path or smoke tests
💵 Cheaper
🐢 Slower
🐞 Harder to debug
🤝 Test interactions
😊
😔
⛵️ Loosely coupled to implementation
@yjaaidi
🖌 Unit Testing
🐁 Test a small piece of code
🎯 Precision
@yjaaidi
🖌 Unit Testing
🐁 Test a small piece of code
🎯 Precision
@yjaaidi
it('should parse price', () => {
const price = parsePrice('30.01€');
expect(price).toEqual({
coefficient: 3001,
exponent: -2,
currency: 'EUR'
});
});
🖌 Unit Testing
🐁 Test a small piece of code
🎯 Precision
😊
@yjaaidi
it('should parse price', () => {
const price = parsePrice('30.01€');
expect(price).toEqual({
coefficient: 3001,
exponent: -2,
currency: 'EUR'
});
});
🐇 Faster
🐞 Easier to debug
📐 Edge case testing
🧰 Less setup
🖌 Unit Testing
🐁 Test a small piece of code
🎯 Precision
😊
😔
@yjaaidi
it('should parse price', () => {
const price = parsePrice('30.01€');
expect(price).toEqual({
coefficient: 3001,
exponent: -2,
currency: 'EUR'
});
});
🔗 More coupled to implementation
🤝 Doesn't test interactions
🐇 Faster
🐞 Easier to debug
📐 Edge case testing
🧰 Less setup
Unit Tests are F.I.R.S.T.
@yjaaidi
🐇 Fast
🐻 Independent
🔁 Repeatable
🤖 Self-validating
⏰ Timely
@yjaaidi
Integration Tests
Like Unit Tests but...
🐢 Slow
🔗 Have an external dependency
or
or
🍕 Cover more than one unit of code
Integration Tests
@yjaaidi
Like Unit Tests but...
🐢 Slow
🔗 Have an external dependency
or
or
🍕 Cover more than one unit of code
What's the right size for a unit of code?
🐁
Small unit
🎭
Less mocks
💵
More tests
🐞
Easy debug
🔗
Coupled to implementation
🤝
No interaction
🚫
@yjaaidi
🐁
Small unit
🎭
Less mocks
💵
More tests
🐘
Big unit
🐞
Easy debug
🔗
Coupled to implementation
🤝
No interaction
🚫
🤝
Interaction
⛵️
Loosely coupled
🐞
Hard debug
💵
Less tests
🎭
More mocks
@yjaaidi
Classicists
Detroit Style
🐘 Bigger units
📉 Less tests for same coverage
⛵️ Testing the feature
(state-based testing)
🎭 Avoid mocks
🚫
@yjaaidi
Mockists
London Style
🐁 Small units
🎭 Mocks
📈 More tests for same coverage
🔗 Testing the implementation (interaction-based testing)
@yjaaidi
Code
Interaction Point
Test Suite
Test
Stub / Fake / Spy / Mock
Coverage
Mockists
@yjaaidi
Code
Interaction Point
Test Suite
Test
Stub / Fake / Spy / Mock
Coverage
Mockists
Classicists
Step 1
@yjaaidi
Code
Interaction Point
Test Suite
Test
Stub / Fake / Spy / Mock
Coverage
Mockists
Classicists
Step 1
Step 2
@yjaaidi
The Right Size for a Unit of Code
📈
Calculate your Return on Investment
@yjaaidi
The Right Size for a Unit of Code
🐘 Largest unit where the following conditions are still true
🎭 You don't need mocks (or not too many)
⏱ You can investigate broken tests in seconds
🏁 You can cover the whole unit
Test-Driven Development
⏰
Write tests first
@yjaaidi
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
@yjaaidi
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🏁
Know when you are done
@yjaaidi
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
@yjaaidi
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
@yjaaidi
📉
Less code
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
🔗
Not coupled
to implementation
🚫
@yjaaidi
📉
Less code
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
🔗
Not coupled
to implementation
🚫
@yjaaidi
📉
Less code
💵
Cheap
Refactoring
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
🎯
Share intention
🔗
Not coupled
to implementation
🚫
@yjaaidi
📉
Less code
💵
Cheap
Refactoring
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
🎯
Share intention
🔁
Early feedback
🔗
Not coupled
to implementation
🚫
@yjaaidi
📉
Less code
💵
Cheap
Refactoring
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🛀
Relaxed implementation
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
🎯
Share intention
🔁
Early feedback
🔗
Not coupled
to implementation
🚫
@yjaaidi
📉
Less code
💵
Cheap
Refactoring
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🛀
Relaxed implementation
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
🎯
Share intention
🔁
Early feedback
🔗
Not coupled
to implementation
🚫
@yjaaidi
📉
Less code
💵
Cheap
Refactoring
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🛀
Relaxed implementation
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
🎯
Share intention
🔁
Early feedback
🔗
Not coupled
to implementation
🚫
@yjaaidi
📉
Less code
💵
Cheap
Refactoring
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🛀
Relaxed implementation
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
🎯
Share intention
🔁
Early feedback
🔗
Not coupled
to implementation
🚫
@yjaaidi
📉
Less code
💵
Cheap
Refactoring
Test-Driven Development
⏰
Write tests first
👷♂️
Focus on the feature
🛀
Relaxed implementation
🏁
Know when you are done
🏭
Avoid Artificial
Complexity
🎯
Share intention
🔁
Early feedback
🔗
Not coupled
to implementation
🚫
💉
TDD
Addiction
@yjaaidi
📉
Less code
💵
Cheap
Refactoring
TDD Limitations
@yjaaidi
🙍🏻♂️ Doesn't enforce collective ownership
🐘 Doesn't encourage small changes
🦋 Doesn't solve asymmetry
🚫
Limbo
while(true);
do
git pull --rebase;
git push;
done;
🦋 More symmetry
🍝 Less git spaghetti (cherry pick, rebase, squash, ...)
🤬 Less conflict
Timeboxed TDD
@yjaaidi
⏳
Reset timer
⌛️
Timeout
🗜
Test
⌨️
Code
✅
OK
☁
Commit
& Push
🔥
Revert
🚨
Fail
Few
minutes
Test && Commit || Revert
@yjaaidi
Lars Barlindhaug
Kent Beck
Ole Tjensvoll Johannessen
initiated in Oslo 🇳🇴
by
@barlindh
@kentbeck
Oddmund Strømme
@jraregris
💡 An idea from
Test && Commit || Revert
@yjaaidi
🗜
Test
⌨️
Code
✅
OK
☁
Commit
& Push
🔥
Revert
🚨
Fail
@yjaaidi
Unit Testing
E2E Testing
Visual Testing
Karma
Jest
Protractor
Storybook
+
Jest
+
+
Let's buy sandwiches
@yjaaidi
@yjaaidi
<sandwich-list>
<cart-preview>
Cart
@yjaaidi
<sandwich-list>
<cart-preview>
Cart
totalPrice
addSandwich(sandwich)
@yjaaidi
⌨️ Grab your keyboards ⌨️
@yjaaidi
⌨️
⌨️
⌨️
⌨️
⌨️
⌨️
1. Limbo
while(true);
do
git pull --rebase;
git push;
done;
@yjaaidi
yarn test # or npm test
2. Run tests
@yjaaidi
describe('Cart', () => {
xit('should add sandwich', () => {
throw new Error('🚧 Work in progress');
});
xit('should get total price', () => {
throw new Error('🚧 Work in progress');
});
});
3. Empty specs
src/app/cart/cart.spec.ts
@yjaaidi
describe('Cart', () => {
xit('should add sandwich', () => {
throw new Error('🚧 Work in progress');
});
xit('should get total price', () => {
throw new Error('🚧 Work in progress');
});
});
3. Empty specs
src/app/cart/cart.spec.ts
git commit -m "🗜 Cart specs. 🎯 Add sandwich to cart."
@yjaaidi
xit('should add sandwich', () => {
// cart.addSandwich(a 10€ burger)
// cart.addSandwich(a 5.3€ butter & butter)
// get sandwishes list
// check count & order
throw new Error('🚧 Work in progress');
});
4. Comment-Driven Development
git commit -m "🗜 Cart add sanwich specs. 🎯 Add sandwich to cart."
@yjaaidi
xit('should add sandwich', () => {
const burger = new Sandwich({title: 'Burger', price: 10});
const butter = new Sandwich({title: 'Butter & butter', price: 5.3});
cart.addSandwich(burger);
cart.addSandwich(butter);
const sandwichList = cart.getSandwichList();
expect(sandwichList).toEqual([burger, butter]);
});
5. Write the test
git commit -m "🗜 Cart add sanwich specs. 🎯 Add sandwich to cart."
@yjaaidi
xit('should add sandwich', () => {
const burger = new Sandwich({title: 'Burger', price: 10});
const butter = new Sandwich({title: 'Butter & butter', price: 5.3});
cart.addSandwich(burger);
cart.addSandwich(butter);
const sandwichList = cart.getSandwichList();
expect(sandwichList).toEqual([burger, butter]);
});
5. Write the test
@yjaaidi
class Cart {
/**
* @deprecated Work in progress
*/
addSandwich(sandwich: Sandwich) {
throw new Error('🚧 Work in progress');
}
}
6. Generate code
& pre-deprecate
git commit -m "🗜 Cart add sanwich specs. 🎯 Add sandwich to cart."
@yjaaidi
it('should add sandwich', () => {
...
});
7. Enable test & make it pass
git commit -m "🚧 Add sanwich to cart. 🎯 Add sandwich to cart."
@yjaaidi
Recap
1. 💃 Limbo
2. ♾ Run tests forever
3. ✅ Empty specs
4. 📝 Comment-Driven Development
5. 🗜 Write the test
6. 💀 Generate skeleton & pre-deprecate
7. ⌨️ Enable test & make it pass
8. 🧹 Remove deprecation
@yjaaidi
Jest
@yjaaidi
Jest
⚡️ Fast
@yjaaidi
Jest
👌 Less configuration
⚡️ Fast
@yjaaidi
Jest
👌 Less configuration
✌️ Test parallelization & isolation
⚡️ Fast
@yjaaidi
Jest
👌 Less configuration
🚀 No browser
✌️ Test parallelization & isolation
⚡️ Fast
@yjaaidi
Jest
👌 Less configuration
🚀 No browser
✌️ Test parallelization & isolation
⚡️ Fast
✅ Readable reports
@yjaaidi
Jest
👌 Less configuration
🚀 No browser
✌️ Test parallelization & isolation
⚡️ Fast
✅ Readable reports
🏘 Big community
@yjaaidi
Jest
Compatible with Jasmine syntax
👌 Less configuration
🚀 No browser
✌️ Test parallelization & isolation
⚡️ Fast
✅ Readable reports
🏘 Big community
@yjaaidi
Jest
@yjaaidi
npm install -g @briebug/jest-schematic
ng g @briebug/jest-schematic:add
yarn test --watch # or npm test -- --watch
Jest ♥ Angular
@yjaaidi
Testing Components
@yjaaidi
ItemList
ItemPreview
<div> <item-preview>
Component { ... }
<div> <span>
Component { ... }
@yjaaidi
<div> <item-preview>
Component { ... }
<div> <span>
Component { ... }
"Integration"
TestBed.configureTestingModule({
imports: [ItemListModule]
});
ItemList
ItemPreview
@yjaaidi
<div> <item-preview>
Component { ... }
<div> <span>
Component { ... }
Shallow
"Integration"
TestBed.configureTestingModule({
imports: [ItemListModule]
});
TestBed.configureTestingModule({
declarations: [ItemListComponent],
errors: [NO_ERRORS_SCHEMA]
});
ItemList
ItemPreview
@yjaaidi
<div> <item-preview>
Component { ... }
<div> <span>
Component { ... }
Isolated
Shallow
"Integration"
TestBed.configureTestingModule({
imports: [ItemListModule]
});
TestBed.configureTestingModule({
declarations: [ItemListComponent],
errors: [NO_ERRORS_SCHEMA]
});
cmp = new ItemListComponent(...);
ItemList
ItemPreview
@yjaaidi
Mocking a Service
describe('SandwichListComponent', () => {
let cart: Cart;
beforeEach(() => cart = TestBed.get(Cart));
it('should add sandwich', () => {
spyOn(cart, 'addSandwich');
clickFirstBuyButton();
expect(cart.addSandwich).toHaveBeenCalledWith(burger);
});
});
@yjaaidi
Dependency Injection Override
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [SandwichListComponent],
providers: [
{
provide: Cart,
useClass: FakeCart
}
]
})
.compileComponents();
}));
@yjaaidi
Next episodes...
✅ E2E testing
🐒 HttpTestingModule & RouterTestingModule
🦋 Visual testing
@yjaaidi
🏀 RxJS Marble Testing
Prochaine Formation
17 au 19 juin à Lyon
@yjaaidi
Angular Testing Workshop
By Younes
Angular Testing Workshop
- 1,224