SlideShare a Scribd company logo
1 of 91
Download to read offline
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
!
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
!
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 3
! Gabe from Vancouver
🐙 @garbles
📬 reactive@gabe.pizza
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
careers.unbounce.com
4
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
Property-Based Testing for Godly Tests
5
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 6
Why do we
write tests?
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 7
"Program testing can be a very
effective way to show the presence of
bugs, but it is hopelessly inadequate
for showing their absence."
- Edsger Dijkstra
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 8
Tests prevent regressions!
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 9
Tests prevent regressions!
(that you know about)
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 10
Example-Based Tests
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 11
function sort(arr: number[]): number[]
it('does nothing to empty arrays', () => {
expect(sort([])).toEqual([]);
});
it('sorts unsorted arrays', () => {
expect(sort([6, 2, 1])).toEqual([1, 2, 6]);
});
it('does not change a sorted array', () => {
expect(sort([1, 2, 3, 4])).toEqual([1, 2, 3, 4]);
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 12
function sort(arr: number[]): number[]
it('does nothing to empty arrays', () => {
expect(sort([])).toEqual([]);
});
it('sorts unsorted arrays', () => {
expect(sort([6, 2, 1])).toEqual([1, 2, 6]);
});
it('does not change a sorted array', () => {
expect(sort([1, 2, 3, 4])).toEqual([1, 2, 3, 4]);
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 13
function sort(arr: number[]): number[]
it('does nothing to empty arrays', () => {
expect(sort([])).toEqual([]);
});
it('sorts unsorted arrays', () => {
expect(sort([6, 2, 1])).toEqual([1, 2, 6]);
});
it('does not change a sorted array', () => {
expect(sort([1, 2, 3, 4])).toEqual([1, 2, 3, 4]);
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 14
function sort(arr: number[]): number[]
it('does nothing to empty arrays', () => {
expect(sort([])).toEqual([]);
});
it('sorts unsorted arrays', () => {
expect(sort([6, 2, 1])).toEqual([1, 2, 6]);
});
it('does not change a sorted array', () => {
expect(sort([1, 2, 3, 4])).toEqual([1, 2, 3, 4]);
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 15
Set of all possible inputs
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 16
Fuzz Tests
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 17
it('does not throw', () => {
const arr = arrayOfRandomNumbers();
expect(() => sort(arr)).not.toThrow();
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 18
Set of all possible inputs
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 19
it('sorts unsorted arrays', () => {
const arr = arrayOfRandomNumbers();
const sorted = // ??????
expect(sort(arr)).toEqual(sorted);
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 20
Property-Based Tests
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 21
it('keeps the same length', () => {
const arr = arrayOfRandomNumbers();
expect(sort(arr).length).toEqual(arr.length);
});
it('is idempotent', () => {
const arr = arrayOfRandomNumbers();
expect(sort(sort(arr))).toEqual(sort(arr));
});
it('every member is less than or equal to the next one', () => {
const arr = arrayOfRandomNumbers();
const sorted = sort(arr);
for (let i in sorted) {
const current = sorted[i];
const next = sorted[i + 1];
if (next !== undefined) {
expect(current).toBeLessThanOrEqual(next);
}
}
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 22
it('keeps the same length', () => {
const arr = arrayOfRandomNumbers();
expect(sort(arr).length).toEqual(arr.length);
});
it('is idempotent', () => {
const arr = arrayOfRandomNumbers();
expect(sort(sort(arr))).toEqual(sort(arr));
});
it('every member is less than or equal to the next one', () => {
const arr = arrayOfRandomNumbers();
const sorted = sort(arr);
for (let i in sorted) {
const current = sorted[i];
const next = sorted[i + 1];
if (next !== undefined) {
expect(current).toBeLessThanOrEqual(next);
}
}
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 23
it('keeps the same length', () => {
const arr = arrayOfRandomNumbers();
expect(sort(arr).length).toEqual(arr.length);
});
it('is idempotent', () => {
const arr = arrayOfRandomNumbers();
expect(sort(sort(arr))).toEqual(sort(arr));
});
it('every member is less than or equal to the next one', () => {
const arr = arrayOfRandomNumbers();
const sorted = sort(arr);
for (let i in sorted) {
const current = sorted[i];
const next = sorted[i + 1];
if (next !== undefined) {
expect(current).toBeLessThanOrEqual(next);
}
}
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 24
Set of all possible inputs
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 25
"Example-Based tests are very narrow
in their input and broad in their
expectations."
- Jessica Kerr
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 26
Quickcheck
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 27
Testcheck Testcheck.js
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 28
Deliberate randomness
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 29
arrayOfRandomNumbers();
// [1, 500, 6012], [45, 12, 579, 77, -8] ...
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 30
Generators
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 31
import { gen } from 'testcheck';
gen.string; // 'a1x', '', 'e4r' ...
gen.int;
gen.bool;
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 32
import { gen } from 'testcheck';
gen.string;
gen.int; // 0, -1, 3, 1 ...
gen.bool;
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 33
import { gen } from 'testcheck';
gen.string;
gen.int;
gen.bool; // true, true, false ...
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 34
import { gen } from 'testcheck';
const personGen = gen.object({
name: gen.alphaNumString,
age: gen.posInt,
}); // {name: 'er54', age: 2} ...
gen.array(gen.int);
const peopleGen = gen.array(personGen);
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 35
import { gen } from 'testcheck';
const personGen = gen.object({
name: gen.alphaNumString,
age: gen.posInt,
});
gen.array(gen.int); // [3], [] ...
const peopleGen = gen.array(personGen);
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 36
import { gen } from 'testcheck';
const personGen = gen.object({
name: gen.alphaNumString,
age: gen.posInt,
});
gen.array(gen.int);
const peopleGen = gen.array(personGen);
// [{name: 'e', age: 1}] ...
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 37
import { sample, gen } from 'testcheck';
sample(gen.array(gen.int), 20);
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 38
[
[],
[],
[2],
[-1],
[],
[5, 0],
[-6, -4, -5],
[-2, -7, -6],
[6, 7, -8, 7],
[],
[4, 2, 7],
[-5, -2],
[-10, -7, 0, 5],
[8, -7, 3, 5, 11, -4, 5, -1, -6, -12, 9],
[-6, 0, 4, 9, -2, 3, -4, -6, -6, 5, 3, 3],
[-13, -11, -6, -13],
[-16, 5, 0, 8, 16],
[-6, -17, 14],
[0, 13, -18, -15, -1, 0, 12, 1, 4, -15, 16, 5, -2, 6, -3],
[7, 19, -1, 1, 12, 18],
];
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 39
import { sample, gen } from 'testcheck';
const personGen = gen.object({
name: gen.alphaNumString,
age: gen.posInt,
});
sample(personGen, 20);
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 40
[
{ name: '', age: 0 },
{ name: 'D', age: 1 },
{ name: 'nn', age: 0 },
{ name: 'r8', age: 3 },
{ name: 'el1H', age: 0 },
{ name: 't04', age: 2 },
{ name: '47A15bF', age: 3 },
{ name: 'N', age: 2 },
{ name: 'DF18GqP', age: 7 },
{ name: 'iKuySuF4', age: 5 },
{ name: 'bU6', age: 0 },
{ name: '1Zd', age: 11 },
{ name: 'dR62wPY6K4jo', age: 5 },
{ name: 'c57Rv3y', age: 5 },
// ...
];
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 41
it('is idempotent', () => {
const arr = arrayOfRandomNumbers();
expect(sort(sort(arr))).toEqual(sort(arr));
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 42
import { gen } from 'testcheck';
check.it('is idempotent', gen.array(gen.int), arr => {
expect(sort(sort(arr))).toEqual(sort(arr));
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 43
import { gen } from 'testcheck';
check.it('is idempotent', gen.array(gen.int), arr => {
expect(sort(sort(arr))).toEqual(sort(arr));
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 44
import { gen } from 'testcheck';
check.it('keeps the same length', gen.array(gen.int), arr => {
expect(sort(arr).length).toEqual(arr.length);
});
check.it('is idempotent', gen.array(gen.int), arr => {
expect(sort(sort(arr))).toEqual(sort(arr));
});
check.it('every member is less than or equal to the next one',
gen.array(gen.int), arr => {
const sorted = sort(arr);
for (let i in sorted) {
const current = sorted[i];
const next = sorted[i + 1];
if (next !== undefined) {
expect(current).toBeLessThanOrEqual(next);
}
}
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 45
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 46
Shrinking
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 47
{
result: false,
'failing-size': 2,
'num-tests': 3,
fail: [-12, 1, -12, 4],
shrunk: {
'total-nodes-visited': 2,
depth: 1,
result: false,
smallest: [-2, -2]
}
};
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 48
Super useful!
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 49
Where does it fall
apart in JavaScript?
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 50
type Person = {
name: string,
age: number,
};
function savePerson(person: Person): Person
import { gen } from 'testcheck';
const personGen = gen.object({
name: gen.alphaNumString,
age: gen.posInt,
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 51
type Person = {
name: string,
age: number,
};
function savePerson(person: Person): Person
import { gen } from 'testcheck';
const personGen = gen.object({
name: gen.alphaNumString,
age: gen.posInt,
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 52
type Person = {
name: string,
age: number,
};
function savePerson(person: Person): Person
import { gen } from 'testcheck';
const personGen = gen.object({
name: gen.alphaNumString,
age: gen.posInt,
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 53
type ActivationRule = {
id: string,
schemaVersion: 10,
ubCode: string,
embUuid: string,
trigger: Trigger,
meta: RuleMeta,
urlTargets: UrlTargets,
displaySettings: DisplaySettings,
published: PublishedState,
frequency: Frequency,
cookieTargets: CookieTargets,
geoTargets: GeoTargets,
referrerTargets: ReferrerTargets,
event: Event,
version: string,
parentVersion ?: string,
dimensions: Dimensions,
integrations: Integrations
};
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 54
type ActivationRule = {
id: string,
schemaVersion: 10,
ubCode: string,
embUuid: string,
trigger: Trigger,
meta: RuleMeta,
urlTargets: UrlTargets,
displaySettings: DisplaySettings,
published: PublishedState,
frequency: Frequency,
cookieTargets: CookieTargets,
geoTargets: GeoTargets,
referrerTargets: ReferrerTargets,
event: Event,
version: string,
parentVersion ?: string,
dimensions: Dimensions,
integrations: Integrations
};
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 55
type ActivationRule = {
id: string,
schemaVersion: 10,
ubCode: string,
embUuid: string,
trigger: Trigger,
meta: RuleMeta,
urlTargets: UrlTargets,
displaySettings: DisplaySettings,
published: PublishedState,
frequency: Frequency,
cookieTargets: CookieTargets,
geoTargets: GeoTargets,
referrerTargets: ReferrerTargets,
event: Event,
version: string,
parentVersion ?: string,
dimensions: Dimensions,
integrations: Integrations
};
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 56
type TriggerName =
'exit' |
'welcome' |
'timed' |
'scroll' |
'scrollUp' |
'click';
type $Trigger<N: TriggerName, PN, PV> = {
name: $Subtype<N>,
parameters: [{
name: $Subtype<PN>,
value: $Subtype<PV>
}]
};
type Trigger =
$Trigger<'exit', 'exit' | 'topMargin', '20px'> |
$Trigger<'welcome', 'delay', '0'> |
$Trigger<'timed', 'delay', string> |
$Trigger<'scroll', 'scrollPercent', string> |
$Trigger<'scrollUp', 'scrollUp', ''> |
$Trigger<'click', 'clickId' | 'clickClass' | 'clickSelector', string>;
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 57
type TriggerName =
'exit' |
'welcome' |
'timed' |
'scroll' |
'scrollUp' |
'click';
type $Trigger<N: TriggerName, PN, PV> = {
name: $Subtype<N>,
parameters: [{
name: $Subtype<PN>,
value: $Subtype<PV>
}]
};
type Trigger =
$Trigger<'exit', 'exit' | 'topMargin', '20px'> |
$Trigger<'welcome', 'delay', '0'> |
$Trigger<'timed', 'delay', string> |
$Trigger<'scroll', 'scrollPercent', string> |
$Trigger<'scrollUp', 'scrollUp', ''> |
$Trigger<'click', 'clickId' | 'clickClass' | 'clickSelector', string>;
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 58
type TriggerName =
'exit' |
'welcome' |
'timed' |
'scroll' |
'scrollUp' |
'click';
type $Trigger<N: TriggerName, PN, PV> = {
name: $Subtype<N>,
parameters: [{
name: $Subtype<PN>,
value: $Subtype<PV>
}]
};
type Trigger =
$Trigger<'exit', 'exit' | 'topMargin', '20px'> |
$Trigger<'welcome', 'delay', '0'> |
$Trigger<'timed', 'delay', string> |
$Trigger<'scroll', 'scrollPercent', string> |
$Trigger<'scrollUp', 'scrollUp', ''> |
$Trigger<'click', 'clickId' | 'clickClass' | 'clickSelector', string>;
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 59
type TriggerName =
'exit' |
'welcome' |
'timed' |
'scroll' |
'scrollUp' |
'click';
type $Trigger<N: TriggerName, PN, PV> = {
name: $Subtype<N>,
parameters: [{
name: $Subtype<PN>,
value: $Subtype<PV>
}]
};
type Trigger =
$Trigger<'exit', 'exit' | 'topMargin', '20px'> |
$Trigger<'welcome', 'delay', '0'> |
$Trigger<'timed', 'delay', string> |
$Trigger<'scroll', 'scrollPercent', string> |
$Trigger<'scrollUp', 'scrollUp', ''> |
$Trigger<'click', 'clickId' | 'clickClass' | 'clickSelector', string>;
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 60
import type {ActivationRule as ActivationRuleV0} from './v00';
import type {ActivationRule as ActivationRuleV1} from './v01';
import type {ActivationRule as ActivationRuleV2} from './v02';
import type {ActivationRule as ActivationRuleV3} from './v03';
import type {ActivationRule as ActivationRuleV4} from './v04';
import type {ActivationRule as ActivationRuleV5} from './v05';
import type {ActivationRule as ActivationRuleV6} from './v06';
import type {ActivationRule as ActivationRuleV7} from './v07';
import type {ActivationRule as ActivationRuleV8} from './v08';
import type {ActivationRule as ActivationRuleV9} from './v09';
import type {ActivationRule as ActivationRuleV10} from './v10';
type AnyActivationRule =
ActivationRuleV10 |
ActivationRuleV9 |
ActivationRuleV8 |
ActivationRuleV7 |
ActivationRuleV6 |
ActivationRuleV5 |
ActivationRuleV4 |
ActivationRuleV3 |
ActivationRuleV2 |
ActivationRuleV1 |
ActivationRuleV0;
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
const upgradeActivationRule =
(version: number, rule: AnyActivationRule): AnyActivationRule
61
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 62
it('is backwards compatible', () => {
const activationRule = createActivationRule();
const { schemaVersion } = activationRule;
const upgradedRule = upgradeActivationRule(
MAX_SCHEMA_VERSION,
activationRule
);
const originalRule = upgradeActivationRule(
schemaVersion,
upgradedRule
);
expect(originalRule).toEqual(activationRule);
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 63
it('is backwards compatible', () => {
const activationRule = createActivationRule();
const { schemaVersion } = activationRule;
const upgradedRule = upgradeActivationRule(
MAX_SCHEMA_VERSION,
activationRule
);
const originalRule = upgradeActivationRule(
schemaVersion,
upgradedRule
);
expect(originalRule).toEqual(activationRule);
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 64
it('is backwards compatible', () => {
const activationRule = createActivationRule();
const { schemaVersion } = activationRule;
const upgradedRule = upgradeActivationRule(
MAX_SCHEMA_VERSION,
activationRule
);
const originalRule = upgradeActivationRule(
schemaVersion,
upgradedRule
);
expect(originalRule).toEqual(activationRule);
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 65
it('is backwards compatible', () => {
const activationRule = createActivationRule();
const { schemaVersion } = activationRule;
const upgradedRule = upgradeActivationRule(
MAX_SCHEMA_VERSION,
activationRule
);
const originalRule = upgradeActivationRule(
schemaVersion,
upgradedRule
);
expect(originalRule).toEqual(activationRule);
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 66
const activationRule10Gen = gen.object({
id: gen.string,
schemaVersion: gen.return(10),
ubCode: gen.string,
embUuid: gen.string,
trigger: gen.oneOf([
gen.object({
name: gen.return('welcome'),
parameters: gen.object({
name: gen.return('delay'),
value: gen.number
})
}),
// ...
]),
// ...
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 67
const activationRule10Gen = gen.object({
id: gen.string,
schemaVersion: gen.return(10),
ubCode: gen.string,
embUuid: gen.string,
trigger: gen.oneOf([
gen.object({
name: gen.return('welcome'),
parameters: gen.object({
name: gen.return('delay'),
value: gen.number
})
}),
// ...
]),
// ...
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 68
const activationRule10Gen = gen.object({
id: gen.string,
schemaVersion: gen.return(10),
ubCode: gen.string,
embUuid: gen.string,
trigger: gen.oneOf([
gen.object({
name: gen.return('welcome'),
parameters: gen.object({
name: gen.return('delay'),
value: gen.number
})
}),
// ...
]),
// ...
});
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 69
yarn add babel-plugin-transform-flow-to-gen --dev
(https: //github.com/unbounce/babel-plugin-transform-flow-to-gen)
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 70
// .babelrc
{
"presets": ["es-2015"],
"plugins": ["syntax-flow"],
"env": {
"development": {
"plugins": ["strip-flow-types"]
},
"test": {
"plugins": ["flow-to-gen", "strip-flow-types"]
}
}
}
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 71
import { AnyActivationRule } from './types';
const activationRuleGen = AnyActivationRule.toGen();
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
import { AnyActivationRule, MAX_SCHEMA_VERSION } from './types';
check.it('is backwards compatible',
AnyActivationRule.toGen(),
(activationRule) => {
const { schemaVersion } = activationRule;
const upgradedRule = upgradeActivationRule(
MAX_SCHEMA_VERSION,
activationRule
);
const originalRule = upgradeActivationRule(
schemaVersion,
upgradedRule
);
expect(originalRule).toEqual(activationRule);
}
);
72
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
import { AnyActivationRule, MAX_SCHEMA_VERSION } from './types';
check.it('is backwards compatible',
AnyActivationRule.toGen(),
(activationRule) => {
const { schemaVersion } = activationRule;
const upgradedRule = upgradeActivationRule(
MAX_SCHEMA_VERSION,
activationRule
);
const originalRule = upgradeActivationRule(
schemaVersion,
upgradedRule
);
expect(originalRule).toEqual(activationRule);
}
);
73
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 74
Generating generators
generates success
😀
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 75
Locks down
type definitions
😀
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
function someFunc(arr: string[]): string
function someFunc(arr: [string, string, string]): string
76
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
function someFunc(arr: string[]): string
function someFunc(arr: [string, string, string]): string
77
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 78
Fuzz Testing
😀
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
type State = {
// ...
};
type Action = {type: 'DO_A'} | {type: 'DO_B'};
function reducer(state: State, action: Action): Action;
createStore<State>(reducer, initialState);
79
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
check.it('is idempotent', sort.toGen(), (args) => {
expect(sort(sort( ...args))).toEqual(sort( ...args));
});
80
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 81
Can't type check test
code and non-standard
☹
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 82
Very, very, very slow
☹
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 83
No silver bullet
☹
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 84
TS and Flow can now
force declarations to sync
☹😀
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 85
Property-Based Testing
finds flaws in your code.
Lets make it easy to do in
JavaScript.
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 86
Thank you!
🎉
📬 reactive@gabe.pizza
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 87
References:
https: //fsharpforfunandprofit.com/posts/property-based-testing/
https: //fsharpforfunandprofit.com/posts/property-based-testing-2/
https: // www.youtube.com/watch?v=shngiiBfD80
http: //propertesting.com/
https: //github.com/clojure/test.check
http: //leebyron.com/testcheck-js/
Background Image:
https: // www.toptal.com/designers/subtlepatterns/memphis-colorful/
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
import uuid from 'uuid/v4';
import type { $Gen } from 'babel-plugin-transform-flow-to-gen/Gen';
// type $Gen<T, _U> = T;
type User = {
id: $Gen<string, uuid>;
name: string;
}
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
// use higher order type in JS
const janitorGen = Worker.toGen(gen.object({type: 'janitor'}));
// OR create type in Flow
type Janitor = Worker<{type: 'janitor'}>;
const janitorGen = Janitor.toGen();
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
import { generator } from 'my-lib';
import { AnyActivationRule } from './types';
const activationRuleGen =
generator<AnyActivationRule>();
// function generator<T>(): T;
Property-Based Testing for Godly Tests - Gabe Scholz @unbounce
const arr = arrayOfRandomNumbers();
const sorted1 = sort(arr.concat(-Number.MIN_VALUE));
const sorted2 = [-Number.MIN_VALUE].concat(sort(arr));
expect(sorted1).toEqual(sorted2);

More Related Content

Similar to Property-Based Testing for Godly Tests

An introduction to property-based testing
An introduction to property-based testingAn introduction to property-based testing
An introduction to property-based testing
Vincent Pradeilles
 
Front end fundamentals session 1: javascript core
Front end fundamentals session 1: javascript coreFront end fundamentals session 1: javascript core
Front end fundamentals session 1: javascript core
Web Zhao
 

Similar to Property-Based Testing for Godly Tests (20)

Jasmine - why JS tests don't smell fishy
Jasmine - why JS tests don't smell fishyJasmine - why JS tests don't smell fishy
Jasmine - why JS tests don't smell fishy
 
An introduction to property-based testing
An introduction to property-based testingAn introduction to property-based testing
An introduction to property-based testing
 
Strings and Characters
Strings and CharactersStrings and Characters
Strings and Characters
 
Bad test, good test
Bad test, good testBad test, good test
Bad test, good test
 
An introduction to Google test framework
An introduction to Google test frameworkAn introduction to Google test framework
An introduction to Google test framework
 
Test driven node.js
Test driven node.jsTest driven node.js
Test driven node.js
 
Practical JavaScript Programming - Session 6/8
Practical JavaScript Programming - Session 6/8Practical JavaScript Programming - Session 6/8
Practical JavaScript Programming - Session 6/8
 
The Future of JVM Languages
The Future of JVM Languages The Future of JVM Languages
The Future of JVM Languages
 
NoSQL для PostgreSQL: Jsquery — язык запросов
NoSQL для PostgreSQL: Jsquery — язык запросовNoSQL для PostgreSQL: Jsquery — язык запросов
NoSQL для PostgreSQL: Jsquery — язык запросов
 
JavaScript Beginner Tutorial | WeiYuan
JavaScript Beginner Tutorial | WeiYuanJavaScript Beginner Tutorial | WeiYuan
JavaScript Beginner Tutorial | WeiYuan
 
quick json parser
quick json parserquick json parser
quick json parser
 
Arrays string handling java packages
Arrays string handling java packagesArrays string handling java packages
Arrays string handling java packages
 
Scala == Effective Java
Scala == Effective JavaScala == Effective Java
Scala == Effective Java
 
javascript function & closure
javascript function & closurejavascript function & closure
javascript function & closure
 
Java string handling
Java string handlingJava string handling
Java string handling
 
Front end fundamentals session 1: javascript core
Front end fundamentals session 1: javascript coreFront end fundamentals session 1: javascript core
Front end fundamentals session 1: javascript core
 
Tests unitaires pour PostgreSQL avec pgTap
Tests unitaires pour PostgreSQL avec pgTapTests unitaires pour PostgreSQL avec pgTap
Tests unitaires pour PostgreSQL avec pgTap
 
11.ppt
11.ppt11.ppt
11.ppt
 
strings.ppt
strings.pptstrings.ppt
strings.ppt
 
The Future of JavaScript (SXSW '07)
The Future of JavaScript (SXSW '07)The Future of JavaScript (SXSW '07)
The Future of JavaScript (SXSW '07)
 

Recently uploaded

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
 
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
 
%+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
 
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
 

Recently uploaded (20)

Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
Devoxx UK 2024 - Going serverless with Quarkus, GraalVM native images and AWS...
 
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
%in Stilfontein+277-882-255-28 abortion pills for sale in Stilfontein
 
tonesoftg
tonesoftgtonesoftg
tonesoftg
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
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
 
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
Crypto Cloud Review - How To Earn Up To $500 Per DAY Of Bitcoin 100% On AutoP...
 
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...
 
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
 
AI & Machine Learning Presentation Template
AI & Machine Learning Presentation TemplateAI & Machine Learning Presentation Template
AI & Machine Learning Presentation Template
 
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 🔝✔️✔️
 
%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto%in Soweto+277-882-255-28 abortion pills for sale in soweto
%in Soweto+277-882-255-28 abortion pills for sale in soweto
 
WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?WSO2CON 2024 - Does Open Source Still Matter?
WSO2CON 2024 - Does Open Source Still Matter?
 
%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
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
%+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...
 
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
 
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
MarTech Trend 2024 Book : Marketing Technology Trends (2024 Edition) How Data...
 
WSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go PlatformlessWSO2CON2024 - It's time to go Platformless
WSO2CON2024 - It's time to go Platformless
 
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With SimplicityWSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
WSO2Con2024 - Enabling Transactional System's Exponential Growth With Simplicity
 
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
Direct Style Effect Systems -The Print[A] Example- A Comprehension AidDirect Style Effect Systems -The Print[A] Example- A Comprehension Aid
Direct Style Effect Systems - The Print[A] Example - A Comprehension Aid
 

Property-Based Testing for Godly Tests

  • 1. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce !
  • 2. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce !
  • 3. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 3 ! Gabe from Vancouver 🐙 @garbles 📬 reactive@gabe.pizza
  • 4. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce careers.unbounce.com 4
  • 5. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce Property-Based Testing for Godly Tests 5
  • 6. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 6 Why do we write tests?
  • 7. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 7 "Program testing can be a very effective way to show the presence of bugs, but it is hopelessly inadequate for showing their absence." - Edsger Dijkstra
  • 8. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 8 Tests prevent regressions!
  • 9. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 9 Tests prevent regressions! (that you know about)
  • 10. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 10 Example-Based Tests
  • 11. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 11 function sort(arr: number[]): number[] it('does nothing to empty arrays', () => { expect(sort([])).toEqual([]); }); it('sorts unsorted arrays', () => { expect(sort([6, 2, 1])).toEqual([1, 2, 6]); }); it('does not change a sorted array', () => { expect(sort([1, 2, 3, 4])).toEqual([1, 2, 3, 4]); });
  • 12. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 12 function sort(arr: number[]): number[] it('does nothing to empty arrays', () => { expect(sort([])).toEqual([]); }); it('sorts unsorted arrays', () => { expect(sort([6, 2, 1])).toEqual([1, 2, 6]); }); it('does not change a sorted array', () => { expect(sort([1, 2, 3, 4])).toEqual([1, 2, 3, 4]); });
  • 13. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 13 function sort(arr: number[]): number[] it('does nothing to empty arrays', () => { expect(sort([])).toEqual([]); }); it('sorts unsorted arrays', () => { expect(sort([6, 2, 1])).toEqual([1, 2, 6]); }); it('does not change a sorted array', () => { expect(sort([1, 2, 3, 4])).toEqual([1, 2, 3, 4]); });
  • 14. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 14 function sort(arr: number[]): number[] it('does nothing to empty arrays', () => { expect(sort([])).toEqual([]); }); it('sorts unsorted arrays', () => { expect(sort([6, 2, 1])).toEqual([1, 2, 6]); }); it('does not change a sorted array', () => { expect(sort([1, 2, 3, 4])).toEqual([1, 2, 3, 4]); });
  • 15. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 15 Set of all possible inputs
  • 16. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 16 Fuzz Tests
  • 17. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 17 it('does not throw', () => { const arr = arrayOfRandomNumbers(); expect(() => sort(arr)).not.toThrow(); });
  • 18. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 18 Set of all possible inputs
  • 19. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 19 it('sorts unsorted arrays', () => { const arr = arrayOfRandomNumbers(); const sorted = // ?????? expect(sort(arr)).toEqual(sorted); });
  • 20. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 20 Property-Based Tests
  • 21. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 21 it('keeps the same length', () => { const arr = arrayOfRandomNumbers(); expect(sort(arr).length).toEqual(arr.length); }); it('is idempotent', () => { const arr = arrayOfRandomNumbers(); expect(sort(sort(arr))).toEqual(sort(arr)); }); it('every member is less than or equal to the next one', () => { const arr = arrayOfRandomNumbers(); const sorted = sort(arr); for (let i in sorted) { const current = sorted[i]; const next = sorted[i + 1]; if (next !== undefined) { expect(current).toBeLessThanOrEqual(next); } } });
  • 22. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 22 it('keeps the same length', () => { const arr = arrayOfRandomNumbers(); expect(sort(arr).length).toEqual(arr.length); }); it('is idempotent', () => { const arr = arrayOfRandomNumbers(); expect(sort(sort(arr))).toEqual(sort(arr)); }); it('every member is less than or equal to the next one', () => { const arr = arrayOfRandomNumbers(); const sorted = sort(arr); for (let i in sorted) { const current = sorted[i]; const next = sorted[i + 1]; if (next !== undefined) { expect(current).toBeLessThanOrEqual(next); } } });
  • 23. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 23 it('keeps the same length', () => { const arr = arrayOfRandomNumbers(); expect(sort(arr).length).toEqual(arr.length); }); it('is idempotent', () => { const arr = arrayOfRandomNumbers(); expect(sort(sort(arr))).toEqual(sort(arr)); }); it('every member is less than or equal to the next one', () => { const arr = arrayOfRandomNumbers(); const sorted = sort(arr); for (let i in sorted) { const current = sorted[i]; const next = sorted[i + 1]; if (next !== undefined) { expect(current).toBeLessThanOrEqual(next); } } });
  • 24. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 24 Set of all possible inputs
  • 25. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 25 "Example-Based tests are very narrow in their input and broad in their expectations." - Jessica Kerr
  • 26. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 26 Quickcheck
  • 27. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 27 Testcheck Testcheck.js
  • 28. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 28 Deliberate randomness
  • 29. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 29 arrayOfRandomNumbers(); // [1, 500, 6012], [45, 12, 579, 77, -8] ...
  • 30. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 30 Generators
  • 31. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 31 import { gen } from 'testcheck'; gen.string; // 'a1x', '', 'e4r' ... gen.int; gen.bool;
  • 32. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 32 import { gen } from 'testcheck'; gen.string; gen.int; // 0, -1, 3, 1 ... gen.bool;
  • 33. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 33 import { gen } from 'testcheck'; gen.string; gen.int; gen.bool; // true, true, false ...
  • 34. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 34 import { gen } from 'testcheck'; const personGen = gen.object({ name: gen.alphaNumString, age: gen.posInt, }); // {name: 'er54', age: 2} ... gen.array(gen.int); const peopleGen = gen.array(personGen);
  • 35. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 35 import { gen } from 'testcheck'; const personGen = gen.object({ name: gen.alphaNumString, age: gen.posInt, }); gen.array(gen.int); // [3], [] ... const peopleGen = gen.array(personGen);
  • 36. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 36 import { gen } from 'testcheck'; const personGen = gen.object({ name: gen.alphaNumString, age: gen.posInt, }); gen.array(gen.int); const peopleGen = gen.array(personGen); // [{name: 'e', age: 1}] ...
  • 37. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 37 import { sample, gen } from 'testcheck'; sample(gen.array(gen.int), 20);
  • 38. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 38 [ [], [], [2], [-1], [], [5, 0], [-6, -4, -5], [-2, -7, -6], [6, 7, -8, 7], [], [4, 2, 7], [-5, -2], [-10, -7, 0, 5], [8, -7, 3, 5, 11, -4, 5, -1, -6, -12, 9], [-6, 0, 4, 9, -2, 3, -4, -6, -6, 5, 3, 3], [-13, -11, -6, -13], [-16, 5, 0, 8, 16], [-6, -17, 14], [0, 13, -18, -15, -1, 0, 12, 1, 4, -15, 16, 5, -2, 6, -3], [7, 19, -1, 1, 12, 18], ];
  • 39. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 39 import { sample, gen } from 'testcheck'; const personGen = gen.object({ name: gen.alphaNumString, age: gen.posInt, }); sample(personGen, 20);
  • 40. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 40 [ { name: '', age: 0 }, { name: 'D', age: 1 }, { name: 'nn', age: 0 }, { name: 'r8', age: 3 }, { name: 'el1H', age: 0 }, { name: 't04', age: 2 }, { name: '47A15bF', age: 3 }, { name: 'N', age: 2 }, { name: 'DF18GqP', age: 7 }, { name: 'iKuySuF4', age: 5 }, { name: 'bU6', age: 0 }, { name: '1Zd', age: 11 }, { name: 'dR62wPY6K4jo', age: 5 }, { name: 'c57Rv3y', age: 5 }, // ... ];
  • 41. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 41 it('is idempotent', () => { const arr = arrayOfRandomNumbers(); expect(sort(sort(arr))).toEqual(sort(arr)); });
  • 42. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 42 import { gen } from 'testcheck'; check.it('is idempotent', gen.array(gen.int), arr => { expect(sort(sort(arr))).toEqual(sort(arr)); });
  • 43. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 43 import { gen } from 'testcheck'; check.it('is idempotent', gen.array(gen.int), arr => { expect(sort(sort(arr))).toEqual(sort(arr)); });
  • 44. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 44 import { gen } from 'testcheck'; check.it('keeps the same length', gen.array(gen.int), arr => { expect(sort(arr).length).toEqual(arr.length); }); check.it('is idempotent', gen.array(gen.int), arr => { expect(sort(sort(arr))).toEqual(sort(arr)); }); check.it('every member is less than or equal to the next one', gen.array(gen.int), arr => { const sorted = sort(arr); for (let i in sorted) { const current = sorted[i]; const next = sorted[i + 1]; if (next !== undefined) { expect(current).toBeLessThanOrEqual(next); } } });
  • 45. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 45
  • 46. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 46 Shrinking
  • 47. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 47 { result: false, 'failing-size': 2, 'num-tests': 3, fail: [-12, 1, -12, 4], shrunk: { 'total-nodes-visited': 2, depth: 1, result: false, smallest: [-2, -2] } };
  • 48. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 48 Super useful!
  • 49. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 49 Where does it fall apart in JavaScript?
  • 50. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 50 type Person = { name: string, age: number, }; function savePerson(person: Person): Person import { gen } from 'testcheck'; const personGen = gen.object({ name: gen.alphaNumString, age: gen.posInt, });
  • 51. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 51 type Person = { name: string, age: number, }; function savePerson(person: Person): Person import { gen } from 'testcheck'; const personGen = gen.object({ name: gen.alphaNumString, age: gen.posInt, });
  • 52. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 52 type Person = { name: string, age: number, }; function savePerson(person: Person): Person import { gen } from 'testcheck'; const personGen = gen.object({ name: gen.alphaNumString, age: gen.posInt, });
  • 53. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 53 type ActivationRule = { id: string, schemaVersion: 10, ubCode: string, embUuid: string, trigger: Trigger, meta: RuleMeta, urlTargets: UrlTargets, displaySettings: DisplaySettings, published: PublishedState, frequency: Frequency, cookieTargets: CookieTargets, geoTargets: GeoTargets, referrerTargets: ReferrerTargets, event: Event, version: string, parentVersion ?: string, dimensions: Dimensions, integrations: Integrations };
  • 54. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 54 type ActivationRule = { id: string, schemaVersion: 10, ubCode: string, embUuid: string, trigger: Trigger, meta: RuleMeta, urlTargets: UrlTargets, displaySettings: DisplaySettings, published: PublishedState, frequency: Frequency, cookieTargets: CookieTargets, geoTargets: GeoTargets, referrerTargets: ReferrerTargets, event: Event, version: string, parentVersion ?: string, dimensions: Dimensions, integrations: Integrations };
  • 55. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 55 type ActivationRule = { id: string, schemaVersion: 10, ubCode: string, embUuid: string, trigger: Trigger, meta: RuleMeta, urlTargets: UrlTargets, displaySettings: DisplaySettings, published: PublishedState, frequency: Frequency, cookieTargets: CookieTargets, geoTargets: GeoTargets, referrerTargets: ReferrerTargets, event: Event, version: string, parentVersion ?: string, dimensions: Dimensions, integrations: Integrations };
  • 56. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 56 type TriggerName = 'exit' | 'welcome' | 'timed' | 'scroll' | 'scrollUp' | 'click'; type $Trigger<N: TriggerName, PN, PV> = { name: $Subtype<N>, parameters: [{ name: $Subtype<PN>, value: $Subtype<PV> }] }; type Trigger = $Trigger<'exit', 'exit' | 'topMargin', '20px'> | $Trigger<'welcome', 'delay', '0'> | $Trigger<'timed', 'delay', string> | $Trigger<'scroll', 'scrollPercent', string> | $Trigger<'scrollUp', 'scrollUp', ''> | $Trigger<'click', 'clickId' | 'clickClass' | 'clickSelector', string>;
  • 57. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 57 type TriggerName = 'exit' | 'welcome' | 'timed' | 'scroll' | 'scrollUp' | 'click'; type $Trigger<N: TriggerName, PN, PV> = { name: $Subtype<N>, parameters: [{ name: $Subtype<PN>, value: $Subtype<PV> }] }; type Trigger = $Trigger<'exit', 'exit' | 'topMargin', '20px'> | $Trigger<'welcome', 'delay', '0'> | $Trigger<'timed', 'delay', string> | $Trigger<'scroll', 'scrollPercent', string> | $Trigger<'scrollUp', 'scrollUp', ''> | $Trigger<'click', 'clickId' | 'clickClass' | 'clickSelector', string>;
  • 58. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 58 type TriggerName = 'exit' | 'welcome' | 'timed' | 'scroll' | 'scrollUp' | 'click'; type $Trigger<N: TriggerName, PN, PV> = { name: $Subtype<N>, parameters: [{ name: $Subtype<PN>, value: $Subtype<PV> }] }; type Trigger = $Trigger<'exit', 'exit' | 'topMargin', '20px'> | $Trigger<'welcome', 'delay', '0'> | $Trigger<'timed', 'delay', string> | $Trigger<'scroll', 'scrollPercent', string> | $Trigger<'scrollUp', 'scrollUp', ''> | $Trigger<'click', 'clickId' | 'clickClass' | 'clickSelector', string>;
  • 59. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 59 type TriggerName = 'exit' | 'welcome' | 'timed' | 'scroll' | 'scrollUp' | 'click'; type $Trigger<N: TriggerName, PN, PV> = { name: $Subtype<N>, parameters: [{ name: $Subtype<PN>, value: $Subtype<PV> }] }; type Trigger = $Trigger<'exit', 'exit' | 'topMargin', '20px'> | $Trigger<'welcome', 'delay', '0'> | $Trigger<'timed', 'delay', string> | $Trigger<'scroll', 'scrollPercent', string> | $Trigger<'scrollUp', 'scrollUp', ''> | $Trigger<'click', 'clickId' | 'clickClass' | 'clickSelector', string>;
  • 60. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 60 import type {ActivationRule as ActivationRuleV0} from './v00'; import type {ActivationRule as ActivationRuleV1} from './v01'; import type {ActivationRule as ActivationRuleV2} from './v02'; import type {ActivationRule as ActivationRuleV3} from './v03'; import type {ActivationRule as ActivationRuleV4} from './v04'; import type {ActivationRule as ActivationRuleV5} from './v05'; import type {ActivationRule as ActivationRuleV6} from './v06'; import type {ActivationRule as ActivationRuleV7} from './v07'; import type {ActivationRule as ActivationRuleV8} from './v08'; import type {ActivationRule as ActivationRuleV9} from './v09'; import type {ActivationRule as ActivationRuleV10} from './v10'; type AnyActivationRule = ActivationRuleV10 | ActivationRuleV9 | ActivationRuleV8 | ActivationRuleV7 | ActivationRuleV6 | ActivationRuleV5 | ActivationRuleV4 | ActivationRuleV3 | ActivationRuleV2 | ActivationRuleV1 | ActivationRuleV0;
  • 61. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce const upgradeActivationRule = (version: number, rule: AnyActivationRule): AnyActivationRule 61
  • 62. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 62 it('is backwards compatible', () => { const activationRule = createActivationRule(); const { schemaVersion } = activationRule; const upgradedRule = upgradeActivationRule( MAX_SCHEMA_VERSION, activationRule ); const originalRule = upgradeActivationRule( schemaVersion, upgradedRule ); expect(originalRule).toEqual(activationRule); });
  • 63. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 63 it('is backwards compatible', () => { const activationRule = createActivationRule(); const { schemaVersion } = activationRule; const upgradedRule = upgradeActivationRule( MAX_SCHEMA_VERSION, activationRule ); const originalRule = upgradeActivationRule( schemaVersion, upgradedRule ); expect(originalRule).toEqual(activationRule); });
  • 64. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 64 it('is backwards compatible', () => { const activationRule = createActivationRule(); const { schemaVersion } = activationRule; const upgradedRule = upgradeActivationRule( MAX_SCHEMA_VERSION, activationRule ); const originalRule = upgradeActivationRule( schemaVersion, upgradedRule ); expect(originalRule).toEqual(activationRule); });
  • 65. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 65 it('is backwards compatible', () => { const activationRule = createActivationRule(); const { schemaVersion } = activationRule; const upgradedRule = upgradeActivationRule( MAX_SCHEMA_VERSION, activationRule ); const originalRule = upgradeActivationRule( schemaVersion, upgradedRule ); expect(originalRule).toEqual(activationRule); });
  • 66. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 66 const activationRule10Gen = gen.object({ id: gen.string, schemaVersion: gen.return(10), ubCode: gen.string, embUuid: gen.string, trigger: gen.oneOf([ gen.object({ name: gen.return('welcome'), parameters: gen.object({ name: gen.return('delay'), value: gen.number }) }), // ... ]), // ... });
  • 67. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 67 const activationRule10Gen = gen.object({ id: gen.string, schemaVersion: gen.return(10), ubCode: gen.string, embUuid: gen.string, trigger: gen.oneOf([ gen.object({ name: gen.return('welcome'), parameters: gen.object({ name: gen.return('delay'), value: gen.number }) }), // ... ]), // ... });
  • 68. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 68 const activationRule10Gen = gen.object({ id: gen.string, schemaVersion: gen.return(10), ubCode: gen.string, embUuid: gen.string, trigger: gen.oneOf([ gen.object({ name: gen.return('welcome'), parameters: gen.object({ name: gen.return('delay'), value: gen.number }) }), // ... ]), // ... });
  • 69. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 69 yarn add babel-plugin-transform-flow-to-gen --dev (https: //github.com/unbounce/babel-plugin-transform-flow-to-gen)
  • 70. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 70 // .babelrc { "presets": ["es-2015"], "plugins": ["syntax-flow"], "env": { "development": { "plugins": ["strip-flow-types"] }, "test": { "plugins": ["flow-to-gen", "strip-flow-types"] } } }
  • 71. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 71 import { AnyActivationRule } from './types'; const activationRuleGen = AnyActivationRule.toGen();
  • 72. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce import { AnyActivationRule, MAX_SCHEMA_VERSION } from './types'; check.it('is backwards compatible', AnyActivationRule.toGen(), (activationRule) => { const { schemaVersion } = activationRule; const upgradedRule = upgradeActivationRule( MAX_SCHEMA_VERSION, activationRule ); const originalRule = upgradeActivationRule( schemaVersion, upgradedRule ); expect(originalRule).toEqual(activationRule); } ); 72
  • 73. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce import { AnyActivationRule, MAX_SCHEMA_VERSION } from './types'; check.it('is backwards compatible', AnyActivationRule.toGen(), (activationRule) => { const { schemaVersion } = activationRule; const upgradedRule = upgradeActivationRule( MAX_SCHEMA_VERSION, activationRule ); const originalRule = upgradeActivationRule( schemaVersion, upgradedRule ); expect(originalRule).toEqual(activationRule); } ); 73
  • 74. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 74 Generating generators generates success 😀
  • 75. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 75 Locks down type definitions 😀
  • 76. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce function someFunc(arr: string[]): string function someFunc(arr: [string, string, string]): string 76
  • 77. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce function someFunc(arr: string[]): string function someFunc(arr: [string, string, string]): string 77
  • 78. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 78 Fuzz Testing 😀
  • 79. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce type State = { // ... }; type Action = {type: 'DO_A'} | {type: 'DO_B'}; function reducer(state: State, action: Action): Action; createStore<State>(reducer, initialState); 79
  • 80. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce check.it('is idempotent', sort.toGen(), (args) => { expect(sort(sort( ...args))).toEqual(sort( ...args)); }); 80
  • 81. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 81 Can't type check test code and non-standard ☹
  • 82. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 82 Very, very, very slow ☹
  • 83. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 83 No silver bullet ☹
  • 84. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 84 TS and Flow can now force declarations to sync ☹😀
  • 85. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 85 Property-Based Testing finds flaws in your code. Lets make it easy to do in JavaScript.
  • 86. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 86 Thank you! 🎉 📬 reactive@gabe.pizza
  • 87. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce 87 References: https: //fsharpforfunandprofit.com/posts/property-based-testing/ https: //fsharpforfunandprofit.com/posts/property-based-testing-2/ https: // www.youtube.com/watch?v=shngiiBfD80 http: //propertesting.com/ https: //github.com/clojure/test.check http: //leebyron.com/testcheck-js/ Background Image: https: // www.toptal.com/designers/subtlepatterns/memphis-colorful/
  • 88. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce import uuid from 'uuid/v4'; import type { $Gen } from 'babel-plugin-transform-flow-to-gen/Gen'; // type $Gen<T, _U> = T; type User = { id: $Gen<string, uuid>; name: string; }
  • 89. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce // use higher order type in JS const janitorGen = Worker.toGen(gen.object({type: 'janitor'})); // OR create type in Flow type Janitor = Worker<{type: 'janitor'}>; const janitorGen = Janitor.toGen();
  • 90. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce import { generator } from 'my-lib'; import { AnyActivationRule } from './types'; const activationRuleGen = generator<AnyActivationRule>(); // function generator<T>(): T;
  • 91. Property-Based Testing for Godly Tests - Gabe Scholz @unbounce const arr = arrayOfRandomNumbers(); const sorted1 = sort(arr.concat(-Number.MIN_VALUE)); const sorted2 = [-Number.MIN_VALUE].concat(sort(arr)); expect(sorted1).toEqual(sorted2);