We write tests to show us when there are problems with our code, but what if there are intermittent
problems with the tests themselves?
That can be a big hassle.
In this episode, we have Gregory Kapphammer and Owen Perry on the show to share their
research and advice for taming flaky tests.
This is Talk Python to Me, episode 429, recorded August 10th, 2023.
Welcome to Talk Python to Me, a weekly podcast on Python.
This is your host, Michael Kennedy.
Follow me on Mastodon, where I'm @mkennedy, and follow the podcast using @talkpython,
both on fosstodon.org.
Be careful with impersonating accounts on other instances.
There are many.
Keep up with the show and listen to over seven years of past episodes at talkpython.fm.
We've started streaming most of our episodes live on YouTube.
Subscribe to our YouTube channel over at talkpython.fm/youtube to get notified about upcoming
shows and be part of that episode.
This episode is brought to you by JetBrains, who encourage you to get work done with PyCharm.
Download your free trial of PyCharm Professional at talkpython.fm/done dash with dash PyCharm.
And it's brought to you by Sentry.
Don't let those errors go unnoticed.
Use Sentry.
Get started at talkpython.fm/sentry.
Owen, Gregory, welcome to Talk Python to me.
Hi, Michael.
It's great to be on the show.
Thank you for inviting us today.
Hi there.
Thanks for having us.
Really great to have you both on the show.
It's going to be a bit of a flaky episode, though, wouldn't you say?
It's definitely going to be flaky.
Very flaky.
Looking forward to talking about flaky tests and what we can do about them.
It's one of these realities of writing lots of unit tests for real world systems, right?
They end up in weird places.
So for better or for worse, I've implemented a lot of programs in Python, and many of them
have test suites with flaky test cases inside of them.
So I have to confess to everyone, I myself have written programs with many flaky tests.
As have I.
All right.
Before we get into the show itself, maybe just a little bit of background on you two.
Gregory, you want to go first?
Just a quick introduction about who you are, how you got into programming in Python.
Sure.
My name is Gregory Kapphammer, and I'm a faculty member in the Department of Computer Science
at Allegheny College.
And I've actually been programming in Python since I took an AI course in graduate school years
ago, and we had to implement all of our algorithms in Python.
Stopped using Python for a short period of time and then picked it back up again once I learned
about pytest because I found it to be such an awesome test automation framework.
And I've been programming in Python regularly since then.
Oh, that's cool.
What's pretty interesting is people who don't even necessarily do Python sometimes use Python
to write the tests.
Yeah, absolutely.
I have to say I've used a bunch of different test automation frameworks and pytest is by far
and away my favorite framework out of them all.
Owen, hello.
Hi, I'm a PhD student at the University of Sheffield.
I'm actually coming right to the end of my time as a PhD student.
So that's quite a journey, isn't it?
It is quite a journey.
Yeah.
And throughout my whole PhD, my main topic has been flaky tests.
Okay.
So before I even started my PhD, I had Python experience from just odd undergraduate projects
that had to be done in Python.
But for all of my research, I've thought it very important to use real software, real tests
written by real people to identify flaky tests, find out what causes them, that kind of thing.
And all of my sort of practical work has been done with Python.
So for example, I've written pytest plugins to help detect flaky tests.
And as Greg said, I think pytest is a great framework.
It's very extensible.
It's very great for writing plugins, very good API.
And yeah, I've used lots of different types of Python projects as subjects and experiments.
So I've seen quite a wide array of different types of software written in Python.
Have you studied a lot of other people's software,
interviewed different people to see how they're encountering flaky tests?
So I've not interviewed anyone exactly,
but I did do a questionnaire that I sent out on Twitter, LinkedIn, that kind of thing,
where we just wanted to get as many different kinds of developers talking about flaky tests.
So we asked them questions like, first of all, what is a flaky test?
People have slightly different definitions.
And then I went into, you know, what do you think causes them?
What impacts do they have on you and your sort of professional workflow?
And then we talked a little bit about what people do about them as well.
Well, maybe that's a good place to start.
And either of you just jump in as you see fit.
So let's just start with, you know, what is a flaky test?
I mean, we all know that having unit tests and broader tests, you know, integration tests and so on,
4R code is generally a good thing.
I guess if you write them poorly enough, it's not a good thing.
But, you know, mostly it's recommended advice and there's a spectrum of how strongly it's recommended.
You know, is it extreme programming, TDD recommended, or is it you have to have some tests to submit a PR level recommended?
But we think it's great.
But there are these negative aspects of having tests as well, right?
It's easy to sell them as a positive, but they become a maintenance burden.
They become duplicate code.
And the flakiness, I think, is a particularly challenging part of it.
Let's start there.
What's a flaky test for you all?
So I'll start off and then, Owen, if you'd like to add more details, that would be awesome.
I would say that a flaky test is a test case that passes or fails in a non-deterministic fashion, even when you're not changing the source code of the program or the test suite for the program.
So this is a situation where the test case may sometimes pass, then it may fail, and then it may start to pass again, even though, as a developer, you're not making changes to the program under test or the source code of the test suite itself.
Yeah, that is tricky, right?
Because just one day the tests start failing.
We didn't change anything.
Nothing has changed here.
Why?
How could this have anything to do with it, right?
Owen?
Sorry, go ahead, Greg.
The only other thing I was going to add is that flaky test cases could manifest themselves on a developer workstation, or they could also manifest when you're running them in a continuous integration environment as well.
Yeah, for sure.
So to just sort of build on what Greg said there a little bit.
So one interesting thing we found from that developer survey.
So the definition that Greg gave just then was pretty much the definition we proposed to the respondents to the survey.
And then we asked them, do you agree?
If not, what's your definition?
Most people agreed, but some people said, well, it doesn't just depend on the source code of the program and the code of the test, but also the execution environment.
So you can take one piece of software and its associated test suite, run it on one computer, it passes, run it on another system, and then for whatever reason, it doesn't.
So nothing's changed at all except for the execution environment.
And that's quite prevalent when you're talking about CI, because most of the time these are running on cloud machines, which may not be all exactly the same in spec.
From the perspective of the developer, it looks as if a test could just fail, but maybe it failed because it was running on a slightly different machine that time it was run.
I hadn't really thought of that.
Obviously, the different environments, right?
Like a CI machine is very different than my MacBook.
So clearly, it could be the case that a test passes on my machine, not in CI or vice versa.
But I hadn't really thought about, well, this time it got an AMD cloud processor, and it was already under heavy load.
And so the timing changed versus the other time it was on like a premium Intel thing in the cloud that had no other thing going on.
So it behaved differently.
Yeah.
That's pretty wild.
Your point is a good one, Michael.
It's actually often the case that the speed of the CPU or the amount of memory or the amount of disk on the testing workstation can make a big difference when it comes to manifesting a flaky test.
Yeah.
I guess that makes a lot of sense, especially if it's a race condition, right?
If you're having some sort of parallelism, then that could really come into memory as well, maybe.
Maybe you run out of memory and get an out of memory exception.
So when we're talking about flaky tests, one thing that came to mind for me, and I want to bring it up at the start here, because I'm wondering if this classifies as flaky for you, if this is some other kind of not great test, is has to do with science, right?
So scientific type of computing, mathematical stuff.
Obviously, you shouldn't say I've got some floating point number equal, equal, you know, some other long, precise, you know, here's my definition of the square root of two as a float equal, equal that, right?
That might be too precise.
But what I'm thinking about is if you make the smallest amount of change to some algorithm or the way something works could change some, like maybe you're trying to say, does this, do we get a curve that looks like this?
Or do we match some kind of criteria on, you know, statistics?
It could change just a little bit, but the way that you're testing for it to be a match, it changes enough in that regard, even though effectively it kind of means the same thing.
You know what I'm asking?
Does that count as a flaky test for you?
So what you're talking about is a very specific category of flaky test.
So I would call that a flaky test.
So yeah, so when you're dealing with programs, like for example, various machine learning packages that are very common in Bison,
You'll see a lot of test cases that will say assert X is equal to this within a certain tolerance range or something, or is approximately equal to something.
With these kinds of tests, they are kind of inherently flaky.
There is a trade-off between how strong you want the test to be.
So IE how narrow you want that acceptable band to be versus how flaky you want it to be.
Sorry, how flaky you don't want it to be.
So the stronger you make the test, the more flaky it's likely to be because that band is narrower.
But if you just increase that tolerance, then yeah, it won't be as flaky anymore.
But then maybe it won't catch as many bugs anymore because it's too relaxed of a test.
Sure.
And unfortunately, if you're going to reduce a test case to either a pass or a fail, which is something that we have to do, there's no real way around that.
There has been done work where people have sort of tried to calculate via, there we go.
It's on a motion sensor.
Got away with the light.
It's going dark for you.
I was like, yeah.
So there is work people have done where they try to calculate sort of what is the best sort of tolerance threshold in test cases,
but there's no kind of silver bullet solution for that kind of flakiness.
Yeah, it's tricky, isn't it?
Yeah.
Pradvan out in the audience has an interesting one.
It maybe probably lands a little more into the realm directly of the kind of stuff that you're talking about.
Says at one of my jobs testing, we were testing chained celery tasks that become flaky sometimes since one celery task fails.
For some reason, the chain task could fail as well.
Like those kind of external systems are probably at the heart of a lot of this, right?
Yeah, I think it is often at the heart of it.
And whether you're using celery or some other work processing queue, or alternatively, if you're interacting with a document database or a relational database,
in all of those occasions, when you interact with some storage or processing system in your environment,
you may not have control over that part of your environment.
And then once again, that's another way in which flakiness can creep into the testing process.
Right.
And you maybe don't care about how the celery server is doing, but at the same time, you maybe you haven't mocked that part out.
You need to somehow, are you doing an end-to-end test or something?
It becomes part of the reliability of your system.
Even though maybe that's like a Q&A celery server, not the production server, right?
Yeah.
And in fact, you brought up another really good point when it comes to mocking.
There are many circumstances in which I've had to use one of the various mocking features that Python provides
in order to stand up my own version of an external service.
However, that is a trade-off like Owen mentioned previously, because now my test case may be less flaky,
and yet it's also less realistic, and so therefore may not be able to catch certain types of bugs.
So now we've seen another example of the trade-off associated with making a test less flaky,
but perhaps also making it less realistic.
Yeah, I'm starting to get a sense that there's probably a precision versus stability.
trade-off that's always at play here.
Yeah, obviously in the extreme end of the spectrum, you can make any test non-flaky by just deleting it, right?
Put an ignore attribute on it.
Exactly.
So you've got to be careful that you're not optimizing your tests just for passing,
which if you're trying to get a PR through, then that is a trap you might fall into.
Yeah, absolutely.
So you talked about that survey a little bit before.
Maybe you want to talk about some more of the things you learned from there?
What were some of the responses there?
So we've got some really interesting ones, actually.
I'll just, if you want to find the paper yourself, or if anyone's listening wants to find the paper,
if you just Google surveying the developer experience of flaky tests, you should be able to find it.
As I said, we also asked developers what they thought were the most common causes of flaky tests.
And the cause that got the most votes was setup and teardown.
So what I mean by that is flakiness being caused by a test case that either doesn't fully set up its executing environment,
alternatively doesn't clean up after itself.
And if it doesn't clean up after itself correctly, it could leave some global state behind that could then impact a later test case that's executed, if that makes sense.
Yeah, absolutely.
The cleanup one is especially tricky, right?
We kind of know about setup because you're like, oh, well, we have to do this in order for this file to exist or whatever.
Teardown part, that becomes really tricky because it could have knock-on effects for tests that either pass and they shouldn't,
or don't pass because it didn't start fresh, right?
This portion of Talk Python to Me is brought to you by JetBrains and PyCharm.
Are you a data scientist or a web developer looking to take your projects to the next level?
Well, I have the perfect tool for you, PyCharm.
PyCharm is a powerful integrated development environment that empowers developers and data scientists like us to write clean and efficient code with ease.
Whether you're analyzing complex data sets or building dynamic web applications, PyCharm has got you covered.
With its intuitive interface and robust features, you can boost your productivity and bring your ideas to life faster than ever before.
For data scientists, PyCharm offers seamless integration with popular libraries like NumPy, Pandas, and Matplotlib.
You can explore, visualize, and manipulate data effortlessly, unlocking valuable insights with just a few lines of code.
And for us web developers, PyCharm provides a rich set of tools to streamline your workflow.
From intelligent code completion to advanced debugging capabilities, PyCharm helps you write clean, scalable code that powers stunning web applications.
Plus, PyCharm's support for popular frameworks like Django, FastAPI, and React make it a breeze to build and deploy your web projects.
It's time to say goodbye to tedious configuration and hello to rapid development.
But wait, there's more.
With PyCharm, you get even more advanced features like remote development, database integration, and version control, ensuring your projects stay organized and secure.
So whether you're diving into data science or shaping the future of the web, PyCharm is your go-to tool.
Join me and try PyCharm today.
Just visit talkpython.fm/done-with-pycharm, links in your show notes, and experience the power of PyCharm firsthand for three months free.
PyCharm.
It's how I get work done.
Well, this kind of leads us into a whole other type of flaky test called a test order dependent test.
Okay.
When you have a test case that doesn't clean up after itself properly, then that can potentially mean that later tests that perhaps are targeting similar parts of the program where there might be some state involved.
They might fail when they should pass or alternatively pass when they should fail just because the assumptions that were there when the developer wrote that test aren't being met anymore because something's changed by another test.
So what that means is that if you just take an arbitrary test suite, randomize the order, shuffle it, for any large test suite, I can almost guarantee that some tests are going to fail and all you've done is change the order.
And they're failing because somewhere a test isn't cleaning up after itself properly.
Yeah.
Or cleaning up can mean different things, right?
Cleaning up can mean we didn't change.
We didn't take away that file we created or put back the file we deleted as part of this test scenario we're working with.
But it could also be we're testing by talking to a database and we made an insert to it and didn't roll that back.
Or maybe the most subtle, I guess there's two more levels here.
One, you could have changed in memory state.
Yeah.
Right?
You could have, like there's a shared variable, which is probably the most common reason.
Like some shared state of the process just isn't in its starting or expected position.
But the fourth one, when I said, oh, there's three, but actually I think there's more, is you mocked out something.
Like I've mocked out what daytime.now means.
I forgot to put it back.
So time has stopped or something like that, right?
Yeah.
Those are all really good examples of the flakiness that can appear when you have shared state.
And building on what both you and Owen just said, again, I think there's another tradeoff here.
One of the tradeoffs is connected to the efficiency of the testing process.
There's a flakiness of the testing process.
So if you do a really good job at clearing out state from your database or resetting state in the memory of your process,
that may take a longer time, but potentially reduce the amount of flakiness that manifests in your tests.
And then additionally, it's worth noting that when you have test suites with really good setup and really good teardown and cleaning mechanisms,
those are also more time consuming for us to write as developers,
which may mean we're spending a lot of time investing in our test suite and perhaps slightly less time actually adding new features to our program.
And so there's tradeoffs both in terms of developer productivity and the efficiency of the testing process.
Those both matter.
Which one matters more to you probably depends on your situation, right?
If you're a small team and you need to move quick, the developer overhead is probably a serious hassle.
But if you're a large team and you have 100,000 tests, you want to get answers today, not tomorrow from your test suite.
The speed of execution probably matters more at that point.
Yeah, I think that's absolutely the case.
And so there have been some situations where I have certain test cases that take a really long time to run.
And so in pytest, I might set a marker and only run those test cases at certain points of time during development on my laptop and then always run them inside a CI.
And the nice thing about removing those long running test cases is that it can make the testing process faster.
And I don't have to do my rigorous cleaning approach except when I am running them in CI.
Yeah, that's an interesting idea.
Maybe giving them tags and then coming up with a category of speed.
I mean, I know I've heard of people doing like marking a test as slow, a set of tests as slow.
Maybe that's not fine grained enough.
Maybe doing something like fast, less than a second, less than five seconds, less than 10 seconds.
So I'm willing to run all the ones that run in three seconds or less, but not more than that.
Right.
So you could kind of scale it up more than just fast and slow.
So on the topic of markers, there's several plugins for pytest that enable you to mark a test as flaky.
Okay.
Basically, what that then means is that if it fails, it will retry it some number of times.
And then if it passes at least once, it will call the whole thing a pass.
So while that means, yeah, you can just make your test suite pass.
It's like, so for example, so in the, in the survey, we had one respondent tell us sometimes a flaky test always a bad thing because sometimes the fact that a test is non-deterministic is showing that part of the software is non-deterministic when it shouldn't be.
So if you were to follow this methodology of just rerunning all your flaky tests and ignoring them, if they pass at least once, then you'd miss out on that because you wouldn't be notified that that test failed.
Yeah.
That's a good point.
Maybe it's highlighting a weakness in your infrastructure, your DevOps story.
Yeah.
You could say, well, that's out of my control.
It's not my problem.
Or you could say, no, actually folks, look, this is pointing out, this is the least stable pillar of our uptime for our app, right?
Yeah, that's a good point.
The other thing, since we're talking about randomness, that's important to discuss is the use of property-based testing tools.
So, for example, I use Hypothesis in order to automatically generate inputs and then send them into my function under test.
And there may be cases where Hypothesis reveals a bug, and that could, in fact, actually be a bug in my program, even though I've run exactly that same test case frequently in the past.
And it just happens to be the case that in that run, when I was using a Hypothesis property-based test, it was able to find a potential problem.
So, in that situation, even though that test didn't fail the last three times, this could still be a silver lining to suggest that there is a problem with my program, and I need to resolve it because Hypothesis has randomly generated an input that I haven't seen previously.
Yeah, the Hypothesis story is interesting.
I was thinking about that as well after reading some of the work that you're doing here.
Thinking things like Hypothesis and parameterized testing and those sorts of things, where they just naturally take some test scenario and run it over and over with a bunch of different inputs, probably uncovers this better than one-off tests, I imagine.
And Hypothesis also has a mode that lets you run a long-running fuzz testing campaign.
And in those situations, it doesn't constrain its execution by a specific period of time.
And I have found that when I let a fuzzing campaign go for a given function, maybe I've described its inputs with a JSON schema, and I'm using the Hypothesis JSON schema plugin.
I might not find a bug for a long period of time, and then suddenly a bug will crop up.
And I often use that as an opportunity to rethink my assumptions about the function that I'm testing.
So you said fuzzing.
Tell people out there.
Give people a definition of that if they're unfamiliar with fuzzing.
So when I think of fuzzing as a process where you're randomly generating inputs that you're going to send to the function under test, and you're frequently doing that in what I would call a campaign,
which means that the input generation process is going to range perhaps for an extended period of time.
And you may have different goals during that fuzzing campaign, like covering more of the program under test, or attempting to realize as many crash-inducing inputs as is possible.
Yeah, that's such a cool idea.
And that's sort of how a hypothesis works.
Although I don't know it's really meant for fuzzing in the sense of just we're going to hit it with a whole bunch of stuff at extreme scale until it breaks.
But it certainly is meant to run it a lot of times with different inputs.
So sort of the effect, if not necessarily the intent of it.
Here's another one from the audience that also is one that I hadn't thought about from Marwan.
It says, sometimes flakiness shows up as conflicts to access a shared resource when more than one CI pipeline is running for the same code.
That's pretty wild.
I hadn't really thought about that, right?
But if you have a lock on something.
Resource availability in general is quite a common cause of flakiness.
So this resource could be a file system or a database or anything really.
Or even something, even if we're not talking about CI, just on a local machine, you've got, like you said, locks and things that aren't supposed to be shared between multiple processes or whatever.
So yeah, that is a relatively common cause that I've seen in the programs that I've tested.
Yeah, the thing about that that stands out to me is you might have an assumption that only one process of your app is ever going to be running on a server at a time.
And yet somehow, because, you know, there were multiple Git commits that you weren't even aware of.
Now they're running kind of in parallel.
So you're in this situation that, you know, you never saw coming because you just don't run your app that way.
You know what I mean?
Yeah.
This is actually a really good example of when a flaky test is again a silver lining because it forces you to question your assumptions about how your program will run and when it will run.
And how much of resources your program is going to consume.
So in the situation when I never thought about my program running in multiple instances at the same time, if my tests become flaky, that may actually open up a whole new opportunity for me to refactor and improve my program.
Yeah, that's right.
You're like, wait a minute.
I didn't realize this was a problem.
But yes, maybe it is.
Let's see.
So let's talk a little bit about some articles that a couple of the big tech companies wrote here.
So both Google and Spotify talked about how they're using, how they're experiencing flaky tests and what they're doing to either reduce them or maybe, as you put it, Gregory, some of the silver linings that they're finding in it and some of the tools that are building to help deal with it.
So over at Google, they say they're running, obviously, a large set of tests.
I could probably like that is a massive understatement, I imagine.
But it says they see a continual rate of flakiness of about 1.5% on all test cases, which for them, I imagine, is a lot of tests.
And so you want to talk a little bit about this, either of you guys, and maybe some of the mitigation strategies they have?
So from a developer perspective, so this article and others as well, point out an interesting side of flakiness that when you're talking from a purely technical perspective, you don't really consider.
And that's the kind of the sort of the psychological impact of them.
So it's a little bit like the boy who cried wolf.
So if you have a test case that's known to be flaky, you might be tempted to just put some marker on it that says ignore it or it's an expected fail or whatever.
But then suppose it fails for real sometime and you're ignoring it or you have it quarantined or something, then that means you're missing out on real bugs.
So as well as just being a hindrance to CI and that kind of thing, it could almost make a developer team lose the discipline to properly investigate every test failure.
If they've got a test suite that's known to be full of flaky tests, then naturally you're not going to trust it as much.
Yeah.
So that's probably one of the biggest problems that flaky tests cause, in my opinion.
I think the mental aspect of it, how much do I trust it?
Do I have faith in our test suite?
Do I have faith in the continuous deployment capabilities of our team?
Pipelines and things like that.
That's, I think that's pretty serious.
There's already a bit of a challenge, I think, on teams to have complete buy-in on making sure the software is self-evaluating.
You know, like some people will check in code that breaks the build, but they're kind of like yellow, whatever.
Other people really, you know, somehow they really want the build to work.
So it's their job to kind of chase that person down and make them fix it.
And it's always kind of a bit of a struggle, but that's when the tests are awesome, right?
It just changes to the code, makes it sort of adds this, these breaking builds.
But if the code is flaky, all of a sudden you can start to see CI as an annoyance because it tells you something's wrong.
You're like, I know nothing's wrong.
It's just, it'll go away.
So maybe speak to the psychological bit of like how flakiness can maybe degrade people's caring about tests at all.
I would say that overall developers have the risk of losing a confidence in two types of correctness.
First of all, flaky test cases may cause developers to begin to mistrust and lose confidence in the test suite.
Then they also may lose confidence in the overall correctness of their program and then may cause them to stop running test cases, which then reduces test quality and maybe even also reduces the quality of the program under test as well.
So I think regrettably, it's a negative reinforcing cycle where you start to mistrust your tests so you don't run them.
But then you start to lose confidence in the correctness of your program.
And now you're not sure what to do because tests are failing for spurious reasons.
You disable them.
But then as Owen mentioned previously, you lose the opportunity to get the feedback from those tests.
It goes both ways, right?
You don't feel like if it says it's broken, it provides you much value because it might report broken even though it's working.
But on the flip side, if you were doing, you know, continuous deployment.
And by that, I mean, I check into a branch, that branch notices the change automatically, it rolls out the new version, right?
Maybe you merge over to a production branch and it just it takes off.
The gate to making that not go to production is the CI system that's going to say whether or not the test pass.
If the test pass and maybe they shouldn't have because, you know, you've got this flakiness.
Well, that's also also not good.
That's a situation when you could have just deployed software that wasn't working.
And then the flip side of that is you have a flaky build and you want to be able to release quickly.
But because test cases are failing, you don't release your system.
And so it can really be a hindrance to being able to quickly push your work to production because you frequently have flaky test cases that are causing you to limit the velocity of your development process.
This portion of Talk Python to me is brought to you by Sentry.
You know Sentry for their error tracking service.
But did you know you can take that all the way through your multi-tiered and distributed app with their distributed tracing feature?
Distributed tracing is a debugging technique that involves tracking requests of your system, starting from the very beginning, like a user action, all the way to the back end, database and third party services.
This can help you identify if the cause of an error in one project is due to the error in another.
Every system can benefit from distributed tracing, but they're especially useful for microservices.
In this architecture, logs won't give you the full picture, so you can't debug every request in full just by reading the logs.
Distributed tracing with a platform like Sentry gives you a visual overview about which services were called during the execution of certain requests.
Aside from debugging and visualizing architecture, distributed tracing also helps you identify performance bottlenecks.
Through a visual like a Gantt chart, you can see if a particular span in your stack took longer than expected and how it could be causing slowdowns in other parts of your app.
Learn more and see some examples in the tracing section at docs.sentry.io.
To take advantage of all the features of the Sentry platform, just create your free account.
And for all of you Talk Python listeners, use the code Talk Python, all one word, and you'll activate a free month of their premium paid features.
Get started today at talkpython.fm/sentry dash trace.
That link is in your podcast player show notes and the episode page.
Thank you to Sentry for supporting Talk Python to me.
You got thoughts on that?
No, I think Greg's pretty much covered that pretty well.
Yeah, yeah.
Excellent.
So the two things, two takeaways from the Google article is, one, they talked about mitigation strategies.
And they said they have a tool that monitors the flakiness of tests.
And if the flakiness is too high, it automatically quarantines the test, takes it out of the critical path, takes it out of CI.
You know, maybe somebody notices like, hey, there's a new flaky test.
We need to go find the root cause of that.
But that's a pretty interesting idea, isn't it?
Some sort of automation or maybe not quite totally automatic, but some kind of tool that you can run that'll say, this thing has reached a point where maybe its value in the test suite is degraded because it's so flaky that we need to either fix it or just delete it.
I think the problem with quarantining tests is it only works if the development team is serious about investigating them.
Otherwise, what could end up happening is quarantining becomes effectively equivalent to just deleting it.
If they all end up in a special flaky bucket and no one looks at them again, the whole point of the process is kind of moot, really.
So doing something like that, I think, can be really useful if the developers are willing to actually investigate why these tests are flaky.
It's true.
If it becomes just a black box and basically a trash can for tests, then what's the point, right?
Exactly.
Yeah.
It kind of goes back to my talking about like there's some people on the team that really care about the build and continuous integration and all this and other people who just don't.
So it does come back to the team mentality and people really caring about these things.
But it's a cool idea if, at least in the optimistic point of view, where assuming everyone wants to make sure these keep working and someone's going to pay attention to this and so on.
Yeah, it's a difficult one because, I mean, sometimes you can just write a bad test and that test is flaky purely because it's a bad test.
But other times you can write a good test that's flaky because there's a problem.
Like I said before, we had one developer say that sometimes a flaky test implies that a part of the program they thought was deterministic was actually non-deterministic.
So you're potentially throwing away useful information as well as potentially throwing away just poorly written tests.
Right.
And it's hard to distinguish between those two.
I'm sure that it is.
Yeah, I mean, identifying these, maybe not quarantining them, but identifying them is pretty valuable, I would think.
And then you can see what lessons come from that, right?
What you do once you've identified it, I think that is up for debate, right?
Yeah.
Okay.
The other one is Test Flakiness, Methods for Identifying and Dealing with Flaky Tests by Jason Palmer from Spotify, which is also cool.
This one has pictures, which is fun.
They've got like a graphical analysis of their tests and the flakiness of it and so on.
They came up with a thing called Flaky Bot.
And it's a GitHub integration, GitHub bot that they can make it run and they can ask it to exercise the test really quickly and see if it's flaky.
And I got the sense that it does that by just running into a bunch of different times with different delays and seeing if it always passes or if it potentially sometimes passes or fails.
So I think broadly, one of the things that is mentioned in this article and something that's done by a number of pytest plugins as well is rerunning the test suite.
And so you could imagine rerunning each test case in isolation.
You could also imagine picking a group of test cases and then rerunning the test cases in that group, either in a random order or in certain fixed orders.
So rerunning is often a very helpful way for us to detect flaky test cases, whether we rerun the whole test suite, whether we run test cases individually or whether we run test cases in groups.
Obviously, one of the clear downsides associated with rerunning a test suite is the execution time associated with the rerunning process.
Yeah, the more you run it, the more likely you're able to detect flakiness if it's only a little bit flaky.
But at the same time, the longer that goes, the longer it takes, that's also a problem.
There's another thing in here that I thought was pretty interesting, but I'm struggling to find it in this article for the second.
Integration, no?
I thought end-to-end maybe.
So in the Spotify article, they say that, in their assessment, that end-to-end tests are flaky by nature.
Write fewer of them.
So I get the sense, I don't know, I get the sense maybe you all sort of feel this way as well, but I don't necessarily agree with that.
I think end-to-end tests, if they are flaky, that's telling you something about your program.
It might not be really precisely narrowing in on it, but it's telling you something about your program if you can write end-to-end tests that are flaky.
What do you think?
I think with end-to-end tests, I mean, sort of saying they're flaky by nature may be a little strong, but they're certainly more susceptible to flakiness purely because there's a hell of a lot more going on.
So I think when we talk about this sort of flakiness and sort of precision trade-off, I think with end-to-end tests, you should be a little bit more forgiving with flakiness purely because there's more going on.
Yeah.
So like, for example, for a unit test, you shouldn't really accept any flakiness because that's a very focused test case.
So yeah, those are my thoughts on that.
Okay.
I would agree with what Owen said.
I still think there is quite a bit of value end-to-end or integration testing because from my perspective, it's increasing the realism of the testing process.
So I still write end-to-end test cases if I'm building a web API or even if I'm building an application, but I think I have to be willing to tolerate a little bit more flakiness and perhaps even be creative with the various strategies that I adopt when I do rerunning.
Maybe I need to run some of my integration tests with really good setup and teardown to avoid pollution between test cases.
Or maybe certain integration test cases have to be run completely in isolation and they can't be run while any other part of the program is being used.
So in those cases, maybe my integration tests are run less frequently, but I still keep them as a part of my pytest test suite.
Yeah.
Interesting.
Both of you.
For me, one of the things I do that I think is really valuable is over at Talk Python, we have the courses and the web app that serves up the courses and those people buy them and all that sort of stuff.
It's like 20,000 lines of Python, maybe more these days, haven't measured it for a long time, but it's, you know, a non-trivial amount.
And it's got a sitemap of all the pages on there.
And one of the things I do for the test is just go and find every, pull the sitemap, look at every page on the site and just request it and make sure it doesn't 500 out or 404 or things like that.
And it just, they all work, right?
Now there's like 6,000 links in the sitemap.
So it says, well, these 500 are all really the same thing with just different data behind it.
So just pick one of those.
There's a way to kind of winnow it down to, you know, 20 requests and not 6,000.
But that kind of stuff, there should be no time where there is a 404 on your site.
It's not an inherent flakiness of testing that there's not a 404.
There should not be a 404.
Same thing.
There should not be a 500, my website, my server crashed.
It should never crash, right?
And so those types of integration tests, I think, I think they still add a lot of value, right?
Because you could miss something like, well, I checked the database models, they were fine.
I checked the code that works with the database models are fine.
But the HTML assumed there was a field in the database model we passed to it.
There is value in the sort of holistic, like, does it still click together story, I think.
No, I think you're making a really good point, Michael.
And so what I often do, like, say, for example, I'm adding a new feature or I'm adding a bug fix.
What I'm going to regularly do to be confident in my changes to the system is run my integration tests maybe every once in a while and run my unit tests very frequently during the refactoring process.
I can't run them all the time because they regrettably take too long to run.
But I can run my integration test cases very frequently when I'm adding a bug fix or adding a new feature.
And then every once in a while do the kind of smoke tests that you mentioned and then the integration or end-to-end testing that we've been discussing so that ultimately I have rapid feedback that gives me confidence.
And then additionally, I have longer running tests that further give me confidence that my program is working.
That's a really good point.
And maybe you don't even run all your unit tests as you're writing that feature.
Maybe you run a group of them that you think are related.
Yeah.
So you're bringing up something that I really love about the Python ecosystem, which is the awesome coverage.py and the pytest-cov plugin.
Those plugins are really good.
And what's awesome about coverage.py is that it can track code coverage on a per-test-case basis.
So one of the things that I will often do is look at what test cases cover what part of the system.
And as you mentioned, I'll only run those test cases that are covering the parts of the system that I'm changing because that helps me to get very rapid feedback from my unit test cases while I'm adding a new feature to a certain part of my program.
I didn't realize that coverage.py would tell you that in reverse.
Like, for this part of your program, these are the five tests.
That's really cool.
Yeah.
So I really like that feature.
I think it was released in coverage.py 5.0.
And I've been using it since the feature was available.
It's incredibly helpful because of the fact that you can look at specific statements in your code and then find out which test cases cover those statements and then choose to rerun those specific tests when you're repeatedly running your test suite.
And I call that test suite reduction or coverage-based test suite reduction.
And having what Coverage.py calls the context of coverage is, in my experience, very, very helpful.
Yeah.
The bigger your test suite is, the more helpful it is, right?
Absolutely.
Owen, did you find that a lot of people were using those kinds of tools to sort of limit the amount of tests they got to run?
With the sort of programs I was working with, for the purposes of my experiments, I was running the whole test suite in its entirety multiple times to find flaky tests.
But I did see evidence of that kind of thing being set up.
So I think it is fairly well-adopted.
Once again, it's a lot more relevant to very large projects as opposed to small projects where if it only takes you 10 seconds to run the whole test suite, obviously there's not a lot of point in doing...
Just let it run.
Yeah.
But when you've got...
I've dealt with test suites that take best part of six hours to run end-to-end.
So having some kind of test selection, test reduction there is essential, really.
In the winter, it's nice because then your computer can spend six hours heating the room.
It puts a little less stress on the house heater or office heater.
Seriously, what do you think about the systems like the IDEs that have their extensions are built right in, where they just constantly run the tests as you make changes?
They just notice the files have changed, so we're rerunning the tests.
So I don't use that in an IDE, but I do have something like that set up that runs in a terminal window.
And I found continuous testing to be quite helpful.
It's really helpful in cases where maybe I forget to run my test suite while I'm refactoring my program, and it can give me immediate feedback.
To go back to a comment that I made previously, you can also use different pytest plugins or use something like Hypothesis so that you can run your test suite with random inputs on a regular basis.
And I have found that's another good way for me to be able to, without having to think too hard, find potential bugs in the functions that I'm testing.
Okay, interesting.
Let's talk about some of the tools.
So you all highlighted a couple of tools that people can use to help find flaky tests.
So over at Datadog, they've got one for flaky test management.
Want to tell people about that?
So many of the tools that are provided by companies like Datadog are offering you a type of dashboard that will help you to better understand the characteristics of your test suite.
So you can see what are the test cases that tend to be the most flaky.
I think oftentimes it's hard for us to get a big picture view of our test suite and to understand what is and is not flaky.
And so therefore having a flaky test management dashboard like Datadog provides can often give me the observability or the visibility that I might miss otherwise.
That's super cool.
And let's see.
There's, I don't know, that's not the one I want to pull up.
Also at Cypress has flaky test management.
This is a really interesting approach because I normally use Cypress when I'm testing websites.
And in my experience, when I'm-
What is Cypress?
I'm not familiar that maybe people aren't as well.
Give us a quick intro first.
Sure, I'd love to do so.
So Cypress is a tool that helps you to do testing of your web user interfaces.
So if you have a web application and you want to be able to test the input to a form,
or you want to be able to test certain workflows through your web application,
you can use Cypress and you write your test cases.
Essentially, it's as if Cypress is running its own Chrome and it can control your test suite.
It can run your test cases.
When things fail, it can actually give you snapshots of what failed.
It can tell you about the browser version that it was using or maybe the mobile ready viewport that it was currently running it at.
And again, the nice thing about things like what Cypress provides is that it can give you some kind of flaky test case analytics,
which can show you which are failing and which are passing.
And it can also say, hey, these that are flaky and then break it out in terms of which ones are the most flaky versus the least flaky.
Again, primarily in the context of testing web interfaces or web applications.
Sounds a little bit like Selenium or Playwright, which are both nice.
It is.
So I have to say I've had the most flaky tests for my Selenium test cases.
But when I switch to either Cypress or Playwright, Playwright as well has a way so that you don't have to do these baked in weights inside of your test case,
which is one of the sources of flakiness that Owen and I have found in a number of real world programs.
I'd say that's almost one of the most common, actually.
Okay, Owen.
So Gregory points out that it could be not exactly there's something wrong with your program or your code or the infrastructure your code depends upon,
but maybe almost a flaky test framework itself, a flaky test runner scenario,
where the flakiness is not even necessarily it's in the observation, not in the execution.
That's pretty interesting.
Well, yeah, the classic formula for something like that is a test case that says launch something asynchronously, wait one second, check something.
Yeah.
You might think that that one second is enough.
If there's a time when for whatever reason there's some background work going on or it takes a little longer than that, then.
And all of a sudden it needed one and a half seconds.
Then you have a flaky test.
Yeah.
Yeah, for sure.
Any of those things where you have to start something and then wait for it to something to happen remotely.
That's got to be pretty sketchy.
The usual approach is to sort of have an explicit wait.
So you'll sort of say, I'm actually going to wait until this is completed, whatever that means.
But then you run into a situation where, well, what if for whatever reason, this asynchronous thing you're interacting with is timed out or frozen,
then you're going to end up with a test that's waiting forever.
So you have to have some kind of upper limit to how long you'll wait for.
Otherwise you may wait forever.
Yeah.
This test case is real slow.
So once again, there's no kind of silver bullet solution, really.
It's just trade-offs again.
Yeah, yeah, yeah.
What do you all think about things like tenacity where you can go and put a decorator onto a function and just say, retry this with some scenario?
Or another one that I just learned about is Hynek's stamina, which is cool as well.
You can say, put a decorator and say, retry a certain number of attempts with, you know, like some kind of back off, an exponential back off where you give it a certain amount of time.
Like for flaky tests, you see it making sense to say, well, maybe call the functions this way in some of your test cases.
So I've never actually seen either of these plugins, but they do look quite interesting.
Yeah.
I haven't used tenacity either, but I was aware of it.
And I think you could imagine using tenacity in two distinct locations.
Maybe you want to put some of these tenacity annotations on your multi-threaded code and then let the test case call those annotated functions.
Yes, exactly.
Don't put them in your production code.
Don't put them on your test, but just have like an intermediate one that you can control the back off and retry count.
Exactly.
And another thing that I think is worth pointing out since we were previously discussing web testing is that there is a way in which it can be a problem with your testing framework, like you previously mentioned, Michael.
So, for example, Playwright does have a really nice auto-weighting feature.
And so when I'm testing a web application, I can use Playwright's auto-weight feature, and that will help me to avoid baking in hard-coded weights inside my test because the actual testing framework itself has a way to do auto-weighting.
So when you say auto-weighting, you can say things like request this page, find this field, put my email address into it, click this button, test that this thing is, you know, now the page has this thing in it.
But obviously servers don't instantly respond to that, right?
So you've got to have some sorts of delays.
So you're talking about the system can kind of track that automatically.
Yeah.
So Playwright can actually do some of that on its own.
So, for example, if you're looking for a certain element in the web page to be available, Playwright has a way that will allow you to ensure that the element is actually attached to the DOM, that it's actually visible, that it hasn't moved around, or that it's not being animated in some way.
And all of those things are actually part of the testing framework, which makes it incredibly helpful because then I don't have to actually implement all of that when I'm writing my test cases.
Fantastic.
We're getting a little short on time here.
Let's round.
I want to round it out with a little bit of a survey.
If I find the right thing.
Of some of y'all mentioned some of the pytest plugins that might be relevant here.
So I'm pulling up Awesome pytest, which I'll link to.
Just an awesome list of pytest things.
But you've got things like in here, like pytest randomly, which lets you randomly order tests and set a seed and those kinds of things.
And a bunch of other stuff.
Do you want to pull out some of these you maybe think are relevant or see if they're at least in your list, the ones you like?
So I've used randomly before.
And like how I said earlier, this could be a great way of finding those tests that not necessarily the ones that don't clean up out of themselves properly, but it will certainly show you tests that are potentially impacted by other tests not cleaning up out of themselves.
So I think if you take almost any large test suite and apply randomly to it, the chances are you are probably going to see some failed tests that weren't failing before you shuffled the order.
So I think that's quite an interesting plugin.
And you can use to quickly assess if you've got order dependent tests in your test suite.
Speaking from experience, the one additional point that I would add is that when I use pytest randomly, I try to make sure I integrate it early into the lifetime of my development process.
So instead of writing 947 test cases and then trying to use pytest randomly, I try to always make sure that pytest randomly is running in GitHub Actions very early in the development of my application.
So that when I only have 40 or 50 test cases, I can immediately find those dependent tests that could have flakiness and then begin to be more proactive when it comes to avoiding flakiness very early when I'm launching a new product.
Yeah, that makes sense.
One of my follow-up questions was going to be, would you all recommend de facto installing that into and turning that on at least unless you have a reason to disable it for new projects?
I find it very helpful.
It's one of the things that I frequently add to a new project.
So when I'm, I use poetry for a lot of my package management and I have various templates that I use and I often add pytest randomly right away as one of my dev dependencies and then make sure I'm always running my test suite in a random order when I'm running it in GitHub Actions.
So I can't remember who I heard this off for, where I read it exactly, but I have heard that at Google, other companies as well,
running the tests in a random order is actually standard practice for the reason, like Greg just said.
So when you start on a new project, you're starting with this sort of shuffled order test running.
And I suppose it's kind of like a technical debt then you're kind of paying it off early rather than writing a bunch of tests and then having a big fixing effort when you realize there's a big problem with a whole bunch of them.
I feel like it's a little similar to linting and things that go through and tell you these are the recommended issues or issues we found.
We recommend fixing them for your code.
If you apply that retroactively, like rough or whatever, if you apply that to your project after it's huge, you'll get thousands of lines and nobody wants to spend the next two weeks fixing them.
But if you just run that as you develop it, go, there's two little things we got to fix.
No big deal.
See, it sounds similar to that effect.
I agree.
Yeah.
Here's another one that's interesting.
High test.socket to disable socket calls during tests.
So you heard me talk about requesting every page on the sitemap.
So I'm not necessarily suggesting that you would want to just do this in general.
But, you know, one of the areas that seems to me that could be result in flakiness for a set of tests is like I depend on an external system.
Like, oh, I thought we were mocking out the database, but I'm actually talking to the database.
Or, oh, I thought we were mocking out the API call, but we're talking to it.
You could probably turn that on for a moment, see which test fails, and just go, well, these three were not supposed to be talking over the network.
But somehow they fail and we don't let them talk to the network.
So that might be worth looking into.
What do you think about that?
That's a good point.
I haven't tried that tool, but the way that you've explained it makes it really clear that there would be a lot of utility to using it.
Yeah.
Let's see.
There's probably a couple other ones in here.
There was one right back here.
It was called a pytest Picked.
And I don't really know how I feel about this.
I don't know if it's precise enough, but you were talking, Greg, about winnowing down the set of tests you were running using coverage.
And this one says it runs test related to changes detected by version control, just unstaged files.
I feel like this is a really cool idea, but it does it in the wrong order.
I feel like it's looking at just the test files that are not changed or that are not committed and rerunning those.
It should look at the code covered.
The changes, unstaged production files, and then use code covers to figure out which tests need to be rerun.
Right.
It's really cool to use the idea of having the source control tell you what the changes are since your last commit.
But then this is just applying it to the test files, I think.
But if it could say, well, now we use coverage to figure out these tests, that would be awesome.
Yeah, and I regret that I can't remember the name of it.
There is a tool that does a type of test suite reduction as a pytest plugin.
And maybe I'll look it up after the show and we can include it in the show notes.
Of course, the thing that you've got to be careful about is that there could be dependencies between program components that are not evidenced in the source code or the cover relationship, but maybe by access to external resources.
And so in those cases, the selection process may not work as intended.
Right.
This thing changed something in the database.
Some other part of the code read it, and that makes it crash.
You didn't actually touch that code over there.
Something like that.
Yeah, absolutely.
Yeah.
Interesting.
Maybe one more.
I don't know too much about this, but Bell points out, says, I remember Anthony had worked on a tool to detect test pollution, which is a kind of related topic.
And says a test pollution is where a test fails due to the side effects of some other test in the suite.
That's pretty interesting.
So maybe that's something for people to look at.
Have you heard of this?
I haven't heard of this before, so I can't speak too much about it.
I've not heard of this specific tool, but I've seen it done in Java as a Java project.
And yeah, you can do it fairly successfully and you can go quite deep with it as well.
I mean, it's hard to see exactly how this one works just based on the description.
But I mean, I think there was an example where it had a global variable.
But I mean, obviously that's quite a trivial example.
But I mean, you can get state pollution in ways you really wouldn't expect it.
So for example, I've seen a test where two tests that were dependent on each other were individual parameterizations of the same test.
There was a dependency because in the parameterization decorator, someone had used a list object as an argument.
So then and then in the test, they've modified that list.
But then the list isn't then recreated for the next test.
So then the next test gets.
But that's not a global variable.
That's just sort of created when that file is executed.
A weird Python default value behavior.
Yeah, that is.
Yeah, it's quite.
I've seen people complain about that quite a lot.
So like when you have a function and you put a list or a mutable object as like a default argument, that's quite a common gotcha.
So it's a similar kind of thing.
Yeah.
And it's if you run tools like I talked about LinkedIn, if you run tools like Ruff or others that will, you know, flicate those types of things.
You know, many of them will warn you like, no, this is a bad idea.
Don't do that.
So maybe running, I would imagine running tools like Ruff and other linters that detect these issues might actually reduce the test flakiness by finding some of these, you know, anti patterns that you maybe didn't catch.
Yeah, it may well do.
Yeah.
I think another thing that's important to note when we're talking about a linter like Ruff is that it's so fast to run that there's not really a big cost from a developer's perspective.
Yeah, it's nearly instant.
Yeah.
Again, integrate it early.
Use it regularly.
Have it in your IDE.
Use it in CI.
And it's one of those things where it might help you to avoid certain coding practices that would ultimately lead to test flakiness creeping into your system.
Yeah.
Really good advice.
It is so fast.
I ran it against, like I said, 20,000 lines of Python and it went and just it looked like it didn't even do anything.
I thought, oh, I didn't do it right because nothing happened.
No, but it's so quick.
And you can put it, there's plugins for both PyCharm and VS Code that'll just run it.
And PyCharm even integrates it into its code fixes and all of its behaviors and just reformat code options and stuff.
It's really good.
I've been using Ruff recently and I really like it as well.
Along with the point that you mentioned, I like the fact that you can configure it through the PyProject Toml file, which is where I'm already putting all of my other configurations.
Yeah.
And then it also essentially can serve as a language server protocol implementation.
So even if you don't use the two text editors that you mentioned, you can still get all of the code actions and fixes.
And because it's so fast, it's really easy to use it even on big code basis.
Okay.
One more really quick, because I think this is a good suggestion.
And this goes back to how I talked about, you know, like the call is coming from inside the house type of problem in that the error could actually be with the test framework and the test code, not just not actually your code.
So Marwan points out that scoping fixtures incorrectly could be another source of flakiness.
So the fixture could say, create a generator and pass it over to you.
But you could say this is a class based one instead of a test instance one.
And then you get different results depending on the order and all these kinds of things.
Right.
That's really interesting.
I think.
I would agree.
I think that's a really good point.
The other thing that I sometimes need to be very careful about is having auto use test fixtures inside of my code, because then those might be applied everywhere, along with other decorators that are fixtures that are just applied selectively.
And then I might get a kind of non-determinant process just because of the way that various text fixtures are applied or the order in which they're applied.
Absolutely.
All right, Owen.
Last word on this.
The other plugin that might be interesting is pytest Xdist or running these distributed.
Like, what do you think of how's that help or hurt us here?
Finding your tests in parallel can be obviously a great way to expose concurrency related flakiness.
Because as you said before, when you're writing the test, you might be writing it under the assumption that you're the only one accessing certain resources or running at a certain time.
Yeah.
Another thing that something like this can do as well is it can also expose order dependent tests because so the way it will work is it will create.
Say you're wanting to run eight tests at a time.
This plugin will then create 10.
So you create eight separate processes.
But within those processes, each one has its own independent Python interpreter.
So they're running independently of each other.
But then you could also, by doing that, expose a test case that was expecting a previous test to run, but now isn't because it's running in a different process.
And that test could then go on to fail.
So that would then be another issue of inadequate setup from that test.
Yeah.
This is something I should probably be running more of as well.
Like, why not?
I have 10 cores on this computer.
Why don't I just have my test run faster?
It's probably not 10 times faster, but it could do more than just running one thread in serial.
You could do, yeah.
But certainly running them in parallel would certainly pull up some of those ordering issues as well as resource contention issues.
Yeah, so as well as providing a speed up, it's also great because it exposed some problems in your test suite as well.
Absolutely.
All right, guys.
I think we're going to have to leave it there for the time we got.
But excellent stuff.
And there's a lot of detail here, isn't there, as you dig into it?
Yeah, I think flaky tests are something that all of us as developers have encountered.
We recognize that they limit us as developers.
But also there's something that if we can automatically detect them or mitigate them in some way, we can remove that hassle from developers.
So I think what we would like to do, both as researchers and developers, is allow people who write pytest test suites to be more productive and to write better tests that are less flaky.
Excellent.
All right, before we wrap up the show, I'll just ask you one quick question I usually do at the end.
And that is you've got a flaky test related project on PyPI.
Some library, some package you want to recommend to people.
Or it could be something other than flaky related.
But something you want to recommend, some package you've come across lately.
I was actually going to recommend something that's not connected to flaky test cases.
Go for it.
So a lot of the work that I do involves various types of processing of the abstract syntax tree of a Python program.
And so I thought I might first call out the AST package that's actually a part of Python.
Go right in, right?
Yeah.
Which is built in and an incredibly useful tool, which isn't available in a lot of programming languages.
The other two packages which are on PyPI, which I'll share about, is number one, libcst, which implements something that's called a concrete syntax tree.
And it's a super useful tool when you want to be able to make changes to Python code or detect patterns in Python code.
And you want to be able to fully preserve things like the blank space in the code and the comment strings in the code and things of that nature.
Libcst is actually the foundation for another tool, which is called FixIt.
And FixIt is a little bit like Ruff, except that it allows you to very easily write your own linting rules.
And then finally, the last thing that I would share on this same theme, Michael, is that there's a really fun to use tool by someone who is a core member of the Django project.
And it's called PyASTGrip.
And it actually lets you write XPath expressions.
And then you can use those XPath expressions to essentially query the abstract syntax tree of your Python program.
Incredible.
Okay.
Looks like I guess syntax trees are a little bit like XML, aren't they?
Okay.
And if anybody has to do work where they're building an automated refactoring tool or they're building a new linting tool or various types of program analysis tools, the packages that I've mentioned might be very helpful.
Thank you.
That was a bunch of good ones.
Owen, you got anything you want to give a shout out to?
Well, it's actually a bit spooky because I was also about to recommend Libcst as well.
So one small library I've used a few times is Radon.
So that's R-A-D-O-N, I believe it's spelled.
So this will basically calculate a load of code metrics for you.
Oh, nice.
Okay.
So these are from relatively simple things like number of lines while taking into account comments and that kind of things.
To more complex metrics.
So there's this maintainability index, which is basically like a weighted sum of a bunch of other code metrics.
I really like that one.
Yeah.
It's like it combines and says, well, it's like complexity is this, line length is that, function length, like all that kind of stuff.
Right.
And I've actually found sort of empirically there is appears to be some correlation in some cases to between having a high soil, having a poor maintainability to having very complex code.
Having very complex test case code and that test case actually being flaky, which is interesting.
Yeah, I can believe it.
Okay.
That's also a cool, I hadn't heard of Radon.
That's neat.
All right, guys.
Thank you for being on the show.
It's been a super interesting conversation.
Final call to action.
People either have flaky tests and want to get out of them or they want to avoid having them in the first place.
What do you tell them?
What are your parting thoughts?
So my quick parting thought is as follows.
We'll have some links in the show notes to various papers and tools that Owen and our colleagues and I have developed.
And we hope that people will try them out.
It would also be awesome if people can get in contact with us and share some of their flaky test case war stories.
We would love to learn from you and partner with you to help you solve some of the flaky test case challenges that you have.
Owen, what else do you want to add?
I think that's pretty much it for me.
I'd say probably the most important thing to do would just be just stick with testing.
Don't let flaky tests put you off test-driven development or anything like that because it's better than not testing.
Yeah, indeed.
All right.
Well, thanks, guys.
Thanks for being on the show.
Thank you.
This has been another episode of Talk Python to Me.
Thank you to our sponsors.
Be sure to check out what they're offering.
It really helps support the show.
The folks over at JetBrains encourage you to get work done with PyCharm.
PyCharm Professional understands complex projects across multiple languages and technologies,
so you can stay productive while you're writing Python code and other code like HTML or SQL.
Download your free trial at talkpython.fm/done with PyCharm.
Take some stress out of your life.
Get notified immediately about errors and performance issues in your web or mobile applications with Sentry.
Just visit talkpython.fm/sentry and get started for free.
And be sure to use the promo code TALKPYTHON, all one word.
Want to level up your Python?
We have one of the largest catalogs of Python video courses over at Talk Python.
Our content ranges from true beginners to deeply advanced topics like memory and async.
And best of all, there's not a subscription in sight.
Check it out for yourself at training.talkpython.fm.
Be sure to subscribe to the show.
Open your favorite podcast app and search for Python.
We should be right at the top.
You can also find the iTunes feed at /itunes,
the Google Play feed at /play,
and the direct RSS feed at /rss on talkpython.fm.
We're live streaming most of our recordings these days.
If you want to be part of the show and have your comments featured on the air,
be sure to subscribe to our YouTube channel at talkpython.fm/youtube.
This is your host, Michael Kennedy.
Thanks so much for listening.
I really appreciate it.
Now get out there and write some Python code.
I'll see you next time.