SlideShare a Scribd company logo
UNIT TESTING DATAUNIT TESTING DATA
WITH MARBLESWITH MARBLES
JANE ADAMS & LEIF WALSHJANE ADAMS & LEIF WALSH
ONCE UPON A TIME...ONCE UPON A TIME...
ONCE UPON A TIME...ONCE UPON A TIME...
CHILDREN WERE OLDER THAN THEIR PARENTS.CHILDREN WERE OLDER THAN THEIR PARENTS.
ONCE UPON A TIME...ONCE UPON A TIME...
CHILDREN WERE OLDER THAN THEIR PARENTS.CHILDREN WERE OLDER THAN THEIR PARENTS.
THEY WERETHEY WERE ALOTALOTOLDER THAN THEIR PARENTS.OLDER THAN THEIR PARENTS.
ONCE UPON A TIME...ONCE UPON A TIME...
CHILDREN WERE OLDER THAN THEIR PARENTS.CHILDREN WERE OLDER THAN THEIR PARENTS.
THEY WERETHEY WERE ALOTALOTOLDER THAN THEIR PARENTS.OLDER THAN THEIR PARENTS.
THEY WERE A LOT OLDER THANTHEY WERE A LOT OLDER THAN EVERYONEEVERYONE..
EVERYONE THAT WORKS WITH DATA HAS STORIES LIKEEVERYONE THAT WORKS WITH DATA HAS STORIES LIKE
THISTHIS
HI, I'M JANEHI, I'M JANE
WHAT WERE MY ASSUMPTIONS?WHAT WERE MY ASSUMPTIONS?
WHAT WERE MY ASSUMPTIONS?WHAT WERE MY ASSUMPTIONS?
1. Children are born after their parents
WHAT WERE MY ASSUMPTIONS?WHAT WERE MY ASSUMPTIONS?
1. Children are born after their parents
2. People can't live forever
WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA?
WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA?
Values are correct
WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA?
Values are correct
We're not missing any data
WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA?
Values are correct
We're not missing any data
Records are unique
WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA?
Values are correct
We're not missing any data
Records are unique
Measurements are precise
WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA?
Values are correct
We're not missing any data
Records are unique
Measurements are precise
(this is a non-exhaustive list)
WHY DOES THIS MATTER?WHY DOES THIS MATTER?
WE DON'T JUST HAVE DATA TO HAVE IT.WE DON'T JUST HAVE DATA TO HAVE IT.
WE DON'T JUST HAVE DATA TO HAVE IT.WE DON'T JUST HAVE DATA TO HAVE IT.
WE USE DATA TO MAKE DECISIONS.WE USE DATA TO MAKE DECISIONS.
WE SHOULD BE EXPLICIT ABOUT OUR ASSUMPTIONS.WE SHOULD BE EXPLICIT ABOUT OUR ASSUMPTIONS.
WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE?
WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE?
Data are always changing
WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE?
Data are always changing
Some changes are loud while others are silent
WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE?
Data are always changing
Some changes are loud while others are silent
Manually checking data is inconsistent and error-prone
WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE?
Data are always changing
Some changes are loud while others are silent
Manually checking data is inconsistent and error-prone
We're working with alotof data
WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE?
Data are always changing
Some changes are loud while others are silent
Manually checking data is inconsistent and error-prone
We're working with alotof data
We're working with a lot of differentkindsof data
I'M LEIFI'M LEIF
WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO?
WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO?
Encode our assumptions in testable form
WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO?
Encode our assumptions in testable form
Test those assumptions on incoming data
WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO?
Encode our assumptions in testable form
Test those assumptions on incoming data
Report when our assumptions don't hold
WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO?
Encode our assumptions in testable form
Test those assumptions on incoming data
Report when our assumptions don't hold
Report allof the assumptions that don't hold
"Whatifwewroteunittestsfordata
likewewriteunittestsforcode?"
unittestunittest
HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM?
HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM?
Encode our assumptions in testable form
HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM?
Test those assumptions on incoming data
Encode our assumptions in testable form
HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM?
Report when our assumptions don't hold
Encode our assumptions in testable form
Test those assumptions on incoming data
HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM?
Report allof the assumptions that don't hold
Encode our assumptions in testable form
Test those assumptions on incoming data
Report when our assumptions don't hold
tripduration 0 days 00:18:09
starttime 2018-08-01 00:00:09.341000-04:00
stoptime 2018-08-01 00:18:18.889000-04:00
start station id 31
start station name Seaport Hotel - Congress St at Seaport Ln
start station latitude 42.3488
start station longitude -71.0417
end station id 190
end station name Nashua Street at Red Auerbach Way
end station latitude 42.3657
end station longitude -71.0643
bikeid 1026
usertype Subscriber
birth year 1969
gender 0
Hubway Bike Share Dataset
How long was the trip?
tripduration 0 days 00:18:09
starttime 2018-08-01 00:00:09.341000-04:00
stoptime 2018-08-01 00:18:18.889000-04:00
start station id 31
start station name Seaport Hotel - Congress St at Seaport Ln
start station latitude 42.3488
start station longitude -71.0417
end station id 190
end station name Nashua Street at Red Auerbach Way
end station latitude 42.3657
end station longitude -71.0643
bikeid 1026
usertype Subscriber
birth year 1969
gender 0
Hubway Bike Share Dataset
How far was the trip?
tripduration 0 days 00:18:09
starttime 2018-08-01 00:00:09.341000-04:00
stoptime 2018-08-01 00:18:18.889000-04:00
start station id 31
start station name Seaport Hotel - Congress St at Seaport Ln
start station latitude 42.3488
start station longitude -71.0417
end station id 190
end station name Nashua Street at Red Auerbach Way
end station latitude 42.3657
end station longitude -71.0643
bikeid 1026
usertype Subscriber
birth year 1969
gender 0
Hubway Bike Share Dataset
Internal metadata
tripduration 0 days 00:18:09
starttime 2018-08-01 00:00:09.341000-04:00
stoptime 2018-08-01 00:18:18.889000-04:00
start station id 31
start station name Seaport Hotel - Congress St at Seaport Ln
start station latitude 42.3488
start station longitude -71.0417
end station id 190
end station name Nashua Street at Red Auerbach Way
end station latitude 42.3657
end station longitude -71.0643
bikeid 1026
usertype Subscriber
birth year 1969
gender 0
Hubway Bike Share Dataset
Who took the trip?
tripduration 0 days 00:18:09
starttime 2018-08-01 00:00:09.341000-04:00
stoptime 2018-08-01 00:18:18.889000-04:00
start station id 31
start station name Seaport Hotel - Congress St at Seaport Ln
start station latitude 42.3488
start station longitude -71.0417
end station id 190
end station name Nashua Street at Red Auerbach Way
end station latitude 42.3657
end station longitude -71.0643
bikeid 1026
usertype Subscriber
birth year 1969
gender 0
Hubway Bike Share Dataset
???
tripduration 0 days 00:18:09
starttime 2018-08-01 00:00:09.341000-04:00
stoptime 2018-08-01 00:18:18.889000-04:00
start station id 31
start station name Seaport Hotel - Congress St at Seaport Ln
start station latitude 42.3488
start station longitude -71.0417
end station id 190
end station name Nashua Street at Red Auerbach Way
end station latitude 42.3657
end station longitude -71.0643
bikeid 1026
usertype Subscriber
birth year 1969
gender 0
Hubway Bike Share Dataset
class TripDistanceTestCase(unittest.TestCase):
 
def setUp(self):
self.data = ...
 
def tearDown(self):
delattr(self, 'data')
 
def test_for_long_trips(self):
thresholds = [
('marathon', 42195),
('10km', 10000)
]
 
for severity, threshold in thresholds:
with self.subTest(severity=severity):
long_trips = self.data[
self.data['distance_meters'] > threshold]
self.assertTrue(long_trips.empty)
Load the data
class TripDistanceTestCase(unittest.TestCase):
 
def setUp(self):
self.data = ...
 
def tearDown(self):
delattr(self, 'data')
 
def test_for_long_trips(self):
thresholds = [
('marathon', 42195),
('10km', 10000)
]
 
for severity, threshold in thresholds:
with self.subTest(severity=severity):
long_trips = self.data[
self.data['distance_meters'] > threshold]
self.assertTrue(long_trips.empty)
Pick some thresholds
class TripDistanceTestCase(unittest.TestCase):
 
def setUp(self):
self.data = ...
 
def tearDown(self):
delattr(self, 'data')
 
def test_for_long_trips(self):
thresholds = [
('marathon', 42195),
('10km', 10000)
]
 
for severity, threshold in thresholds:
with self.subTest(severity=severity):
long_trips = self.data[
self.data['distance_meters'] > threshold]
self.assertTrue(long_trips.empty)
For each threshold
class TripDistanceTestCase(unittest.TestCase):
 
def setUp(self):
self.data = ...
 
def tearDown(self):
delattr(self, 'data')
 
def test_for_long_trips(self):
thresholds = [
('marathon', 42195),
('10km', 10000)
]
 
for severity, threshold in thresholds:
with self.subTest(severity=severity):
long_trips = self.data[
self.data['distance_meters'] > threshold]
self.assertTrue(long_trips.empty)
Find trips longer than the threshold
class TripDistanceTestCase(unittest.TestCase):
 
def setUp(self):
self.data = ...
 
def tearDown(self):
delattr(self, 'data')
 
def test_for_long_trips(self):
thresholds = [
('marathon', 42195),
('10km', 10000)
]
 
for severity, threshold in thresholds:
with self.subTest(severity=severity):
long_trips = self.data[
self.data['distance_meters'] > threshold]
self.assertTrue(long_trips.empty)
Assert none exist
class TripDistanceTestCase(unittest.TestCase):
 
def setUp(self):
self.data = ...
 
def tearDown(self):
delattr(self, 'data')
 
def test_for_long_trips(self):
thresholds = [
('marathon', 42195),
('10km', 10000)
]
 
for severity, threshold in thresholds:
with self.subTest(severity=severity):
long_trips = self.data[
self.data['distance_meters'] > threshold]
self.assertTrue(long_trips.empty)
$ python -m unittest test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/leif/test_bikeshare.py", line 107, in test_for_long_trips
self.assertTrue(long_trips.empty)
AssertionError: False is not true
 
----------------------------------------------------------------------
Ran 1 test in 0.000s
 
FAILED (failures=1)
WHAT DOES THIS GIVE US?WHAT DOES THIS GIVE US?
WHAT DOES THIS GIVE US?WHAT DOES THIS GIVE US?
New data are automatically tested for long trips
WHAT DOES THIS GIVE US?WHAT DOES THIS GIVE US?
New data are automatically tested for long trips
Don't have to remember howto test for long trips
WHAT DOES THIS GIVE US?WHAT DOES THIS GIVE US?
New data are automatically tested for long trips
Don't have to remember howto test for long trips
Can easily run this test over historical data
TEST WRITING INTERLUDE...TEST WRITING INTERLUDE...
TEST WRITING INTERLUDE...TEST WRITING INTERLUDE...
WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT!
WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT!
1. We've thought through our assumptions about the data
WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT!
1. We've thought through our assumptions about the data
2. We've made them explicit by writing them down
WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT!
1. We've thought through our assumptions about the data
2. We've made them explicit by writing them down
3. We've made them executable
WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT!
1. We've thought through our assumptions about the data
2. We've made them explicit by writing them down
3. We've made them executable
4. We've automated them
WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT!
1. We've thought through our assumptions about the data
2. We've made them explicit by writing them down
3. We've made them executable
4. We've automated them
⭐⭐
MONTHS PASS...MONTHS PASS...
$ python -m unittest test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/leif/test_bikeshare.py", line 107, in test_for_long_trips
self.assertTrue(long_trips.empty)
AssertionError: False is not true
 
----------------------------------------------------------------------
Ran 1 test in 0.000s
 
FAILED (failures=1)
Her: "Is there a way to see local variables in my unittest output?"
$ python -m unittest test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/leif/test_bikeshare.py", line 107, in test_for_long_trips
self.assertTrue(long_trips.empty)
AssertionError: False is not true
 
----------------------------------------------------------------------
Ran 1 test in 0.000s
 
FAILED (failures=1)
Her: "Is there a way to see local variables in my unittest output?"
Him: "I think pytest does that..."
$ python -m unittest test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/leif/test_bikeshare.py", line 107, in test_for_long_trips
self.assertTrue(long_trips.empty)
AssertionError: False is not true
 
----------------------------------------------------------------------
Ran 1 test in 0.000s
 
FAILED (failures=1)
PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES
PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES
What is this test doing? Why is it here?
PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES
What is this test doing? Why is it here?
What am I supposed to do about this failure?
PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES
What is this test doing? Why is it here?
What am I supposed to do about this failure?
How bad is it?
PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES
What is this test doing? Why is it here?
What am I supposed to do about this failure?
How bad is it?
Have we seen this failure before? When?
PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES
What is this test doing? Why is it here?
What am I supposed to do about this failure?
How bad is it?
Have we seen this failure before? When?
CONTEXT IS EXPENSIVE TO RECOVERCONTEXT IS EXPENSIVE TO RECOVER
THIS ISN'T UNIQUE TO DATA,THIS ISN'T UNIQUE TO DATA,
BUT IT'S ESPECIALLY HARD WITH DATABUT IT'S ESPECIALLY HARD WITH DATA
THIS ISN'T UNIQUE TO DATA,THIS ISN'T UNIQUE TO DATA,
BUT IT'S ESPECIALLY HARD WITH DATABUT IT'S ESPECIALLY HARD WITH DATA
1. Assumptions aren't black-and-white
THIS ISN'T UNIQUE TO DATA,THIS ISN'T UNIQUE TO DATA,
BUT IT'S ESPECIALLY HARD WITH DATABUT IT'S ESPECIALLY HARD WITH DATA
1. Assumptions aren't black-and-white
2. Failures are usually introduced by someone else
THIS ISN'T UNIQUE TO DATA,THIS ISN'T UNIQUE TO DATA,
BUT IT'S ESPECIALLY HARD WITH DATABUT IT'S ESPECIALLY HARD WITH DATA
1. Assumptions aren't black-and-white
2. Failures are usually introduced by someone else
3. Different tests require different follow-up
ANATOMY OF A MARBLES FAILURE MESSAGEANATOMY OF A MARBLES FAILURE MESSAGE
$ python -m marbles test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
marbles.core.marbles.ContextualAssertionError: False is not true
 
Source (/home/leif/test_bikeshare.py):
151 self.data['distance_meters'] > threshold]
> 152 self.assertTrue(long_trips.empty, note=note)
153
Locals:
severity = 'marathon'
threshold = 42195
long_trips =
start station latitude stop station latitude ...
27955 42.366277 0.0 ...
Note:
There appear to be some trips in the data that are longer than a
marathon! If these are legitimate trips, consider contacting the
local news station about a human-interest story. If these do not
appear to be legitimate trips, contact the bike share mechanics
to have affected bikes identified and repaired.
What is this test doing?
$ python -m marbles test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
marbles.core.marbles.ContextualAssertionError: False is not true
 
Source (/home/leif/test_bikeshare.py):
151 self.data['distance_meters'] > threshold]
> 152 self.assertTrue(long_trips.empty, note=note)
153
Locals:
severity = 'marathon'
threshold = 42195
long_trips =
start station latitude stop station latitude ...
27955 42.366277 0.0 ...
Note:
There appear to be some trips in the data that are longer than a
marathon! If these are legitimate trips, consider contacting the
local news station about a human-interest story. If these do not
appear to be legitimate trips, contact the bike share mechanics
to have affected bikes identified and repaired.
What is this test doing?
$ python -m marbles test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
marbles.core.marbles.ContextualAssertionError: False is not true
 
Source (/home/leif/test_bikeshare.py):
151 self.data['distance_meters'] > threshold]
> 152 self.assertTrue(long_trips.empty, note=note)
153
Locals:
severity = 'marathon'
threshold = 42195
long_trips =
start station latitude stop station latitude ...
27955 42.366277 0.0 ...
Note:
There appear to be some trips in the data that are longer than a
marathon! If these are legitimate trips, consider contacting the
local news station about a human-interest story. If these do not
appear to be legitimate trips, contact the bike share mechanics
to have affected bikes identified and repaired.
Why is it here?
$ python -m marbles test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
marbles.core.marbles.ContextualAssertionError: False is not true
 
Source (/home/leif/test_bikeshare.py):
151 self.data['distance_meters'] > threshold]
> 152 self.assertTrue(long_trips.empty, note=note)
153
Locals:
severity = 'marathon'
threshold = 42195
long_trips =
start station latitude stop station latitude ...
27955 42.366277 0.0 ...
Note:
There appear to be some trips in the data that are longer than a
marathon! If these are legitimate trips, consider contacting the
local news station about a human-interest story. If these do not
appear to be legitimate trips, contact the bike share mechanics
to have affected bikes identified and repaired.
What am I supposed to do about this failure?
$ python -m marbles test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
marbles.core.marbles.ContextualAssertionError: False is not true
 
Source (/home/leif/test_bikeshare.py):
151 self.data['distance_meters'] > threshold]
> 152 self.assertTrue(long_trips.empty, note=note)
153
Locals:
severity = 'marathon'
threshold = 42195
long_trips =
start station latitude stop station latitude ...
27955 42.366277 0.0 ...
Note:
There appear to be some trips in the data that are longer than a
marathon! If these are legitimate trips, consider contacting the
local news station about a human-interest story. If these do not
appear to be legitimate trips, contact the bike share mechanics
to have affected bikes identified and repaired.
How bad is it?
$ python -m marbles test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
marbles.core.marbles.ContextualAssertionError: False is not true
 
Source (/home/leif/test_bikeshare.py):
151 self.data['distance_meters'] > threshold]
> 152 self.assertTrue(long_trips.empty, note=note)
153
Locals:
severity = 'marathon'
threshold = 42195
long_trips =
start station latitude stop station latitude ...
27955 42.366277 0.0 ...
Note:
There appear to be some trips in the data that are longer than a
marathon! If these are legitimate trips, consider contacting the
local news station about a human-interest story. If these do not
appear to be legitimate trips, contact the bike share mechanics
to have affected bikes identified and repaired.
Can we add more context?
$ python -m marbles test_bikeshare.py
======================================================================
FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase)
----------------------------------------------------------------------
marbles.core.marbles.ContextualAssertionError: False is not true
 
Source (/home/leif/test_bikeshare.py):
151 self.data['distance_meters'] > threshold]
> 152 self.assertTrue(long_trips.empty, note=note)
153
Locals:
severity = 'marathon'
threshold = 42195
long_trips =
start station latitude stop station latitude ...
27955 42.366277 0.0 ...
Note:
There appear to be some trips in the data that are longer than a
marathon! If these are legitimate trips, consider contacting the
local news station about a human-interest story. If these do not
appear to be legitimate trips, contact the bike share mechanics
to have affected bikes identified and repaired.
SEMANTIC ASSERTIONSSEMANTIC ASSERTIONS
SEMANTIC ASSERTIONSSEMANTIC ASSERTIONS
self.assertTrue((lower < x) and (x < upper))
SEMANTIC ASSERTIONSSEMANTIC ASSERTIONS
self.assertTrue((lower < x) and (x < upper))
self.assertGreater(x, lower)
self.assertGreater(upper, x)
SEMANTIC ASSERTIONSSEMANTIC ASSERTIONS
self.assertTrue((lower < x) and (x < upper))
self.assertGreater(x, lower)
self.assertGreater(upper, x)
self.assertTrue(all(a < b for a, b in zip([lower, x], [x, upper])))
SEMANTIC ASSERTIONSSEMANTIC ASSERTIONS
self.assertTrue((lower < x) and (x < upper))
self.assertGreater(x, lower)
self.assertGreater(upper, x)
self.assertTrue(all(a < b for a, b in zip([lower, x], [x, upper])))
self.assertBetween(x, lower, upper)
marbles.mixinsmarbles.mixins
marbles.mixinsmarbles.mixins
from marbles.mixins import mixins
 
class TripDistanceTestCase(BikeshareTestCase, mixins.BetweenMixins):
 
def setUp(self):
self.data = ...
 
def tearDown(self):
delattr(self, 'data')
 
def test_for_unreasonable_distances(self):
for distance in self.data['distance_meters']:
self.assertBetween(distance, 100, 42195)
CUSTOM ASSERTIONSCUSTOM ASSERTIONS
CUSTOM ASSERTIONSCUSTOM ASSERTIONS
self.assertTrue(long_trips.empty)
CUSTOM ASSERTIONSCUSTOM ASSERTIONS
self.assertTrue(long_trips.empty)
self.assertEqual(len(long_trips), 0)
CUSTOM ASSERTIONSCUSTOM ASSERTIONS
self.assertTrue(long_trips.empty)
self.assertEqual(len(long_trips), 0)
class DataFrameMixins(object):
 
def assertDataFrameEmpty(self, df, msg=None):
self.assertTrue(df.empty, msg=msg)
DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED?
DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED?
What is this test doing? Why is it here?
DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED?
What am I supposed to do about this failure?
What is this test doing? Why is it here?
DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED?
How bad is it?
What is this test doing? Why is it here?
What am I supposed to do about this failure?
DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED?
Have we seen this failure before? When?
What is this test doing? Why is it here?
What am I supposed to do about this failure?
How bad is it?
ASSERTION LOGGINGASSERTION LOGGING
ASSERTION LOGGINGASSERTION LOGGING
import marbles.core
from marbles.core import log
 
class TripDistanceTestCase(BikeshareTestCase):
...
 
if __name__ == '__main__':
log.logger.configure(logfile='marbles.log')
marbles.core.main()
{
"case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)",
"test_case": "TripDistanceTestCase",
"test_method": "test_for_long_trips",
"assertion": "assertTrue",
...
"locals": [
{
"key": "severity",
"value": "marathon"
},
{
"key": "threshold",
"value": "42195"
},
{
"key": "long_trips",
"value": "
start station latitude stop station latitude ...
27955 42.366277 0.0 ... "
}
],
"month": "2016-07-01",
"severity": "marathon",
"anomalies": "1",
"result": "fail"
}
Which test was running?
{
"case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)",
"test_case": "TripDistanceTestCase",
"test_method": "test_for_long_trips",
"assertion": "assertTrue",
...
"locals": [
{
"key": "severity",
"value": "marathon"
},
{
"key": "threshold",
"value": "42195"
},
{
"key": "long_trips",
"value": "
start station latitude stop station latitude ...
27955 42.366277 0.0 ... "
}
],
"month": "2016-07-01",
"severity": "marathon",
"anomalies": "1",
"result": "fail"
}
What did we assert?
{
"case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)",
"test_case": "TripDistanceTestCase",
"test_method": "test_for_long_trips",
"assertion": "assertTrue",
...
"locals": [
{
"key": "severity",
"value": "marathon"
},
{
"key": "threshold",
"value": "42195"
},
{
"key": "long_trips",
"value": "
start station latitude stop station latitude ...
27955 42.366277 0.0 ... "
}
],
"month": "2016-07-01",
"severity": "marathon",
"anomalies": "1",
"result": "fail"
}
Local variables
{
"case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)",
"test_case": "TripDistanceTestCase",
"test_method": "test_for_long_trips",
"assertion": "assertTrue",
...
"locals": [
{
"key": "severity",
"value": "marathon"
},
{
"key": "threshold",
"value": "42195"
},
{
"key": "long_trips",
"value": "
start station latitude stop station latitude ...
27955 42.366277 0.0 ... "
}
],
"month": "2016-07-01",
"severity": "marathon",
"anomalies": "1",
"result": "fail"
}
Which data were we testing?
{
"case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)",
"test_case": "TripDistanceTestCase",
"test_method": "test_for_long_trips",
"assertion": "assertTrue",
...
"locals": [
{
"key": "severity",
"value": "marathon"
},
{
"key": "threshold",
"value": "42195"
},
{
"key": "long_trips",
"value": "
start station latitude stop station latitude ...
27955 42.366277 0.0 ... "
}
],
"month": "2016-07-01",
"severity": "marathon",
"anomalies": "1",
"result": "fail"
}
Other information about the assertion
{
"case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)",
"test_case": "TripDistanceTestCase",
"test_method": "test_for_long_trips",
"assertion": "assertTrue",
...
"locals": [
{
"key": "severity",
"value": "marathon"
},
{
"key": "threshold",
"value": "42195"
},
{
"key": "long_trips",
"value": "
start station latitude stop station latitude ...
27955 42.366277 0.0 ... "
}
],
"month": "2016-07-01",
"severity": "marathon",
"anomalies": "1",
"result": "fail"
}
More (not pictured)
{
"case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)",
"test_case": "TripDistanceTestCase",
"test_method": "test_for_long_trips",
"assertion": "assertTrue",
...
"locals": [
{
"key": "severity",
"value": "marathon"
},
{
"key": "threshold",
"value": "42195"
},
{
"key": "long_trips",
"value": "
start station latitude stop station latitude ...
27955 42.366277 0.0 ... "
}
],
"month": "2016-07-01",
"severity": "marathon",
"anomalies": "1",
"result": "fail"
}
HISTORICAL FAILURESHISTORICAL FAILURES
HISTORICAL FAILURESHISTORICAL FAILURES
"Have we seen this kind of problem before?"
HISTORICAL FAILURESHISTORICAL FAILURES
"Have we seen this kind of problem before?"
AGGREGATE DATASET HEALTH METRICSAGGREGATE DATASET HEALTH METRICS
AGGREGATE DATASET HEALTH METRICSAGGREGATE DATASET HEALTH METRICS
df = df.pivot_table(
index=['month'], columns=['severity'],
values='anomalies', aggfunc=sum)
df.describe()
AGGREGATE DATASET HEALTH METRICSAGGREGATE DATASET HEALTH METRICS
df = df.pivot_table(
index=['month'], columns=['severity'],
values='anomalies', aggfunc=sum)
df.describe()
CONTEXT IS GOOD FOR SOFTWARE TESTS, TOOCONTEXT IS GOOD FOR SOFTWARE TESTS, TOO
CONTEXT IS GOOD FOR SOFTWARE TESTS, TOOCONTEXT IS GOOD FOR SOFTWARE TESTS, TOO
$ python -m unittest
F
======================================================================
FAIL: test_return_code (docs.examples.getting_started.ResponseTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/home/leif/git/marbles/docs/examples/getting_started.py", line 43, in test_return_code
201
AssertionError: 409 != 201
 
----------------------------------------------------------------------
Ran 1 test in 0.000s
CONTEXT IS GOOD FOR SOFTWARE TESTS, TOOCONTEXT IS GOOD FOR SOFTWARE TESTS, TOO
$ python -m marbles
F
======================================================================
FAIL: test_return_code (docs.examples.getting_started.ResponseTestCase)
----------------------------------------------------------------------
marbles.core.marbles.ContextualAssertionError: 409 != 201
 
Source (/home/leif/git/marbles/docs/examples/getting_started.py):
40 res = requests.put(endpoint, data=data)
> 41 self.assertEqual(
42 res.status_code,
43 201
44 )
Locals:
endpoint = 'http://example.com/api/v1/resource'
data = {'id': 1, 'name': 'Little Bobby Tables'}
res = <docs.examples.getting_started.Response object at 0x7fae97e78978>
 
 
----------------------------------------------------------------------
Ran 1 test in 0.001s
TWO STEPS TO MARBLESTWO STEPS TO MARBLES
$ pip install marbles
$ python -m marbles test_module.py
GITHUBGITHUB
github.com/twosigma/marbles
DOCUMENTATIONDOCUMENTATION
marbles.readthedocs.io
DOCUMENTATIONDOCUMENTATION
marbles.readthedocs.io
CONTRIBUTING AND GETTING HELPCONTRIBUTING AND GETTING HELP
github.com/twosigma/marbles/issues
✨ READ BETTER TEST FAILURES ✨✨ READ BETTER TEST FAILURES ✨
&
github.com/twosigma/marbles
marbles.readthedocs.io
@thejunglejane @leifwalsh

More Related Content

More from PyData

Michal Mucha: Build and Deploy an End-to-end Streaming NLP Insight System | P...
Michal Mucha: Build and Deploy an End-to-end Streaming NLP Insight System | P...Michal Mucha: Build and Deploy an End-to-end Streaming NLP Insight System | P...
Michal Mucha: Build and Deploy an End-to-end Streaming NLP Insight System | P...
PyData
 
The TileDB Array Data Storage Manager - Stavros Papadopoulos, Jake Bolewski
The TileDB Array Data Storage Manager - Stavros Papadopoulos, Jake BolewskiThe TileDB Array Data Storage Manager - Stavros Papadopoulos, Jake Bolewski
The TileDB Array Data Storage Manager - Stavros Papadopoulos, Jake Bolewski
PyData
 
Using Embeddings to Understand the Variance and Evolution of Data Science... ...
Using Embeddings to Understand the Variance and Evolution of Data Science... ...Using Embeddings to Understand the Variance and Evolution of Data Science... ...
Using Embeddings to Understand the Variance and Evolution of Data Science... ...
PyData
 
Deploying Data Science for Distribution of The New York Times - Anne Bauer
Deploying Data Science for Distribution of The New York Times - Anne BauerDeploying Data Science for Distribution of The New York Times - Anne Bauer
Deploying Data Science for Distribution of The New York Times - Anne Bauer
PyData
 
Graph Analytics - From the Whiteboard to Your Toolbox - Sam Lerma
Graph Analytics - From the Whiteboard to Your Toolbox - Sam LermaGraph Analytics - From the Whiteboard to Your Toolbox - Sam Lerma
Graph Analytics - From the Whiteboard to Your Toolbox - Sam Lerma
PyData
 
Do Your Homework! Writing tests for Data Science and Stochastic Code - David ...
Do Your Homework! Writing tests for Data Science and Stochastic Code - David ...Do Your Homework! Writing tests for Data Science and Stochastic Code - David ...
Do Your Homework! Writing tests for Data Science and Stochastic Code - David ...
PyData
 
RESTful Machine Learning with Flask and TensorFlow Serving - Carlo Mazzaferro
RESTful Machine Learning with Flask and TensorFlow Serving - Carlo MazzaferroRESTful Machine Learning with Flask and TensorFlow Serving - Carlo Mazzaferro
RESTful Machine Learning with Flask and TensorFlow Serving - Carlo Mazzaferro
PyData
 
Mining dockless bikeshare and dockless scootershare trip data - Stefanie Brod...
Mining dockless bikeshare and dockless scootershare trip data - Stefanie Brod...Mining dockless bikeshare and dockless scootershare trip data - Stefanie Brod...
Mining dockless bikeshare and dockless scootershare trip data - Stefanie Brod...
PyData
 
Avoiding Bad Database Surprises: Simulation and Scalability - Steven Lott
Avoiding Bad Database Surprises: Simulation and Scalability - Steven LottAvoiding Bad Database Surprises: Simulation and Scalability - Steven Lott
Avoiding Bad Database Surprises: Simulation and Scalability - Steven Lott
PyData
 
Words in Space - Rebecca Bilbro
Words in Space - Rebecca BilbroWords in Space - Rebecca Bilbro
Words in Space - Rebecca Bilbro
PyData
 
End-to-End Machine learning pipelines for Python driven organizations - Nick ...
End-to-End Machine learning pipelines for Python driven organizations - Nick ...End-to-End Machine learning pipelines for Python driven organizations - Nick ...
End-to-End Machine learning pipelines for Python driven organizations - Nick ...
PyData
 
Pydata beautiful soup - Monica Puerto
Pydata beautiful soup - Monica PuertoPydata beautiful soup - Monica Puerto
Pydata beautiful soup - Monica Puerto
PyData
 
1D Convolutional Neural Networks for Time Series Modeling - Nathan Janos, Jef...
1D Convolutional Neural Networks for Time Series Modeling - Nathan Janos, Jef...1D Convolutional Neural Networks for Time Series Modeling - Nathan Janos, Jef...
1D Convolutional Neural Networks for Time Series Modeling - Nathan Janos, Jef...
PyData
 
Extending Pandas with Custom Types - Will Ayd
Extending Pandas with Custom Types - Will AydExtending Pandas with Custom Types - Will Ayd
Extending Pandas with Custom Types - Will Ayd
PyData
 
Measuring Model Fairness - Stephen Hoover
Measuring Model Fairness - Stephen HooverMeasuring Model Fairness - Stephen Hoover
Measuring Model Fairness - Stephen Hoover
PyData
 
What's the Science in Data Science? - Skipper Seabold
What's the Science in Data Science? - Skipper SeaboldWhat's the Science in Data Science? - Skipper Seabold
What's the Science in Data Science? - Skipper Seabold
PyData
 
Applying Statistical Modeling and Machine Learning to Perform Time-Series For...
Applying Statistical Modeling and Machine Learning to Perform Time-Series For...Applying Statistical Modeling and Machine Learning to Perform Time-Series For...
Applying Statistical Modeling and Machine Learning to Perform Time-Series For...
PyData
 
Solving very simple substitution ciphers algorithmically - Stephen Enright-Ward
Solving very simple substitution ciphers algorithmically - Stephen Enright-WardSolving very simple substitution ciphers algorithmically - Stephen Enright-Ward
Solving very simple substitution ciphers algorithmically - Stephen Enright-Ward
PyData
 
The Face of Nanomaterials: Insightful Classification Using Deep Learning - An...
The Face of Nanomaterials: Insightful Classification Using Deep Learning - An...The Face of Nanomaterials: Insightful Classification Using Deep Learning - An...
The Face of Nanomaterials: Insightful Classification Using Deep Learning - An...
PyData
 
Deprecating the state machine: building conversational AI with the Rasa stack...
Deprecating the state machine: building conversational AI with the Rasa stack...Deprecating the state machine: building conversational AI with the Rasa stack...
Deprecating the state machine: building conversational AI with the Rasa stack...
PyData
 

More from PyData (20)

Michal Mucha: Build and Deploy an End-to-end Streaming NLP Insight System | P...
Michal Mucha: Build and Deploy an End-to-end Streaming NLP Insight System | P...Michal Mucha: Build and Deploy an End-to-end Streaming NLP Insight System | P...
Michal Mucha: Build and Deploy an End-to-end Streaming NLP Insight System | P...
 
The TileDB Array Data Storage Manager - Stavros Papadopoulos, Jake Bolewski
The TileDB Array Data Storage Manager - Stavros Papadopoulos, Jake BolewskiThe TileDB Array Data Storage Manager - Stavros Papadopoulos, Jake Bolewski
The TileDB Array Data Storage Manager - Stavros Papadopoulos, Jake Bolewski
 
Using Embeddings to Understand the Variance and Evolution of Data Science... ...
Using Embeddings to Understand the Variance and Evolution of Data Science... ...Using Embeddings to Understand the Variance and Evolution of Data Science... ...
Using Embeddings to Understand the Variance and Evolution of Data Science... ...
 
Deploying Data Science for Distribution of The New York Times - Anne Bauer
Deploying Data Science for Distribution of The New York Times - Anne BauerDeploying Data Science for Distribution of The New York Times - Anne Bauer
Deploying Data Science for Distribution of The New York Times - Anne Bauer
 
Graph Analytics - From the Whiteboard to Your Toolbox - Sam Lerma
Graph Analytics - From the Whiteboard to Your Toolbox - Sam LermaGraph Analytics - From the Whiteboard to Your Toolbox - Sam Lerma
Graph Analytics - From the Whiteboard to Your Toolbox - Sam Lerma
 
Do Your Homework! Writing tests for Data Science and Stochastic Code - David ...
Do Your Homework! Writing tests for Data Science and Stochastic Code - David ...Do Your Homework! Writing tests for Data Science and Stochastic Code - David ...
Do Your Homework! Writing tests for Data Science and Stochastic Code - David ...
 
RESTful Machine Learning with Flask and TensorFlow Serving - Carlo Mazzaferro
RESTful Machine Learning with Flask and TensorFlow Serving - Carlo MazzaferroRESTful Machine Learning with Flask and TensorFlow Serving - Carlo Mazzaferro
RESTful Machine Learning with Flask and TensorFlow Serving - Carlo Mazzaferro
 
Mining dockless bikeshare and dockless scootershare trip data - Stefanie Brod...
Mining dockless bikeshare and dockless scootershare trip data - Stefanie Brod...Mining dockless bikeshare and dockless scootershare trip data - Stefanie Brod...
Mining dockless bikeshare and dockless scootershare trip data - Stefanie Brod...
 
Avoiding Bad Database Surprises: Simulation and Scalability - Steven Lott
Avoiding Bad Database Surprises: Simulation and Scalability - Steven LottAvoiding Bad Database Surprises: Simulation and Scalability - Steven Lott
Avoiding Bad Database Surprises: Simulation and Scalability - Steven Lott
 
Words in Space - Rebecca Bilbro
Words in Space - Rebecca BilbroWords in Space - Rebecca Bilbro
Words in Space - Rebecca Bilbro
 
End-to-End Machine learning pipelines for Python driven organizations - Nick ...
End-to-End Machine learning pipelines for Python driven organizations - Nick ...End-to-End Machine learning pipelines for Python driven organizations - Nick ...
End-to-End Machine learning pipelines for Python driven organizations - Nick ...
 
Pydata beautiful soup - Monica Puerto
Pydata beautiful soup - Monica PuertoPydata beautiful soup - Monica Puerto
Pydata beautiful soup - Monica Puerto
 
1D Convolutional Neural Networks for Time Series Modeling - Nathan Janos, Jef...
1D Convolutional Neural Networks for Time Series Modeling - Nathan Janos, Jef...1D Convolutional Neural Networks for Time Series Modeling - Nathan Janos, Jef...
1D Convolutional Neural Networks for Time Series Modeling - Nathan Janos, Jef...
 
Extending Pandas with Custom Types - Will Ayd
Extending Pandas with Custom Types - Will AydExtending Pandas with Custom Types - Will Ayd
Extending Pandas with Custom Types - Will Ayd
 
Measuring Model Fairness - Stephen Hoover
Measuring Model Fairness - Stephen HooverMeasuring Model Fairness - Stephen Hoover
Measuring Model Fairness - Stephen Hoover
 
What's the Science in Data Science? - Skipper Seabold
What's the Science in Data Science? - Skipper SeaboldWhat's the Science in Data Science? - Skipper Seabold
What's the Science in Data Science? - Skipper Seabold
 
Applying Statistical Modeling and Machine Learning to Perform Time-Series For...
Applying Statistical Modeling and Machine Learning to Perform Time-Series For...Applying Statistical Modeling and Machine Learning to Perform Time-Series For...
Applying Statistical Modeling and Machine Learning to Perform Time-Series For...
 
Solving very simple substitution ciphers algorithmically - Stephen Enright-Ward
Solving very simple substitution ciphers algorithmically - Stephen Enright-WardSolving very simple substitution ciphers algorithmically - Stephen Enright-Ward
Solving very simple substitution ciphers algorithmically - Stephen Enright-Ward
 
The Face of Nanomaterials: Insightful Classification Using Deep Learning - An...
The Face of Nanomaterials: Insightful Classification Using Deep Learning - An...The Face of Nanomaterials: Insightful Classification Using Deep Learning - An...
The Face of Nanomaterials: Insightful Classification Using Deep Learning - An...
 
Deprecating the state machine: building conversational AI with the Rasa stack...
Deprecating the state machine: building conversational AI with the Rasa stack...Deprecating the state machine: building conversational AI with the Rasa stack...
Deprecating the state machine: building conversational AI with the Rasa stack...
 

Recently uploaded

GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
Neo4j
 
Communications Mining Series - Zero to Hero - Session 1
Communications Mining Series - Zero to Hero - Session 1Communications Mining Series - Zero to Hero - Session 1
Communications Mining Series - Zero to Hero - Session 1
DianaGray10
 
Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !
KatiaHIMEUR1
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
91mobiles
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
Jemma Hussein Allen
 
Introduction to CHERI technology - Cybersecurity
Introduction to CHERI technology - CybersecurityIntroduction to CHERI technology - Cybersecurity
Introduction to CHERI technology - Cybersecurity
mikeeftimakis1
 
Generative AI Deep Dive: Advancing from Proof of Concept to Production
Generative AI Deep Dive: Advancing from Proof of Concept to ProductionGenerative AI Deep Dive: Advancing from Proof of Concept to Production
Generative AI Deep Dive: Advancing from Proof of Concept to Production
Aggregage
 
By Design, not by Accident - Agile Venture Bolzano 2024
By Design, not by Accident - Agile Venture Bolzano 2024By Design, not by Accident - Agile Venture Bolzano 2024
By Design, not by Accident - Agile Venture Bolzano 2024
Pierluigi Pugliese
 
Pushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 daysPushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 days
Adtran
 
Video Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the FutureVideo Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the Future
Alpen-Adria-Universität
 
Essentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FMEEssentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FME
Safe Software
 
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdfFIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance
 
UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4
DianaGray10
 
GraphSummit Singapore | The Art of the Possible with Graph - Q2 2024
GraphSummit Singapore | The Art of the  Possible with Graph - Q2 2024GraphSummit Singapore | The Art of the  Possible with Graph - Q2 2024
GraphSummit Singapore | The Art of the Possible with Graph - Q2 2024
Neo4j
 
PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)
Ralf Eggert
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
James Anderson
 
GridMate - End to end testing is a critical piece to ensure quality and avoid...
GridMate - End to end testing is a critical piece to ensure quality and avoid...GridMate - End to end testing is a critical piece to ensure quality and avoid...
GridMate - End to end testing is a critical piece to ensure quality and avoid...
ThomasParaiso2
 
Alt. GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using ...
Alt. GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using ...Alt. GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using ...
Alt. GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using ...
James Anderson
 
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 previewState of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
Prayukth K V
 
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdfFIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance
 

Recently uploaded (20)

GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
GraphSummit Singapore | Graphing Success: Revolutionising Organisational Stru...
 
Communications Mining Series - Zero to Hero - Session 1
Communications Mining Series - Zero to Hero - Session 1Communications Mining Series - Zero to Hero - Session 1
Communications Mining Series - Zero to Hero - Session 1
 
Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !Securing your Kubernetes cluster_ a step-by-step guide to success !
Securing your Kubernetes cluster_ a step-by-step guide to success !
 
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdfSmart TV Buyer Insights Survey 2024 by 91mobiles.pdf
Smart TV Buyer Insights Survey 2024 by 91mobiles.pdf
 
The Future of Platform Engineering
The Future of Platform EngineeringThe Future of Platform Engineering
The Future of Platform Engineering
 
Introduction to CHERI technology - Cybersecurity
Introduction to CHERI technology - CybersecurityIntroduction to CHERI technology - Cybersecurity
Introduction to CHERI technology - Cybersecurity
 
Generative AI Deep Dive: Advancing from Proof of Concept to Production
Generative AI Deep Dive: Advancing from Proof of Concept to ProductionGenerative AI Deep Dive: Advancing from Proof of Concept to Production
Generative AI Deep Dive: Advancing from Proof of Concept to Production
 
By Design, not by Accident - Agile Venture Bolzano 2024
By Design, not by Accident - Agile Venture Bolzano 2024By Design, not by Accident - Agile Venture Bolzano 2024
By Design, not by Accident - Agile Venture Bolzano 2024
 
Pushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 daysPushing the limits of ePRTC: 100ns holdover for 100 days
Pushing the limits of ePRTC: 100ns holdover for 100 days
 
Video Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the FutureVideo Streaming: Then, Now, and in the Future
Video Streaming: Then, Now, and in the Future
 
Essentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FMEEssentials of Automations: The Art of Triggers and Actions in FME
Essentials of Automations: The Art of Triggers and Actions in FME
 
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdfFIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
FIDO Alliance Osaka Seminar: Passkeys at Amazon.pdf
 
UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4UiPath Test Automation using UiPath Test Suite series, part 4
UiPath Test Automation using UiPath Test Suite series, part 4
 
GraphSummit Singapore | The Art of the Possible with Graph - Q2 2024
GraphSummit Singapore | The Art of the  Possible with Graph - Q2 2024GraphSummit Singapore | The Art of the  Possible with Graph - Q2 2024
GraphSummit Singapore | The Art of the Possible with Graph - Q2 2024
 
PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)PHP Frameworks: I want to break free (IPC Berlin 2024)
PHP Frameworks: I want to break free (IPC Berlin 2024)
 
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using Deplo...
 
GridMate - End to end testing is a critical piece to ensure quality and avoid...
GridMate - End to end testing is a critical piece to ensure quality and avoid...GridMate - End to end testing is a critical piece to ensure quality and avoid...
GridMate - End to end testing is a critical piece to ensure quality and avoid...
 
Alt. GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using ...
Alt. GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using ...Alt. GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using ...
Alt. GDG Cloud Southlake #33: Boule & Rebala: Effective AppSec in SDLC using ...
 
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 previewState of ICS and IoT Cyber Threat Landscape Report 2024 preview
State of ICS and IoT Cyber Threat Landscape Report 2024 preview
 
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdfFIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
FIDO Alliance Osaka Seminar: Passkeys and the Road Ahead.pdf
 

Unit testing data with marbles - Jane Stewart Adams, Leif Walsh

  • 1. UNIT TESTING DATAUNIT TESTING DATA WITH MARBLESWITH MARBLES JANE ADAMS & LEIF WALSHJANE ADAMS & LEIF WALSH
  • 2. ONCE UPON A TIME...ONCE UPON A TIME...
  • 3. ONCE UPON A TIME...ONCE UPON A TIME... CHILDREN WERE OLDER THAN THEIR PARENTS.CHILDREN WERE OLDER THAN THEIR PARENTS.
  • 4. ONCE UPON A TIME...ONCE UPON A TIME... CHILDREN WERE OLDER THAN THEIR PARENTS.CHILDREN WERE OLDER THAN THEIR PARENTS. THEY WERETHEY WERE ALOTALOTOLDER THAN THEIR PARENTS.OLDER THAN THEIR PARENTS.
  • 5. ONCE UPON A TIME...ONCE UPON A TIME... CHILDREN WERE OLDER THAN THEIR PARENTS.CHILDREN WERE OLDER THAN THEIR PARENTS. THEY WERETHEY WERE ALOTALOTOLDER THAN THEIR PARENTS.OLDER THAN THEIR PARENTS. THEY WERE A LOT OLDER THANTHEY WERE A LOT OLDER THAN EVERYONEEVERYONE..
  • 6. EVERYONE THAT WORKS WITH DATA HAS STORIES LIKEEVERYONE THAT WORKS WITH DATA HAS STORIES LIKE THISTHIS
  • 7. HI, I'M JANEHI, I'M JANE
  • 8. WHAT WERE MY ASSUMPTIONS?WHAT WERE MY ASSUMPTIONS?
  • 9. WHAT WERE MY ASSUMPTIONS?WHAT WERE MY ASSUMPTIONS? 1. Children are born after their parents
  • 10. WHAT WERE MY ASSUMPTIONS?WHAT WERE MY ASSUMPTIONS? 1. Children are born after their parents 2. People can't live forever
  • 11. WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA?
  • 12. WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA? Values are correct
  • 13. WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA? Values are correct We're not missing any data
  • 14. WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA? Values are correct We're not missing any data Records are unique
  • 15. WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA? Values are correct We're not missing any data Records are unique Measurements are precise
  • 16. WHAT ELSE DO WE ASSUME ABOUT DATA?WHAT ELSE DO WE ASSUME ABOUT DATA? Values are correct We're not missing any data Records are unique Measurements are precise (this is a non-exhaustive list)
  • 17. WHY DOES THIS MATTER?WHY DOES THIS MATTER?
  • 18. WE DON'T JUST HAVE DATA TO HAVE IT.WE DON'T JUST HAVE DATA TO HAVE IT.
  • 19. WE DON'T JUST HAVE DATA TO HAVE IT.WE DON'T JUST HAVE DATA TO HAVE IT. WE USE DATA TO MAKE DECISIONS.WE USE DATA TO MAKE DECISIONS.
  • 20. WE SHOULD BE EXPLICIT ABOUT OUR ASSUMPTIONS.WE SHOULD BE EXPLICIT ABOUT OUR ASSUMPTIONS.
  • 21. WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE?
  • 22. WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE? Data are always changing
  • 23. WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE? Data are always changing Some changes are loud while others are silent
  • 24. WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE? Data are always changing Some changes are loud while others are silent Manually checking data is inconsistent and error-prone
  • 25. WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE? Data are always changing Some changes are loud while others are silent Manually checking data is inconsistent and error-prone We're working with alotof data
  • 26. WHAT ARE THE IMPORTANT PROBLEMS HERE?WHAT ARE THE IMPORTANT PROBLEMS HERE? Data are always changing Some changes are loud while others are silent Manually checking data is inconsistent and error-prone We're working with alotof data We're working with a lot of differentkindsof data
  • 28. WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO?
  • 29. WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO? Encode our assumptions in testable form
  • 30. WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO? Encode our assumptions in testable form Test those assumptions on incoming data
  • 31. WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO? Encode our assumptions in testable form Test those assumptions on incoming data Report when our assumptions don't hold
  • 32. WHAT DO WE WANT TO DO?WHAT DO WE WANT TO DO? Encode our assumptions in testable form Test those assumptions on incoming data Report when our assumptions don't hold Report allof the assumptions that don't hold
  • 35. HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM?
  • 36. HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM? Encode our assumptions in testable form
  • 37. HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM? Test those assumptions on incoming data Encode our assumptions in testable form
  • 38. HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM? Report when our assumptions don't hold Encode our assumptions in testable form Test those assumptions on incoming data
  • 39. HOW DOES UNITTEST SOLVE OUR PROBLEM?HOW DOES UNITTEST SOLVE OUR PROBLEM? Report allof the assumptions that don't hold Encode our assumptions in testable form Test those assumptions on incoming data Report when our assumptions don't hold
  • 40. tripduration 0 days 00:18:09 starttime 2018-08-01 00:00:09.341000-04:00 stoptime 2018-08-01 00:18:18.889000-04:00 start station id 31 start station name Seaport Hotel - Congress St at Seaport Ln start station latitude 42.3488 start station longitude -71.0417 end station id 190 end station name Nashua Street at Red Auerbach Way end station latitude 42.3657 end station longitude -71.0643 bikeid 1026 usertype Subscriber birth year 1969 gender 0 Hubway Bike Share Dataset
  • 41. How long was the trip? tripduration 0 days 00:18:09 starttime 2018-08-01 00:00:09.341000-04:00 stoptime 2018-08-01 00:18:18.889000-04:00 start station id 31 start station name Seaport Hotel - Congress St at Seaport Ln start station latitude 42.3488 start station longitude -71.0417 end station id 190 end station name Nashua Street at Red Auerbach Way end station latitude 42.3657 end station longitude -71.0643 bikeid 1026 usertype Subscriber birth year 1969 gender 0 Hubway Bike Share Dataset
  • 42. How far was the trip? tripduration 0 days 00:18:09 starttime 2018-08-01 00:00:09.341000-04:00 stoptime 2018-08-01 00:18:18.889000-04:00 start station id 31 start station name Seaport Hotel - Congress St at Seaport Ln start station latitude 42.3488 start station longitude -71.0417 end station id 190 end station name Nashua Street at Red Auerbach Way end station latitude 42.3657 end station longitude -71.0643 bikeid 1026 usertype Subscriber birth year 1969 gender 0 Hubway Bike Share Dataset
  • 43. Internal metadata tripduration 0 days 00:18:09 starttime 2018-08-01 00:00:09.341000-04:00 stoptime 2018-08-01 00:18:18.889000-04:00 start station id 31 start station name Seaport Hotel - Congress St at Seaport Ln start station latitude 42.3488 start station longitude -71.0417 end station id 190 end station name Nashua Street at Red Auerbach Way end station latitude 42.3657 end station longitude -71.0643 bikeid 1026 usertype Subscriber birth year 1969 gender 0 Hubway Bike Share Dataset
  • 44. Who took the trip? tripduration 0 days 00:18:09 starttime 2018-08-01 00:00:09.341000-04:00 stoptime 2018-08-01 00:18:18.889000-04:00 start station id 31 start station name Seaport Hotel - Congress St at Seaport Ln start station latitude 42.3488 start station longitude -71.0417 end station id 190 end station name Nashua Street at Red Auerbach Way end station latitude 42.3657 end station longitude -71.0643 bikeid 1026 usertype Subscriber birth year 1969 gender 0 Hubway Bike Share Dataset
  • 45. ??? tripduration 0 days 00:18:09 starttime 2018-08-01 00:00:09.341000-04:00 stoptime 2018-08-01 00:18:18.889000-04:00 start station id 31 start station name Seaport Hotel - Congress St at Seaport Ln start station latitude 42.3488 start station longitude -71.0417 end station id 190 end station name Nashua Street at Red Auerbach Way end station latitude 42.3657 end station longitude -71.0643 bikeid 1026 usertype Subscriber birth year 1969 gender 0 Hubway Bike Share Dataset
  • 46. class TripDistanceTestCase(unittest.TestCase):   def setUp(self): self.data = ...   def tearDown(self): delattr(self, 'data')   def test_for_long_trips(self): thresholds = [ ('marathon', 42195), ('10km', 10000) ]   for severity, threshold in thresholds: with self.subTest(severity=severity): long_trips = self.data[ self.data['distance_meters'] > threshold] self.assertTrue(long_trips.empty)
  • 47. Load the data class TripDistanceTestCase(unittest.TestCase):   def setUp(self): self.data = ...   def tearDown(self): delattr(self, 'data')   def test_for_long_trips(self): thresholds = [ ('marathon', 42195), ('10km', 10000) ]   for severity, threshold in thresholds: with self.subTest(severity=severity): long_trips = self.data[ self.data['distance_meters'] > threshold] self.assertTrue(long_trips.empty)
  • 48. Pick some thresholds class TripDistanceTestCase(unittest.TestCase):   def setUp(self): self.data = ...   def tearDown(self): delattr(self, 'data')   def test_for_long_trips(self): thresholds = [ ('marathon', 42195), ('10km', 10000) ]   for severity, threshold in thresholds: with self.subTest(severity=severity): long_trips = self.data[ self.data['distance_meters'] > threshold] self.assertTrue(long_trips.empty)
  • 49. For each threshold class TripDistanceTestCase(unittest.TestCase):   def setUp(self): self.data = ...   def tearDown(self): delattr(self, 'data')   def test_for_long_trips(self): thresholds = [ ('marathon', 42195), ('10km', 10000) ]   for severity, threshold in thresholds: with self.subTest(severity=severity): long_trips = self.data[ self.data['distance_meters'] > threshold] self.assertTrue(long_trips.empty)
  • 50. Find trips longer than the threshold class TripDistanceTestCase(unittest.TestCase):   def setUp(self): self.data = ...   def tearDown(self): delattr(self, 'data')   def test_for_long_trips(self): thresholds = [ ('marathon', 42195), ('10km', 10000) ]   for severity, threshold in thresholds: with self.subTest(severity=severity): long_trips = self.data[ self.data['distance_meters'] > threshold] self.assertTrue(long_trips.empty)
  • 51. Assert none exist class TripDistanceTestCase(unittest.TestCase):   def setUp(self): self.data = ...   def tearDown(self): delattr(self, 'data')   def test_for_long_trips(self): thresholds = [ ('marathon', 42195), ('10km', 10000) ]   for severity, threshold in thresholds: with self.subTest(severity=severity): long_trips = self.data[ self.data['distance_meters'] > threshold] self.assertTrue(long_trips.empty)
  • 52. $ python -m unittest test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/leif/test_bikeshare.py", line 107, in test_for_long_trips self.assertTrue(long_trips.empty) AssertionError: False is not true   ---------------------------------------------------------------------- Ran 1 test in 0.000s   FAILED (failures=1)
  • 53. WHAT DOES THIS GIVE US?WHAT DOES THIS GIVE US?
  • 54. WHAT DOES THIS GIVE US?WHAT DOES THIS GIVE US? New data are automatically tested for long trips
  • 55. WHAT DOES THIS GIVE US?WHAT DOES THIS GIVE US? New data are automatically tested for long trips Don't have to remember howto test for long trips
  • 56. WHAT DOES THIS GIVE US?WHAT DOES THIS GIVE US? New data are automatically tested for long trips Don't have to remember howto test for long trips Can easily run this test over historical data
  • 57. TEST WRITING INTERLUDE...TEST WRITING INTERLUDE...
  • 58. TEST WRITING INTERLUDE...TEST WRITING INTERLUDE...
  • 59. WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT!
  • 60. WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT! 1. We've thought through our assumptions about the data
  • 61. WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT! 1. We've thought through our assumptions about the data 2. We've made them explicit by writing them down
  • 62. WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT! 1. We've thought through our assumptions about the data 2. We've made them explicit by writing them down 3. We've made them executable
  • 63. WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT! 1. We've thought through our assumptions about the data 2. We've made them explicit by writing them down 3. We've made them executable 4. We've automated them
  • 64. WE'RE IN A PRETTY GOOD SPOT!WE'RE IN A PRETTY GOOD SPOT! 1. We've thought through our assumptions about the data 2. We've made them explicit by writing them down 3. We've made them executable 4. We've automated them ⭐⭐
  • 66. $ python -m unittest test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/leif/test_bikeshare.py", line 107, in test_for_long_trips self.assertTrue(long_trips.empty) AssertionError: False is not true   ---------------------------------------------------------------------- Ran 1 test in 0.000s   FAILED (failures=1)
  • 67. Her: "Is there a way to see local variables in my unittest output?" $ python -m unittest test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/leif/test_bikeshare.py", line 107, in test_for_long_trips self.assertTrue(long_trips.empty) AssertionError: False is not true   ---------------------------------------------------------------------- Ran 1 test in 0.000s   FAILED (failures=1)
  • 68. Her: "Is there a way to see local variables in my unittest output?" Him: "I think pytest does that..." $ python -m unittest test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/leif/test_bikeshare.py", line 107, in test_for_long_trips self.assertTrue(long_trips.empty) AssertionError: False is not true   ---------------------------------------------------------------------- Ran 1 test in 0.000s   FAILED (failures=1)
  • 69. PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES
  • 70. PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES What is this test doing? Why is it here?
  • 71. PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES What is this test doing? Why is it here? What am I supposed to do about this failure?
  • 72. PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES What is this test doing? Why is it here? What am I supposed to do about this failure? How bad is it?
  • 73. PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES What is this test doing? Why is it here? What am I supposed to do about this failure? How bad is it? Have we seen this failure before? When?
  • 74. PUT YOURSELF IN THE TEST CONSUMER'S SHOESPUT YOURSELF IN THE TEST CONSUMER'S SHOES What is this test doing? Why is it here? What am I supposed to do about this failure? How bad is it? Have we seen this failure before? When? CONTEXT IS EXPENSIVE TO RECOVERCONTEXT IS EXPENSIVE TO RECOVER
  • 75. THIS ISN'T UNIQUE TO DATA,THIS ISN'T UNIQUE TO DATA, BUT IT'S ESPECIALLY HARD WITH DATABUT IT'S ESPECIALLY HARD WITH DATA
  • 76. THIS ISN'T UNIQUE TO DATA,THIS ISN'T UNIQUE TO DATA, BUT IT'S ESPECIALLY HARD WITH DATABUT IT'S ESPECIALLY HARD WITH DATA 1. Assumptions aren't black-and-white
  • 77. THIS ISN'T UNIQUE TO DATA,THIS ISN'T UNIQUE TO DATA, BUT IT'S ESPECIALLY HARD WITH DATABUT IT'S ESPECIALLY HARD WITH DATA 1. Assumptions aren't black-and-white 2. Failures are usually introduced by someone else
  • 78. THIS ISN'T UNIQUE TO DATA,THIS ISN'T UNIQUE TO DATA, BUT IT'S ESPECIALLY HARD WITH DATABUT IT'S ESPECIALLY HARD WITH DATA 1. Assumptions aren't black-and-white 2. Failures are usually introduced by someone else 3. Different tests require different follow-up
  • 79.
  • 80. ANATOMY OF A MARBLES FAILURE MESSAGEANATOMY OF A MARBLES FAILURE MESSAGE
  • 81. $ python -m marbles test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- marbles.core.marbles.ContextualAssertionError: False is not true   Source (/home/leif/test_bikeshare.py): 151 self.data['distance_meters'] > threshold] > 152 self.assertTrue(long_trips.empty, note=note) 153 Locals: severity = 'marathon' threshold = 42195 long_trips = start station latitude stop station latitude ... 27955 42.366277 0.0 ... Note: There appear to be some trips in the data that are longer than a marathon! If these are legitimate trips, consider contacting the local news station about a human-interest story. If these do not appear to be legitimate trips, contact the bike share mechanics to have affected bikes identified and repaired.
  • 82. What is this test doing? $ python -m marbles test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- marbles.core.marbles.ContextualAssertionError: False is not true   Source (/home/leif/test_bikeshare.py): 151 self.data['distance_meters'] > threshold] > 152 self.assertTrue(long_trips.empty, note=note) 153 Locals: severity = 'marathon' threshold = 42195 long_trips = start station latitude stop station latitude ... 27955 42.366277 0.0 ... Note: There appear to be some trips in the data that are longer than a marathon! If these are legitimate trips, consider contacting the local news station about a human-interest story. If these do not appear to be legitimate trips, contact the bike share mechanics to have affected bikes identified and repaired.
  • 83. What is this test doing? $ python -m marbles test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- marbles.core.marbles.ContextualAssertionError: False is not true   Source (/home/leif/test_bikeshare.py): 151 self.data['distance_meters'] > threshold] > 152 self.assertTrue(long_trips.empty, note=note) 153 Locals: severity = 'marathon' threshold = 42195 long_trips = start station latitude stop station latitude ... 27955 42.366277 0.0 ... Note: There appear to be some trips in the data that are longer than a marathon! If these are legitimate trips, consider contacting the local news station about a human-interest story. If these do not appear to be legitimate trips, contact the bike share mechanics to have affected bikes identified and repaired.
  • 84. Why is it here? $ python -m marbles test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- marbles.core.marbles.ContextualAssertionError: False is not true   Source (/home/leif/test_bikeshare.py): 151 self.data['distance_meters'] > threshold] > 152 self.assertTrue(long_trips.empty, note=note) 153 Locals: severity = 'marathon' threshold = 42195 long_trips = start station latitude stop station latitude ... 27955 42.366277 0.0 ... Note: There appear to be some trips in the data that are longer than a marathon! If these are legitimate trips, consider contacting the local news station about a human-interest story. If these do not appear to be legitimate trips, contact the bike share mechanics to have affected bikes identified and repaired.
  • 85. What am I supposed to do about this failure? $ python -m marbles test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- marbles.core.marbles.ContextualAssertionError: False is not true   Source (/home/leif/test_bikeshare.py): 151 self.data['distance_meters'] > threshold] > 152 self.assertTrue(long_trips.empty, note=note) 153 Locals: severity = 'marathon' threshold = 42195 long_trips = start station latitude stop station latitude ... 27955 42.366277 0.0 ... Note: There appear to be some trips in the data that are longer than a marathon! If these are legitimate trips, consider contacting the local news station about a human-interest story. If these do not appear to be legitimate trips, contact the bike share mechanics to have affected bikes identified and repaired.
  • 86. How bad is it? $ python -m marbles test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- marbles.core.marbles.ContextualAssertionError: False is not true   Source (/home/leif/test_bikeshare.py): 151 self.data['distance_meters'] > threshold] > 152 self.assertTrue(long_trips.empty, note=note) 153 Locals: severity = 'marathon' threshold = 42195 long_trips = start station latitude stop station latitude ... 27955 42.366277 0.0 ... Note: There appear to be some trips in the data that are longer than a marathon! If these are legitimate trips, consider contacting the local news station about a human-interest story. If these do not appear to be legitimate trips, contact the bike share mechanics to have affected bikes identified and repaired.
  • 87. Can we add more context? $ python -m marbles test_bikeshare.py ====================================================================== FAIL: test_for_long_trips (test_bikeshare.TripDistanceTestCase) ---------------------------------------------------------------------- marbles.core.marbles.ContextualAssertionError: False is not true   Source (/home/leif/test_bikeshare.py): 151 self.data['distance_meters'] > threshold] > 152 self.assertTrue(long_trips.empty, note=note) 153 Locals: severity = 'marathon' threshold = 42195 long_trips = start station latitude stop station latitude ... 27955 42.366277 0.0 ... Note: There appear to be some trips in the data that are longer than a marathon! If these are legitimate trips, consider contacting the local news station about a human-interest story. If these do not appear to be legitimate trips, contact the bike share mechanics to have affected bikes identified and repaired.
  • 90. SEMANTIC ASSERTIONSSEMANTIC ASSERTIONS self.assertTrue((lower < x) and (x < upper)) self.assertGreater(x, lower) self.assertGreater(upper, x)
  • 91. SEMANTIC ASSERTIONSSEMANTIC ASSERTIONS self.assertTrue((lower < x) and (x < upper)) self.assertGreater(x, lower) self.assertGreater(upper, x) self.assertTrue(all(a < b for a, b in zip([lower, x], [x, upper])))
  • 92. SEMANTIC ASSERTIONSSEMANTIC ASSERTIONS self.assertTrue((lower < x) and (x < upper)) self.assertGreater(x, lower) self.assertGreater(upper, x) self.assertTrue(all(a < b for a, b in zip([lower, x], [x, upper]))) self.assertBetween(x, lower, upper)
  • 94. marbles.mixinsmarbles.mixins from marbles.mixins import mixins   class TripDistanceTestCase(BikeshareTestCase, mixins.BetweenMixins):   def setUp(self): self.data = ...   def tearDown(self): delattr(self, 'data')   def test_for_unreasonable_distances(self): for distance in self.data['distance_meters']: self.assertBetween(distance, 100, 42195)
  • 95.
  • 99. CUSTOM ASSERTIONSCUSTOM ASSERTIONS self.assertTrue(long_trips.empty) self.assertEqual(len(long_trips), 0) class DataFrameMixins(object):   def assertDataFrameEmpty(self, df, msg=None): self.assertTrue(df.empty, msg=msg)
  • 100. DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED?
  • 101. DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED? What is this test doing? Why is it here?
  • 102. DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED? What am I supposed to do about this failure? What is this test doing? Why is it here?
  • 103. DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED? How bad is it? What is this test doing? Why is it here? What am I supposed to do about this failure?
  • 104. DOES MARBLES RECOVER THE CONTEXT WE WANTED?DOES MARBLES RECOVER THE CONTEXT WE WANTED? Have we seen this failure before? When? What is this test doing? Why is it here? What am I supposed to do about this failure? How bad is it?
  • 106. ASSERTION LOGGINGASSERTION LOGGING import marbles.core from marbles.core import log   class TripDistanceTestCase(BikeshareTestCase): ...   if __name__ == '__main__': log.logger.configure(logfile='marbles.log') marbles.core.main()
  • 107. { "case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)", "test_case": "TripDistanceTestCase", "test_method": "test_for_long_trips", "assertion": "assertTrue", ... "locals": [ { "key": "severity", "value": "marathon" }, { "key": "threshold", "value": "42195" }, { "key": "long_trips", "value": " start station latitude stop station latitude ... 27955 42.366277 0.0 ... " } ], "month": "2016-07-01", "severity": "marathon", "anomalies": "1", "result": "fail" }
  • 108. Which test was running? { "case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)", "test_case": "TripDistanceTestCase", "test_method": "test_for_long_trips", "assertion": "assertTrue", ... "locals": [ { "key": "severity", "value": "marathon" }, { "key": "threshold", "value": "42195" }, { "key": "long_trips", "value": " start station latitude stop station latitude ... 27955 42.366277 0.0 ... " } ], "month": "2016-07-01", "severity": "marathon", "anomalies": "1", "result": "fail" }
  • 109. What did we assert? { "case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)", "test_case": "TripDistanceTestCase", "test_method": "test_for_long_trips", "assertion": "assertTrue", ... "locals": [ { "key": "severity", "value": "marathon" }, { "key": "threshold", "value": "42195" }, { "key": "long_trips", "value": " start station latitude stop station latitude ... 27955 42.366277 0.0 ... " } ], "month": "2016-07-01", "severity": "marathon", "anomalies": "1", "result": "fail" }
  • 110. Local variables { "case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)", "test_case": "TripDistanceTestCase", "test_method": "test_for_long_trips", "assertion": "assertTrue", ... "locals": [ { "key": "severity", "value": "marathon" }, { "key": "threshold", "value": "42195" }, { "key": "long_trips", "value": " start station latitude stop station latitude ... 27955 42.366277 0.0 ... " } ], "month": "2016-07-01", "severity": "marathon", "anomalies": "1", "result": "fail" }
  • 111. Which data were we testing? { "case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)", "test_case": "TripDistanceTestCase", "test_method": "test_for_long_trips", "assertion": "assertTrue", ... "locals": [ { "key": "severity", "value": "marathon" }, { "key": "threshold", "value": "42195" }, { "key": "long_trips", "value": " start station latitude stop station latitude ... 27955 42.366277 0.0 ... " } ], "month": "2016-07-01", "severity": "marathon", "anomalies": "1", "result": "fail" }
  • 112. Other information about the assertion { "case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)", "test_case": "TripDistanceTestCase", "test_method": "test_for_long_trips", "assertion": "assertTrue", ... "locals": [ { "key": "severity", "value": "marathon" }, { "key": "threshold", "value": "42195" }, { "key": "long_trips", "value": " start station latitude stop station latitude ... 27955 42.366277 0.0 ... " } ], "month": "2016-07-01", "severity": "marathon", "anomalies": "1", "result": "fail" }
  • 113. More (not pictured) { "case": "test_for_long_trips (test_bikeshare.TripDistanceTestCase)", "test_case": "TripDistanceTestCase", "test_method": "test_for_long_trips", "assertion": "assertTrue", ... "locals": [ { "key": "severity", "value": "marathon" }, { "key": "threshold", "value": "42195" }, { "key": "long_trips", "value": " start station latitude stop station latitude ... 27955 42.366277 0.0 ... " } ], "month": "2016-07-01", "severity": "marathon", "anomalies": "1", "result": "fail" }
  • 115. HISTORICAL FAILURESHISTORICAL FAILURES "Have we seen this kind of problem before?"
  • 116. HISTORICAL FAILURESHISTORICAL FAILURES "Have we seen this kind of problem before?"
  • 117. AGGREGATE DATASET HEALTH METRICSAGGREGATE DATASET HEALTH METRICS
  • 118. AGGREGATE DATASET HEALTH METRICSAGGREGATE DATASET HEALTH METRICS df = df.pivot_table( index=['month'], columns=['severity'], values='anomalies', aggfunc=sum) df.describe()
  • 119. AGGREGATE DATASET HEALTH METRICSAGGREGATE DATASET HEALTH METRICS df = df.pivot_table( index=['month'], columns=['severity'], values='anomalies', aggfunc=sum) df.describe()
  • 120. CONTEXT IS GOOD FOR SOFTWARE TESTS, TOOCONTEXT IS GOOD FOR SOFTWARE TESTS, TOO
  • 121. CONTEXT IS GOOD FOR SOFTWARE TESTS, TOOCONTEXT IS GOOD FOR SOFTWARE TESTS, TOO $ python -m unittest F ====================================================================== FAIL: test_return_code (docs.examples.getting_started.ResponseTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "/home/leif/git/marbles/docs/examples/getting_started.py", line 43, in test_return_code 201 AssertionError: 409 != 201   ---------------------------------------------------------------------- Ran 1 test in 0.000s
  • 122. CONTEXT IS GOOD FOR SOFTWARE TESTS, TOOCONTEXT IS GOOD FOR SOFTWARE TESTS, TOO $ python -m marbles F ====================================================================== FAIL: test_return_code (docs.examples.getting_started.ResponseTestCase) ---------------------------------------------------------------------- marbles.core.marbles.ContextualAssertionError: 409 != 201   Source (/home/leif/git/marbles/docs/examples/getting_started.py): 40 res = requests.put(endpoint, data=data) > 41 self.assertEqual( 42 res.status_code, 43 201 44 ) Locals: endpoint = 'http://example.com/api/v1/resource' data = {'id': 1, 'name': 'Little Bobby Tables'} res = <docs.examples.getting_started.Response object at 0x7fae97e78978>     ---------------------------------------------------------------------- Ran 1 test in 0.001s
  • 123. TWO STEPS TO MARBLESTWO STEPS TO MARBLES $ pip install marbles $ python -m marbles test_module.py
  • 127. CONTRIBUTING AND GETTING HELPCONTRIBUTING AND GETTING HELP github.com/twosigma/marbles/issues
  • 128. ✨ READ BETTER TEST FAILURES ✨✨ READ BETTER TEST FAILURES ✨ & github.com/twosigma/marbles marbles.readthedocs.io @thejunglejane @leifwalsh