Let's talk about your unit testing strategy. How do you select the tests you write, or do you even
write tests? Typically, when you write a test, you have to think about what you are testing and the
exact set of inputs and outcomes that you're looking for. And there are strategies for this.
Try to hit the boundary conditions, test the most common use cases, seek out error handling,
things like that. We all do this to varying degrees of success. But what if we didn't have
to do this? What if there is some kind of way to express relationships between inputs and outputs,
but your test could explore the problem space independently on its own? Well, there is a way,
and it's called property-based testing. This week, you'll learn about Hypothesis,
the most popular property-based testing system for Python created by David McLeaver.
This is Talk Python to Me, episode 67, recorded Monday, July 11th, 2016.
Welcome to Talk Python to Me, a weekly podcast on Python, the language, the libraries, the ecosystem,
and the personalities. This is your host, Michael Kennedy. Follow me on Twitter, where I'm at,
mkennedy. Keep up with the show and listen to past episodes at talkpython.fm, and follow the show
on Twitter via at Talk Python. This episode is brought to you by Hired and SnapCI. Thank them for
supporting the show on Twitter via at Hired underscore HQ and at Snap underscore CI. David, welcome to the show.
Thanks, Michael. It's a pleasure to be here.
I'm really excited to talk about this new angle on testing that I honestly just learned about a few
weeks ago. I'm really thrilled to talk about property-based testing, where the computer can
sort of do more of the heavy lifting and more of the automation, honestly, for us, right?
Yep, that's the idea. Sometimes when I'm feeling particularly salty, describe it as real automated
testing.
Yes, yeah. And we'll get into why that is and what that is. But before we do, let's talk about
your story. How did you get started in programming?
So I actually got started in programming for very selfish reasons, which was simply for the money.
I got out of university with a maths degree and didn't really want to stay in academia,
but was completely lost in anything fewer than 10 dimensions. So I had to figure out what to do
if that would actually work for me. And the options that came up were banking or software,
and software seemed the lesser of the two evils.
I would say you made a good choice.
And I found a nice little company that was happy to take a chance on me and let me learn to program
on the job. And it turned out I quite liked this programming thing. And ironically, having gotten to
it for the money, I've been spending a lot of the last year and a half doing work entirely for free.
But hey, it's been fun.
You know, that's a really interesting sort of view of things. And I kind of got into programming,
not I didn't think of it as for the money, but for the opportunity. I also was working on my PhD in
math. And, and I looked at the jobs and that how difficult and limited the jobs were people coming
out of you, like you said, you can basically go work at like a bank or an insurance company,
or you can be in academia. And it's, it's either super boring, you're working, you know,
like optimizing a little bit of some kind of insurance policy, or it's, it's extremely competitive.
And I decided to go into computers as well, sort of after the fact. And I had a similar experience
that I started working for a company that was willing to take a chance on me. I basically said,
look, I don't know much programming, but this one thing that you need, this is the one thing I can
actually do really well. You know, if you're willing to just let me do this one thing,
and then you give me some support, I can do a great job for you. And they did. And,
you know, it's no, no looking back. It's great. So yeah, very cool that you got that start that way.
Where'd Python come along?
Python originally came along just for another job. I've always been sort of a programming languages
polyglot and tends to just pick up languages either when they seem interesting or when I need them for
a particular problem or a particular job. In Python's case, it was a, it was a job. I had
been working in a company doing Ruby actually for a couple of years. But when I was looking to move on,
the job that seemed most interesting was one using Python. Python seems like a perfectly nice language.
I was very happy to learn it. So I did. I learned it in advance of the job rather than actually
starting from day one with zero Python knowledge. And musingly, a hypothesis was actually my learning
Python project originally. Wow.
It's so the version of hypothesis you see today isn't my learning, isn't my learning Python project
has been essentially completely rewritten since then. I wrote basically a prototype back in 2013,
when I was first learning Python. And then beginning of 2015, when I left another job and had some time on
my hands, I got frustrated that no one had improved on this silly little prototype I wrote. So I took
sort of the core idea and turned it into a real thing.
That's cool. Yeah. And it, you know, looking on GitHub, it's got over a thousand stars. It's,
it's definitely going, how long has it been sort of actively used or, you know, you said you sort of
did it as a learning thing and then you revamped it. Like when was that revamping and when is it kind
of taken off?
So people were actively using even the prototype, which is sort of what prompted me to do this because
Oh my gosh, people don't put another thing.
Yeah, exactly. It's, it was terrible at that point, but it managed to be less terrible than
all the alternatives. So I don't think it had a lot of users then, but I think it had maybe
five or something like that. I know that someone at Twitter once told me a couple of months ago that
they were just updating from hypothesis not 0.1.4, I think, which I think wasn't the 2013 version.
I think that was the first patch release I put out beginning of 2015, but there were definitely
some people using the ridiculous early prototype. In terms of serious usage, basically the first
three months, I think of 2015 were what it took me to go from the 0.1.x series to the 1.0 series.
And by that point, there were definitely a couple of significant users. I think Rob Smallshear was one of the early users of the 1.0 series and whose colleague Austin you had on recently, which is why I mentioned him. And there were another handful.
Yeah, yeah. The mutation testing show. Yeah, exactly. And there were another handful then. But 1.3, which I think happened April 2015 was probably, it's the first one that is really recognizable as sort of modern hypothesis. And I think that's when sort of the traction started
really, sort of when it really started building momentum, because early hypothesis, even in the 1.0 series was still a bit weird. And 1.3 was the point at which all of the visible weirdness went away. And I think it just became a really good, useful system. And people stopped having to scratch their head whenever they got started.
Yeah. I mean, people are still scratching their head a bit, but mostly because they're new to property-based testing rather than because hypothesis is weird.
Yeah, it's a really interesting idea. And I definitely want to talk about it. You know, maybe we should take just a little bit of a step back and talk about testing and property-based testing in general, and then we can dig into hypothesis itself.
So I have a lot of listeners that have a diverse background. So many programmers, but also scientists and data scientists and so on. So maybe not everyone fully gets the benefits of testing. So could you maybe just touch on three reasons why we care about testing that we'll dig into what is property testing and so on?
Sure. So I can give you one reason I care about testing, which is mainly it's about confidence. I was having a good discussion with some people recently about the fact that I do something slightly weird in the releasing hypothesis, which is that someone reports a bug. I go, here's a bug patch. I've done a patch release. Okay. New version is out. And that's fine. You can use it and install it.
And the reason I can do that is because I've got an incredibly comprehensive build and automated test. So if the build passes and if all the tests pass, then I can be basically sure that this code is, if not perfect, it's at least working as well as I've known previous versions to work and I can rely on it.
And for me, that's sort of the big thing about testing is that it gives me a reproducible idea of how well my software is working. And I'm free to make changes to it. I'm free to push out new releases because I know those releases aren't broken.
These days, trying to do any sort of software without that level of confidence feels really upsetting and alien to me.
I mean, I absolutely have some small hack projects which are terribly tested, but that's because they have a single user who is me and I don't really care if they break because I'll just fix them when they break.
But for anything where I want other people to be able to depend on me, tests are amazing for that.
Yeah, and if you're doing an open source project, having tests supporting it make it much more likely that people will have confidence in using your library, right?
If you have an actual product or website that you're shipping, it would be really nice to be able to say, you know, this morning I came in and there was this cool feature somebody wanted.
It took me about an hour. I added it. It has the test to support it. Let's push it live, right?
Rather than we're going to queue up all of our work and give it off the QA and in two months we'll get together over the weekend and take the site down and upgrade it, right?
Like those are super different philosophies and the first one deeply depends on automated testing and automated builds, I think.
Although ideally you wouldn't do the first one either because the problem with adding features that quickly is that you need to then support the features indefinitely.
Sure.
Features are made in haste and regretted at leisure. So I tend to do very rapid releases for bug fixes, but I'm much more cautious about adding new features.
Yeah, I guess there's two parts to it, right? The part that I was expressing is like the ability to make changes to code with confidence quickly.
And then there's the part of product design and evolution needs to be, you know, not done in haste, right?
Like it needs to be carefully thought through. Yeah. All right, cool. So what's the guy, we know what unit testing is and automated testing, but what is this property based testing? What's the idea there?
So one thing where I slightly differ in my framing of this from other people is that I don't regard property based testing as distinct from unit testing.
I think property based testing is an extension to your normal testing tools, but a property based test could be a unit test or it could be an integration test or it could be a functional test.
However, you want to draw those boundaries. It's property based tests versus example based tests are sort of orthogonal to the usual splits that people do for testing.
Right. It's like a layer that you can lay upon any of your testing layers, right? So if you're doing integration tests, you can add property based testing to that to get better coverage.
Yeah. Yeah. Yeah. And so I guess we need to have some terminology here that people may or may not be familiar with, like the traditional unit test with the three A's.
You know, you write out your test and you say, here's the the range. I'm going to set up my data. I'm going to act. I'm going to pass in this number and expect this individual specific value back.
And then I'm going to, you know, assert that that's true. Yeah. That is referred to as example based testing, right?
Yeah. That's certainly the term I use. And it's, it seems to be reasonably standard. Like a number of people have independently invented it. So I think that's probably the best terminology to use.
Yeah. When I first learned about property based testing, I didn't, I didn't know how to refer to it distinctly from, I didn't know how to call the regular or like we're sort of traditional unit based testing and what we're calling example based testing.
I didn't know like what the terminology for that was. It was hard to compare it. But once that sort of realized that that was how it was done before, it's like, here's one specific example of a test.
This number equals this value, but property based testing tries to cover a range or a set of constraints, almost like rules, right?
Yep, exactly. So it looks very like what you were describing, the example based test, but just you replace the steps slightly.
So the first step where you would set up the data yourself, instead you sort of describe to the testing library hypothesis in this case, how to set up the data that you want.
So instead of saying, I want this string, you say, give me a string here. Instead of saying, I want this user, you say, give me a user object here, that sort of thing.
And then at the end, instead of being able to say, you should get this exact value, you, well, I mean, sometimes you can say this, you should get this exact value if the exact value is one of the things you passed in or something you can easily compute to it from it.
Right. If I get a user that's not registered, not a pain user, I should be able to ask the question, is this a pain user? No. Something like this.
Yes, exactly. Yeah. Okay.
Could do an example based test, but it's often not worth doing if you just got a very small range of examples.
Right. A single example really would cover it.
So I think it might be worth to discuss, you know, it's hard to talk about code, but just to give people an idea of what this is like.
So we install hypothesis, the package, and then suppose I've, let's just take an example of, I have some kind of method and it's just calling a function and that function is supposed to return an even number.
And so I'm, I'm doing something like deriving from test case.
So self dot assert this, if I pass it five, it returns me like the number four, like the closest even number or something.
Like how would I change that code to, to become something property based with hypothesis?
So you would start by adding a given decorator, which is the main entry point hypothesis that describes essentially how to call your test function with a bunch of arguments that are your test data.
So you'd say given integers, for example, integers is a standard strategy that hypothesis provides for generating integers clues in the name.
But now you've got your test function, which has an extra argument and is being passed in integers via that argument.
You would then call your test, your method that you're testing and look at the result.
Right. And would you like add a parameter or something to that method or something like this?
Yes, exactly. You, you would add a parameter for the integer you want passed in and you would either pass in, either tell given to pass the parameter by name or positionally as you prefer.
Right. Okay.
But yeah, so then you call your function with the integer you've been passed in and you look at the value.
The first thing you would do is you would assert that the value is always even.
So value mod two equals equals zero.
And then if you wanted that it was always the closest even number, then what you could do is say assert that the absolute value of the difference of your argument and your return value is less than or equal to one.
Okay.
And then I don't change anything else.
Right. Like that's one of the beautiful things about hypothesis is it's not like, well, now use instead of using nose, you now use hype test or whatever.
Right.
It's just you run the test like unchanged basically.
Yeah.
Absolutely. It works with essentially any of the testing frameworks without you having to use its own test runner.
It's a little annoying in unit test because when it fails, it will print out the falsifying example sort of in the middle of your test runs because unit test doesn't have a good way of hooking into test output sensor plane.
Okay. Which one would you say is the best to connect it to?
pytest?
pytest is probably the best.
It works about, pytest has a special, has a plugin for it, but that does almost nothing.
So it works maybe 10% better with pytest than with nose.
And unit test is, or unit test isn't the least supported.
Unit test works fine other than the printing issue.
The least supported is probably Twisted's test runner because the asynchronous support and hypothesis isn't quite there.
Right. That's the most different.
You can do it okay with some hooks to basically turn the asynchronous test into a synchronous test.
So if you want to test asynchronous code from pytest or nose, that will mostly work.
But the, I'm temporarily blanking on what the Twisted test runner is called, but it's assumptions about how tests work or slightly different from the assumption, from hypothesis assumptions about how tests work.
Right. Okay. Sure.
You know, I think I got a pretty good handle on how it works, but a lot of people are hearing this for the first time.
When you run the test, basically the decorator wraps your test function and then it looks at the description and the parameters.
And then it comes up with ways in which it tries to break your test and it passes and executes it with many different values, right?
Yep. That's exactly right.
It doesn't so much come up with ways to break your test.
It's just try it with a large range of values, many of which are quite nasty.
So it doesn't currently sort of look inside your test and figure out what might break things, but it has pretty good heuristics for figuring out values that are like the break things.
Right. And, you know, I think one of the really important and maybe under, you know, it's hard to make generalizations for people, but under tested parts of code is the error handling and the off by one errors and just like right on the boundary of good and bad.
And it seems like hypothesis is really good at like looking for that and unusual inputs and values in general, right?
Yeah, it's definitely, there are a lot of things that hypothesis will try most times, which people sort of forget or a thing.
I don't know if this is actually true, but I often joke that about a third of the bugs that you'll find when running hypothesis on a new code base is that you forgot that something could be empty.
Yeah.
And the third is a made up number, but I've definitely seen this pattern a lot.
It's good at finding Unicode bugs because people often forget about particularly weird Unicode cases.
Although I found recently a class of bugs where it needs to be better at finding them.
Right. Okay.
Yeah. So for example, if I have like some kind of scientific thing that expects, you know, numbers, you know, maybe it assumes that they're all positive and hypothesis would of course pass negative numbers or zero.
But it also might pass like not a number, infinity, all sorts of funky inputs that people typically don't test for in example-based testing or if they even have these at all, right?
One problem I found is that a lot of people who are doing floating point maths don't actually care enough about getting the right answer to make using hypothesis really valuable.
Like if you do care about it and you're sort of prepared to do a numeric stability analysis on your code, then hypothesis is great.
For the most part, I think people who are using floating point kind of want to pretend that they're not.
And the edge cases that hypothesis will tell them about are somewhat unwelcome.
Right. So what if I have like some kind of scientific thing and I know it only expects, say, positive integers, but not infinity, something like this.
And is there a way in the given decorator to say, I want to give it integers, but only in say this range or something like that?
Yeah. So the strategy functions all come with a huge number of mobs to twiddle.
So the integers function accepts both min value and max value parameters.
And you can just specify those as you want.
So you can say min value equals one if you only want to strike the positive integers.
Similarly, floats have, the float strategy has both min and max value.
And you can also tell it, and don't give me infinity or don't give me none.
So you could do something like floats, min value equals zero, allow infinity equals false, allow none equals false.
Right. Okay. Yeah, that's really cool.
This portion of Talk Python to me is brought to you by Hired.
Hired is the platform for top Python developer jobs.
Create your profile and instantly get access to 3,500 companies who will work to compete with you.
Take it from one of Hired's users who recently got a job and said,
I had my first offer on Thursday after going live on Monday, and I ended up getting eight offers in total.
I've worked with recruiters in the past, but they've always been pretty hit and miss.
I tried LinkedIn, but I found Hired to be the best.
I really liked knowing the salary up front.
Privacy was also a huge seller for me.
Sounds awesome, doesn't it?
Well, wait until you hear about the sign-in bonus.
Everyone who accepts a job from Hired gets $1,000 sign-in bonus.
And as Talk Python listeners, it gets way sweeter.
Use the link Hired.com slash Talk Python to me, and Hired will double the sign-in bonus to $2,000.
Opportunity's knocking.
Visit Hired.com slash Talk Python to me and answer the door.
And another thing that I thought was nice on these, I don't know if this works for every strategy you'll have to tell me,
is if I have a set of whatever, like let's just stick with numbers for a moment, integers,
and I want to somehow control them in a way that is not just min-max or something like that.
Like I want only, I don't know, Fibonacci numbers or prime numbers or something like this.
Like you can do a dot filter and then add a lambda expression that will further, like say, you're going to give me a bunch of stuff,
but actually these are the only ones I'll allow you to give me.
Something like this, right?
So you can do that with every strategy, but for a lot of the examples you just gave, you shouldn't.
Because the problem with filter is that it's not magic.
Essentially the way it works is by generating values and then throwing them away and trying again if they don't pass the lambda expression.
So if you've got something like the Fibonacci numbers and you were trying to filter by a test is a Fibonacci number,
then you would basically, the only numbers you'd ever really find from there are the really small ones probably because high up they're too sparse.
But what you can also do is you can, instead of filtering, you can map.
So you could instead generate a positive integer and then say map that by a give me the nth Fibonacci number function.
So you would start by generating, say, 10.
This would get mapped through and you would get the 10th Fibonacci number.
Yeah, some huge number there.
Yeah, can you take like a bounded set of like here's 214 things that I think are valid inputs and please start from here.
Pick one of these.
Yep, absolutely.
The strategy is called sampled from in hypothesis.
Right.
You just give it an arbitrary collection and it will give you values from that collection.
And can you compose or combinatorially combine these?
Like let's say have like three sets.
One's got five, one's got two, and another's got three.
Yep.
Can I say I want to sample from this and this and this and who knows how many combinations that would have to be able to work that out?
But, you know, like it would figure that out and sort of pick some from here and there and test them together.
So if you want one from each, then you can use the tuple strategy, which you can basically just pass in a sequence of n strategies and it will give you a tuple of n elements with the sort of ith element drawn from the ith strategy.
Okay.
So in that case, you could use something like tuples sampled from, sampled from, sampled from.
Right.
Okay, cool.
And then what are some of the other strategies?
Like we're talking about numbers a lot, but there's more to it than that, right?
Yeah.
So there's strategies for most of the built-in types.
You've got, there's unicode, there's datetime, although the datetime one's in an extra package because it depends on PYTZ because no one actually works with datetimes without PYTZ.
You've got all the collections types.
So you've got the tuples one dimension, but you can also generate dicts of things.
You can generate sets, frozen sets.
You've got unicode and byte string generators, of course.
Permutations, right?
Oh, yes.
Yep.
You can generate permutations of a given list.
I don't think many people use that one, but I've occasionally found it super useful.
Right.
When you need it, I'm sure you're like, this is exactly what I need.
Yeah, yeah, exactly.
But not so often, right?
Yep.
And then you've got, those are sort of most of the primitive strategies.
I think there are a few more of them I'm temporary writing on, but essentially if it's in the standard library, you can probably generate it.
Okay.
And then on top of that, you've got various strategy composition functions, which is for things like the map and filter I mentioned.
You've also got the composite decorator, which lets you sort of chain together a bunch of strategy generators and generate whatever data you like.
And you've got builds, which is a bit of a simpler version of that.
And there's also, there's special Django support in, again, in an extra package so that it can generate more or less arbitrary Django models without you having to specify anything in particular.
Right.
You just say models from my model class and it will do the rest.
You can override any of the rest it does, but by default it works reasonably well out of the box.
That's cool.
So if I had like a users table and I wanted to sort of replace that and not actually talk to the database, but I want to test the code that works with those, I could have some kind of, some kind of thing that would randomly generate users that match my data model.
Yeah.
It actually does talk to the database.
It works with the normal Django testing and the model objects it generates are persisted in the database.
Okay.
Interesting.
Yeah.
So this is real Django.
Yeah.
Rather than any sort of mocking.
I see.
Okay.
Well, very cool.
Very cool.
One thing that I thought was neat is, is you can say at given, you give it this strategy.
You say like, so if you're given an, you know, some set of, you know, something from the set of integers and something from the set of floating points where, and do like a filter, like, you know, some, some property on your floating points.
It's going to randomly pick a bunch of combinations and try them.
And that's cool.
But maybe, you know, maybe you're coming from a particular example-based testing scenario where like this combination of numbers for whatever reason is super important.
It's important to test.
And it has to be this.
And so like, in addition to all the randomness that you're doing to sort of explore the problem space, you can say, you can give an example decorator, right?
And say, and also include this particular test that I was testing before, say, if you're like upgrading to property-based testing.
Yep, absolutely.
The example decorator is sort of one of those little touches that as far as I know is unique to Hypothesis, but was in retrospect, obviously a good idea.
The original use case was more for people who wanted to include the examples that Hypothesis had found for them in their source code.
Mm-hmm.
But it found a whole bunch of other applications.
Like one is the one you mentioned of making it much more comfortable to transition from example-based testing.
Another thing that I sometimes do, and I've seen a couple other people doing, is that you use example to ensure that the test always gets the maximum coverage that the test can reach.
So if there's some line that your test covers 80% of the time, then you just add an at example that touches that line and so means that your coverage isn't varying from test run to test run as a result of Hypothesis if you do that.
Interesting.
Yeah, that's really cool.
Can you use it as a negative, a negation operator to like do all this stuff but don't send it this example?
Or is there some way to say this?
I'm not sure that that would make any sense.
You can't do that with example.
That's what the assume function is for.
It basically gives you a way of filtering out some classes of example as not good examples that you don't really care about.
I see.
Is that like a precondition sort of concept?
So I could say like if I was generated an automatic user, I would say and assume the user was generated today or assume that their age is over 18 or something like that.
Yeah, exactly.
It is essentially a precondition, although you can put it anywhere in the test.
So sometimes you would put it in the middle of a test when some previous operations produced a result that you don't care about.
So you could do a bunch of, say you got a list of users and you did a bunch of calculations and you would then assume that, I don't know, you have at least one of the users passed through the test that you just, in the past through the code you just ran and you haven't sort of unregistered all the users or something.
Yeah, sure.
Okay.
I think that's really useful and helpful.
Does hypothesis ever look at the code or the code coverage or anything like this?
Or does it just work black box style?
I'm going to put stuff in and see if it crashes.
So it currently just works black box style.
I've got most of the pieces for making it work in a less black box style.
In fact, I've got a separate library I wrote called Glassbox, which is designed for adding sort of glass box elements to your testing, which uses some coverage information.
A lot of hypothesis internals look quite a lot like the internals of a fuzzer called American Fuzzy Wop, which has this really nice coverage-based metric.
But the reason why the coverage stuff hasn't ever made it into a hypothesis is because coverage metrics work really well when you've got some sort of long-running fuzzing process.
So if you're prepared to run your tests for a day, then coverage metrics are amazing and will do a really good job at poking inside the code and seeing what is going on.
But if you are sort of upset, if your tests take as long as 10 seconds to run, then coverage starts to become less useful because it doesn't really have the time to actually figure things out.
I think I've mostly figured out a way of making use of coverage in a way that works okay within those time constraints, but it hasn't made it into anything production-like yet.
Okay.
Well, that sounds like a cool direction.
However, I have been saying that for about six months, so I wouldn't hold your breath on waiting for those features.
Yeah, of course, of course.
So it seems that it would be interesting for Hypothesis to remember scenarios it came up with that failed.
Like, is there a way to say, try all these examples, and if you ever discover one, you know, save it to something that you're going to know to replay?
It actually does that out in the box.
Okay.
Hypothesis has a database of examples.
Well, I say database.
It's basically an exploded directory format that is designed so you can check it into Git if you want.
But whenever a test fails, then what Hypothesis does is, well, first of all, it shrinks the example.
So it turns the example into a simpler one.
And then in the process of doing that, every example it sees fail is saved in this database so that when you rerun the test, it replays those examples, starting from the simplest and working its way upwards.
So whenever, basically, one of the things that I've made really sure in Hypothesis is that although it's randomness, it's sort of the good kind of randomness because it finds bugs, but it never forgets bugs.
If the test fails, then when you rerun it, the test will continue failing until you fix the bug.
Yeah, because if it didn't remember it, I guess, like, suppose the number 13, for some reason, broke your code, and you just said the strategy was integers.
Like, how would it know to try 13 again if it's, like, really going that, you know, out to that many numbers and so on?
So, okay.
Because it did seem to keep failing consistently.
And I guess I kind of clued in, like, that is a little odd that it seemed just, I'm like, oh, it's really smart.
Yeah.
But I didn't put it together how it's smart, of course.
For a lot of tests, the failure is common enough that even without the database, Hypothesis would be able to do this and would be able to find the test, find a failure consistently each time.
But one of the annoying things that happens there is that sometimes, if it didn't have the database, it would be finding often different bugs each time.
Because you don't always shrink to sort of the globally minimal failure.
And sometimes there are two bugs, and starting from one will shrink one way, and starting from another will shrink the other way.
So, in many ways, like, that's almost more annoying than it failing unreliably.
Because you don't know whether you've just changed the bug or whether you've introduced a new one or what.
Gone are the days of tweaking your server, merging your code, and just hoping it works in your production.
With SnapCI's cloud-based, hosted, continuous delivery tool, you simply do a git push, and they auto-detect and run all the necessary tests through their multi-stage pipelines.
Something fails?
You can even debug it directly in the browser.
With a one-click deployment that you can do from your desk or from 30,000 feet in the air, Snap offers flexibility and ease of mind.
Imagine all the time you'll save.
Thanks, SnapCI, for sponsoring this episode by trying them for free at snap.ci.com.
One of the things I think is interesting is the scenarios we've been talking about so far.
They've been, I give you two numbers, something happens.
The other side, it gives you the right answer, the wrong answer, and the test detects that.
But I think one of the things that seems very powerful is for sort of stateful testing and these set of steps.
Like, let's suppose I have a board game, and I've got to move things around the board in a certain way,
and some property is supposed to always hold like, you know, the number of chips on the board is always the same,
even if it comes from one player comes off and another has to go back on from the other player, right?
Who knows? I have no idea what game this is.
But, like, you could write a test to say, do all these operations, and then keep asserting that this is true.
And if it fails, it gives you a really nice reproducible output, right?
A set of steps almost, right?
The output format is a little idiosyncratic, so it's not, unfortunately, something you can just copy and paste into a test currently.
But, yeah, the stateful testing is really cool.
One of the reasons I don't push it quite as hard as I could is that I don't feel like we've got very good workflows for this right now,
because the stateful testing sort of, it's exploring an incredibly large search space.
So you do want something that's more like the fuzzing workflows I talked about, where you do want to set it running for 24 hours or whatever.
You can use it in your CI, and I'm using it in my CI in a few places, and at least one other project I know of is using it, but I wrote those tests.
That's material.
But the, I think it's not quite there yet in terms of being as, it's certainly not as usable as the rest of the library.
And I think it has some more work required before it gets there.
But I'm really excited about it, and I do want to sort of spend more time developing that.
Yeah, the possibilities of it are really amazing.
It's just, like you said, it's such a problem space that how are you going to find it, right?
In many ways, my crack about hypothesis being true automated testing, it's really only true for the stateful testing.
Because one of the things I emphasize sometimes is that hypothesis doesn't write your test for you.
You're still writing your tests.
It's just the hypothesis is doing some of the heavy lifting in terms of the boring coming up with examples.
But the stateful testing, it's sort of, that's almost no longer true.
At that point, hypothesis is almost writing tests for you.
Right, yeah, that is actually really cool.
But I think even so, that level of automation that you talked about that it's already really good at is super helpful.
Because coming up with those examples is hard.
And like I talked about earlier, I think coming up with examples that are just inside the working realm and just on the edge of the error conditions or these weird inputs, you know, those are hard to come up with.
And you just, I think there's just fatigue as well.
Like, okay, I've tested three cases.
Like, that's probably good enough.
Let's just move on to building new features, right?
Whereas property-based testing will sort of explore that space for you automatically, right?
Yeah, yeah, absolutely.
The fact that it's, the fact that you're still writing the test isn't intended to take away from what hypothesis is doing.
The coming up with examples part is both the most time-consuming part and the most boring part.
And also, it's the bit that people are really bad at.
So having software which can take this boring, difficult task and just do it for you is amazing.
And I get really frustrated when I have to write tests that I don't have the capability for now.
Yeah, I'm sure you do.
And the fact that the problems that it finds get permanently remembered, that's really cool.
So do you recommend people check in their .hypothesis folder into Git?
It's designed so that you can do that.
But I mostly don't do it myself.
I generally think that you're probably better off writing at example decorators for any example you specifically want to be remembered.
The main problem with the example database is that its format is quite opaque.
You can't really look at a file in there and say, I know what example this corresponds to.
So even though from a computer's point of view, it will remember what you need and it will do what you want,
from a human's point of view, you probably want to be more explicit than that.
One of the things I'd like to work on at some point but haven't sort of found the time or bluntly the customers for doing this work
is better sort of centralized management of test examples so that you can have your cake and eat it too.
And rather than checking it into Git, you can have a nice management interface where you can see what hypothesis has run
and get both the memory and the visibility.
But that's sort of a, at some point in the future project.
It's not anything on the short-term roadmap.
Yeah.
Well, even something automated that would take the hypothesis database and inject them as ad examples into your code would be cool.
Yeah.
Because the major problem is that there's no real way of taking a Python object and going from that to a literal that you can just copy and paste into your code and it will produce that object.
You can do it for really simple things.
So a lot of the built-in types, the wrapper will do that.
Yeah.
Basically, the wrapper output is parsable.
It's probably okay, but that's often not the case, especially for custom types.
Yeah.
I would say it's almost never the case for custom types.
One of my idiosyncrasies as programmers, I do try to make sure that all my types have good wrappers and that will evaluate the thing you started with.
But this is very rarely the case in the wild.
Yeah.
Sort of one of the cute little details in hypothesis that I spent far too much time on for what it's worth.
But almost any of the strategies you get out of the standard hypothesis strategies will give you a very nicely formatted wrapper that will exactly reproduce the strategy.
And this is true even up to the point of if you filter by a lambda, it will give you the source code of the lambda in the wrapper for the strategy.
Oh, that's great.
It's all a bit ridiculous and I really don't recommend emulating it, but every time I see it, it makes me smile.
Yeah, yeah, I'm sure.
That's cool.
So, you know, one thing that I seem to hear a lot about when I was looking into property-based testing was there seemed to be a set of like patterns that people come across that seem to repeat themselves that the property-based testing is well served.
Yeah.
Can you talk about some of those?
Do you mean like sort of standard styles of tests that?
Well, I'm thinking like one of the things people often say is a really good type of thing to send to turn this type of system onto is serialization and deserialization.
Or upgrading from like a legacy code to a rewrite.
You could be able to like pull an old library and always ensure that the same inputs here get the same outputs and the same thing in the new system.
Or if I have a really simple algorithm, I'm optimizing using the simple slow version to verify the fast version.
Things like this.
Yeah, absolutely.
So sort of the absolute best thing to test with property-based testing in general is these two things should always give the same answer.
Because it covers such a wide range of behaviors and gives you so many opportunities to get wrong.
And particularly for the sort of the optimized and naive version of an algorithm is great because often they're very different styles of algorithm.
So what you're essentially testing for is have I made the same mistake on both of these things?
And usually you'll make different mistakes.
And so a test failure is either a bug in your optimized one or your naive one.
Right.
It's almost like double-check accounting.
It doesn't necessarily mean your new one is wrong, but something needs to be looked at and it's who knows.
Yeah.
Yep.
One of the things that I've been trying to do with the new-ish hypothesis website, hypothesis.works, is gather a family of these different properties.
Because relatively few of them have been written down.
And there's a blog post scattered across the internet with some of them.
And there are a few really good prior articles.
But a lot of them are either folklore, which hasn't been written down, or start with this long diatribe about category theory.
Yeah.
And I'm not against category theory, but I don't really use it myself.
And I think it tends to scare people off.
So most of the time I'm just starting with, here's a concrete problem.
Let's solve it in Python.
Here's how you test it with hypothesis.
Oh, there is one pattern that I've noticed recently, which is either original to me or is an independent reinvention that no one else has written down before.
But I really like this as a style of testing, which is rather than these two things should give the same thing.
It's if I change this data, then this should move in this direction.
Okay.
So you generate some data, you run a function on it, and you then change the data in some way, and you run it again.
And the change in the output should in some way be reflective of the change in the input.
I originally came up with this for sort of optimization problems, where you run the optimizer and you make some changes, which should make the problem harder.
And you assert that the score of the output doesn't get better.
Or you make the problem easier and you assert that the score of the output doesn't get worse.
But I also had a nice example recently with binary search, which is if you run a binary search, then you insert an extra copy of the value at the point that you search to.
Then this shouldn't change the output of the binary search, because it's only sort of shifted stuff to the right.
Right. Exactly.
And so sort of in general, looking for things where functions should move in predictable ways and end up moving in ways that you didn't expect.
Okay. Interesting.
Like I increased the tax rate in my e-commerce system and the price went down.
What happened?
Or the price didn't change, for example.
Oh, whoops.
We're not actually including the tax.
Who knows?
Yep.
Exactly.
Yeah.
Very interesting.
So I think property-based testing and hypothesis is really exciting.
I think it, in a couple of ways, I think it means that testing is more effective.
And I think that it's less work to write those tests.
So that's like a perfect combination.
Have you done anything like, just as a proof of concept, like grab some popular open source project that has good test support and like convert its test to hypothesis tests and found new bugs or anything like that?
I haven't personally.
One of the problems here is that, as like with any testing, it's very hard to test a project you don't understand.
So I generally don't go into other people's projects and try and write tests for them.
I've done it once or twice.
A customer paid me to do some testing work on Mercurial and add some tests to that, which was interesting.
Did you find any bugs?
Yeah.
So I found a bunch of bugs, actually.
Wow.
None of them particularly critical.
But some of the, so the encoding one came up.
Mercurial has a bunch of internal encoding representations.
There is, for some reason, Mercurial has three different JSON encoders.
And we found bugs in one of them.
And there is some stuff where Mercurial wants to represent things as UTF-8B, which is a way of taking arbitrary binary data and turning it into valid UTF-8 encoded text and backwards.
And that sort of hit, that had a bunch of bugs.
I don't think that's used very widely, but it still had a bunch of bugs.
Yeah.
And then we used the stateful testing for sort of validating repository operations and found two interesting bugs in the HDShelv extension, which is, HDShelv is basically GitStash for Mercurial.
I'm sure someone will be mad at me for saying that, but that's basically what it is.
And I can't remember except, I think one of them was that the set of valid shelf names was narrower than the set of valid branch names.
And so you could create a branch which had a name that wasn't a valid shelf.
And then when you try to shelf stuff, it would default to using the branch name for the shelf name and everything would go wrong.
And the other was, it was something like if you create a file, then immediately delete it.
And without committing the delete and then try to shelf stuff.
No, sorry.
You delete the file, then you create a new file with the same name, which is untracked.
And then you try and shelf stuff.
And the shelf extension gets itself into a complete testing.
Wow.
It seems really interesting.
I'm sure lots of those types of tests were already there, but just those particular cases weren't found.
There was an example, there was a talk at PyCon this year by Matt Bachman.
And he talked about property-based testing on his project actually uncovered a bug in DateUtil.
Because his project was depending on DateUtil and some, you know, your system sent some kind of insane date time, like, you know, before BC or something like this.
And it freaked it out.
Yeah.
So, which they then fixed in DateUtil, which is really cool.
Mm-hmm.
Yeah.
The, I've occasionally thought about making the hypothesis dates a little more restricted by default.
Because, by and large, no one really cares about the representation of 9000 BC or 9000 AD.
Because they were, they'll be using a different calendar or they were using a different calendar then anyway.
But it does come up with these fun bugs.
So, I sort of, I've left it in for now.
Okay.
Yeah.
Cool.
I think, I vaguely recall the bug in question.
I think that one might have actually been slightly more reasonable dates.
Like, I think it was first century AD that went wrong or something.
I vaguely recall there being one where if you try and represent the year 99, then it assumed
that you meant 1999 or something like that.
Yeah.
It's cool.
That's what I kind of asked you about the open source projects.
because I think, you know, it would be fun to take, fun in quotes, to see the results of somebody taking all the tests for, say, like the top 100 most popular PyPI packages.
Go and look at their test suite and convert their example-based testing to property-based testing and just see what that spits out.
Yeah.
In general, what I would recommend in that space is more, if you're working on a popular Python package and you want to give that a try, pop into IRC or send me an email and I'd be very happy to help you out.
Okay.
Because I think, I really do think that what you need for doing that is more experience in the project than it is experience with hypothesis.
Of course.
Understanding the domain of what you're trying to actually test, yeah.
Okay.
Cool.
Well, we know quite a bit about property-based testing now, but maybe tell me a little bit about yourself.
What else do you do in the programming space for work and things like this?
So this is more or less my day job.
I've been working too actively on hypothesis in the last couple of months because I've been sort of researching some related things.
But basically, I do independent R&D and testing tools and I do consulting and training around that, either helping people to use hypothesis or helping people to improve their testing in other ways.
Historically, I've done more sort of back-end data engineering.
But once I sort of got into hypothesis properly and found that I really liked working on this sort of thing and there was demand for it, that's mostly what I've been doing.
Yeah.
I can see a huge demand of companies that have large projects that have tests, but not this kind of test.
They're like, you know, it might be really useful to spend two weeks with you just giving another go with a different system, right?
With hypothesis.
Yeah.
Well, I wish there were a huge demand.
There's huge demand for hypothesis.
I've currently got the thing that I think most new businesses have in their first year where the best way to get new customers is to have existing customers.
So it turns out that sales and marketing are hard.
So right now, I would say that I'm experiencing demand, but not huge.
Right.
Okay.
It may take a few years to get there.
Yeah, but it's a cool project and I definitely can see it growing in the future because it's solving a real problem and it solves it in a better way than what we're doing today.
Well, I mean, hypothesis itself is getting plenty of demand.
I think PyPI stats are broken right now, but certainly when they were last working, it was getting quite a respectable number of downloads compared to projects that I'd really thought of as being much more popular than they were or much more popular than hypothesis.
That's awesome.
Well, yeah, congratulations on it.
That's cool.
Thank you.
All right.
We're getting near the end of the show.
I have two questions I always ask my guests.
First of all, if you're going to write some code, what editor, specifically Python, but in general as well, what editor do you open up?
Basically, Vim.
I've been experimenting with using Windows as my primary operating system recently, and I was trying PyCharm with Vim mode, but I've ended up mostly just going back to Vim even on Windows.
Right.
Okay, cool.
And of all the PyPI packages out there, you know, what one, maybe there's, you know, there's over 80,000.
There's a bunch that we all have exposure to that aren't necessarily mainstream, but you're like, wow, this is really a gem that I found that people didn't know about.
In addition to hypothesis, so in addition to pip install hypothesis, which is cool, what else, what would you recommend?
Most of my, I don't really have any niche packages I can recommend in that regard.
I really like Py.test and coverage is an exceptionally good piece of work.
Both of these are quite well known and unsurprising opinions.
There's actually a package by Matt Pacman, I think, called diff cover that I keep meaning, I haven't used myself, but it looks really good for putting your CI.
So when you want, say, 100% coverage, it's very hard to get to 100% coverage in a single leap.
So what you do is you set ratcheting and just say, I don't care what the coverage currently is, but you can never make it worse.
And so diff cover is just a nice little tool that is designed to help you configure that ratcheting on coverage or on pepH checks or things like that.
Nice.
So you can basically enforce the rule, like coverage always needs to get better as our project grows.
Or at least never gets worse.
Right.
Okay.
But I don't currently use that because I already have 100% coverage and play gate cloneness on all my main projects.
But if I were coming into someone's existing project, which was large and had a slightly less good state, I would absolutely recommend checking something like this out.
Oh, that's excellent.
Thanks for the recommendation.
All right.
Any final call to actions?
How do people get started with Hypothesis?
So getting started with Hypothesis, what I would really recommend is just checking out the Hypothesis.Works website and sort of getting a feel for it, trying it out a bit.
And then if you are a company who wants to improve your testing with Hypothesis, I would very strongly recommend hiring me to come in and either do a consult or run a training workshop.
One of the workshops I run is basically an exploratory thing where you stick me in a room with 10 devs for a day and we figure out how to write a whole bunch of new tests for your software.
And we usually find some interesting bugs, particularly in the Unicode handling.
Well, most of the time we've done it, but also in other weird edge cases that you wouldn't necessarily have thought to test.
Yeah, that's awesome.
I'm sure you do find some weird ones.
That's great.
All right.
Well, David, thanks so much for being on the show.
It's been great to talk to you.
Thank you very much, Michael.
It's been a pleasure.
Yeah, bye.
Bye.
This has been another episode of Talk Python to Me.
Today's guest was David McClaver, and this episode has been sponsored by Hired and SnapCI.
Thank them both for supporting the show.
Hired wants to help you find your next big thing.
Visit Hired.com slash Talk Python to Me to get five or more offers with salary and equity presented right up front and a special listener signing bonus of $2,000.
SnapCI is modern, continuous integration and delivery.
Build, test, and deploy your code directly from GitHub, all in your browser with debugging, Docker, and parallels included.
Try them for free at snap.ci slash Talk Python.
Are you or a colleague trying to learn Python?
Have you tried books and videos that just left you bored by covering topics point by point?
Well, check out my online course, Python Jumpstart, by building 10 apps at talkpython.fm/course to experience a more engaging way to learn Python.
And if you're looking for something a little more advanced, try my WritePythonic code course at talkpython.fm/Pythonic.
You can find the links from this episode at talkpython.fm/episodes slash show slash 67.
Be sure to subscribe to the show.
Open your favorite podcatcher and search for Python.
We should be right at the top.
You can also find the iTunes feed at /itunes.
Google Play feed at /play.
And direct RSS feed at /rss on talkpython.fm.
Our theme music is Developers, Developers, Developers by Corey Smith, who goes by Smix.
Corey just recently started selling his tracks on iTunes, so I recommend you check it out at talkpython.fm/music.
You can browse his tracks he has for sale on iTunes and listen to the full-length version of the theme song.
This is your host, Michael Kennedy.
Thanks so much for listening.
I really appreciate it.
Smix, let's get out of here.
Stating with my voice, there's no norm that I can feel within.
Haven't been sleeping, I've been using lots of rest.
I'll pass the mic back to who rocked it best.