You know you should be testing your code, right? How do you know whether it's well-tested?
Are you testing the right things? If you're not using code coverage, chances are you're guessing.
But you don't need to guess. Just grab coverage.py maintained by our guest this week,
Ned Batchelder. This is Talk Python to Me, episode 178, recorded September 10th, 2018.
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 @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 Brilliant.org and Manning.
Please check out what they're offering during their segments. It really helps support the show.
Ned, welcome to Talk Python.
Hi, thanks, Michael. It's great to be here.
It's great to finally have you on the show. I cannot believe we are at episode 178,
and you have not been a guest on the show. How did this happen?
I know. You're doing something wrong over there. I don't know. You're doing something great over there.
You've got to episode 178, which is astounding.
Anyone who says, I'm going to do a thing and then does it 178 times, clearly is doing something right.
Yeah, we're coming up on, I think on three years.
Yeah, actually, maybe over three years. So I got to do some quick math.
But yeah, it's been going for a while, and it's really fun. I'm just absolutely loving it.
And we're going to dig into a project that you've been actually working on more than three years, right?
Yes. So coverage.py is a project that I've been maintaining for 14 years, which seems crazy.
That is really amazing. And kudos to you for doing that. That's great.
I think what I realized about myself is that I'm very inertial.
It's hard for me to start new things, and then it's also hard for me to stop old things.
Once you get them rolling, they just keep going.
That's right.
It's not a bad trade at all.
So before we get into all the details of code coverage and so on, let's just get a little background on you.
How did you get into programming in Python?
Well, so I've got kind of an unusual story that way.
So I'm fairly old for the Python world.
I'm 56 years old, and I got into programming because my mother was a software person, too.
She was a programmer in the 1960s and 70s, 80s and 90s, I guess, until she retired.
Back when programming was really hard.
There was no internet, not many books.
Programming was different back then.
Yeah, there was definitely no internet.
The books were in a big, huge three-ring binder at the other side of the room, et cetera, et cetera.
But the cool thing was that she would bring home some of those books.
So I remember as a kid looking through the IBM 360 programmer's manuals and puzzling over what this stuff might mean.
So I sort of come by programming naturally.
I've been doing it for a while.
I joke that it's the only skill I've got, so it's a good thing that people will hire me to do it.
And I got into Python probably in the year 2000, maybe 1999.
I'd been working at Lotus on Lotus Notes, which is a collaboration environment.
Was that in C++ before?
Well, Lotus Notes is written in C.
But the reason, the way I got to Python was that Lotus Notes, with its access controls and collaboration controls, someone said, oh, you should look at this thing called Zope.
It also does stuff like that.
And I looked at Zope, and I thought, you know, Zope's kind of cool.
I don't really need that.
But this Python thing it's written in seems kind of interesting.
And so basically from that point on, when I had a choice of tools for writing some little tools or some scripting or some automation, I would reach for Python.
And that's just grown and grown since then.
Now, I guess I've been using it for 18 years or so.
That's really cool.
And Python itself has grown with you, right?
I mean, Python of 18 years ago is not Python of 2018.
Yeah, and it's funny.
I don't even know what version of Python that was.
It might have been a 1.x, I guess.
Yeah, and Python has definitely grown.
I feel like I sort of made a technology choice there, and it's worked out very well.
I've watched Python grow into at least two new major niches since then, into web dev and now into data science.
I feel much more comfortable in web dev.
I feel a little bit like I'm getting left behind with the data science and machine learning.
And even just hanging out where people ask questions about things, it's very clear that the center of interest is outside my expertise now.
So I've got a lot to learn.
It's interesting that you can be an expert in Python after 18 years and be a beginner at the things that people want to use Python for.
Yeah, that's super interesting.
Yeah, like if somebody said, hey, Michael, go do this plot with Matplotlib and get this data loaded up with Pandas.
I'm pretty sure I could not do that without documentation or examples in front of me because I spend most of my time writing web and database code.
Yeah, and I have done Matplotlib in a little bit of notebooks.
And I'm, you know, I am like the typical, I'm on Stack Overflow.
I'm just searching for stuff.
I see a chunk of code.
I don't know what it means.
I paste it in.
It seems to work.
We're done, you know.
And I would love to have a deeper foundational understanding.
But I don't have day-to-day problems that need those tools.
So there's not much chance for me to really get that learning.
Yeah, I feel like it's sort of bimodal now.
There's these two big areas that Python is really being used a lot in, at least.
And, you know, did you catch Jake Vander Plaas' keynote at PyCon 2017 about Python being a mosaic?
I didn't.
2017 was the PyCon I missed of the last decade of PyCons.
That was a good one.
Well, basically it was, look, there's all these different ways and people are using Python and their goals.
And their entire purpose of using Python may be very different than the person you're sitting next to.
But if you learn to appreciate it, it just makes it richer.
And I thought it was a really great way to sort of say, like, look at all these things people are doing.
They have different motivations and whatnot.
But it's just as valid Python in style.
It's for a different use case.
Right, exactly.
Yeah, I feel like we're definitely there.
Yeah, one of the things that I do is organize the Boston Python user group.
And we have project nights every month.
And we just get a big room with a lot of round tables.
And we put labels on each table.
So there's a table labeled beginners and a table labeled web.
And then we get data and we get science and we get hardware.
And it's just really interesting to see the variety of uses that people are putting Python to.
And there was a woman who came last month, was sitting at the beginners table.
And towards the end of the night, I was asking her more about what she wanted to do.
And she mentioned biology.
And I said, oh, you should have at the beginning of the night, you should have stood up and said, I'm doing biology.
We could have found you some biologists to talk to.
And she laughed like I was joking.
But then I introduced her to the four or five biologists across the room who were doing biology with Python.
So it's really a very rich ecosystem of expertise and individual domains, which is fascinating.
One of the things I love about Python is a lot of people seem to come to it with another expertise, kind of like you were just saying, right?
Like if you're a C++ developer, there's a good chance you may be a developer first, right?
But if you're doing Python, you may be something else first that uses Python.
And I think that just makes us a richer community.
That's why I think it's doing so well in data science is that it's, for whatever reason, it's the kind of language and environment that those types of people can succeed in.
Yeah, absolutely.
So you mentioned the Boston user group.
This is a global podcast.
The internet doesn't have a zip code or whatever.
But for people generally in the Northeast, like you want to just tell them really quickly about it so they can find it if they don't know?
Sure.
So the Boston Python user group is a big group.
We run events twice each month, generally, a project night, which is basically a two and a half hour unstructured hackathon with some sorting by topic, like I just described.
And then most months we also run a presentation night where we try to find people to give talks.
We did lightning talks for August.
I'm working on grooming a web scraping talk for September, and we're going to have science talks for November.
And we're very friendly.
We're big and open.
We're on meetup.com or bostonpython.com.
And if you're anywhere around, come and see us.
We've had people travel as long as three hours to get to events.
So all of New England is kind of in scope.
Yeah, absolutely.
Well, I guess it depends on the time as well, right?
Rush hour and all that.
Three hours could be not far away in certain parts of Boston.
That's true.
The really memorable one was the father and son who took a three-hour bus ride down from Maine.
And that kid was 13, and he was one of the smartest people I've ever met.
And they were going to leave and get home at like 2 in the morning based on the bus schedule to have attended.
They only came once.
And I mean, I don't blame them, but it was very impressive.
No, that's cool.
Is there any way to remotely attend?
Any streaming options?
We've never managed to be routine about videoing the presentations, which is really unfortunate.
Because even in Boston, we have 8,000 people on the meetup group, and we can only fit 120 people in the room.
And we always have a waiting list.
So lots of people would like to see video.
But we have just never managed to find the staff to make it a regular thing.
It's almost got to be somebody's, their responsibility, their role to just do that, right?
Absolutely.
Yep.
And they've got to show up every time, et cetera, et cetera.
Yeah.
So another thing that you do, and I'm also super passionate about, has to do with online education, right?
Right.
Yeah, my day job is at edX.
EdX.org is the website that was founded by Harvard and MIT and puts university-level courses online.
We've got, I don't know, 2,000, 3,000 courses from 130 different institutions at this point.
And it's all Python and Django, and it's all open source, which is the thing that really appeals to me because I'm an open source guy.
I actually work on the open source team here at edX.
So we are encouraging and enabling other people to use our software to do online education.
There's about 1,000 other sites besides edX.org that use OpenEdX to do their education, which is thrilling.
Because as great as Harvard and MIT courses are, there's all sorts of other kinds of education that those institutions will never provide.
We just recently discovered there was a website in Indonesia which has something like 150 different courses all very, very focused on specific skills that might lift someone out of poverty.
You know, how to be a maid, how to do hairdressing, how to raise chickens, how to fix small engines, how to catch fish, like just all sorts of things.
Super practical vocational type things.
Super practical vocational in Indonesian for Indonesians.
And edX.org, as many courses as we're going to get.
We're never going to deliver those courses.
So having the software be open source, you know, we give away education on edX.org and we give away the software to give away education to the rest of the world.
And there's 1,000 sites out there that are using it, which is really, really gratifying.
Oh, that's awesome.
It sounds like a great project.
And it's mostly Python and Django?
Yeah.
It's almost, I mean, JavaScript, of course, too.
But yeah, it's all Python and Django.
And it's almost all open source.
Very cool.
And we're hiring, if anyone, you know, tell them Ned sent you.
There's a referral bonus.
Do you guys have remote positions or it's got to be in Boston?
The easiest thing to say is, let's say it's got to be in Boston.
Yeah.
We're not super good at remote, which is something I wish we could get better at.
But that's the reality of the situation today.
Yeah.
So if people are listening and they want a cool Python job in the general Boston area or they're willing to get there.
Yeah.
Awesome.
Get in touch.
Let's see what we can do.
Yeah.
That's really great.
It sounds super fun.
Okay.
So let's talk about this brand new project that you just started called Coverage PY.
That's right.
This is a podcast from December of 2004.
Exactly.
All right.
So let's talk about what is code coverage and what is this project?
Okay.
Well, and let me just start with one thing, which is I didn't actually start this project.
This project was started by a guy named Gareth Reese.
And back in 2004, I was working on a different Python thing and I wanted to use some code coverage on it.
And I found this thing called Coverage.py and it worked almost exactly the way I wanted.
And the way it didn't, I tried to make a change and I tried to get it to Gareth and Gareth didn't seem to be reachable.
So I just sort of published it with my change.
And 14 years later, I'm the maintainer of Coverage.py.
That's how open source works, right?
That's how open source works.
Yeah.
I'm mulling the idea of doing a lighting talk called Lies People Believe About Coverage.py, one of which is that I started it.
Yeah.
Okay.
But to answer your question, so what is code coverage?
So the idea of code coverage is you've got some product code that you've written, meaning the code you actually want to write.
And then to make sure that that code works, you write some tests.
And I'll give the entire audience the benefit of the doubt and say you have written tests.
But now you need to know, are the tests actually doing their job, which is proving that your code works?
And one way to test your tests essentially is to observe the tests running and see if all of the lines of your product code were executed by the tests.
Because if there's a line of code that isn't executed when you run your entire test suite, then there's no way that line of code can be tested.
The converse isn't true.
If the line of code is run, it still might not be properly tested.
But if it isn't run, then it's definitely not properly tested.
Absolutely.
If it was never executed, you know nothing about it.
Absolutely.
That's right.
There's no way you know how that line of code works.
So the code coverage in general is the automation of that process, which is it is a tool that can observe a program being run and can tell you what lines of code were run in the program.
And notice in that sentence, I didn't say anything about tests.
Coverage doesn't know anything about what a test is.
It's just that typically the program you want to watch is your code while the tests are being run.
But you could run coverage for any reason to know what parts were run or not.
Right.
This is typically most spoken about in terms of unit testing and other types of tests.
But one example that comes to mind right away is I've got some app.
It's been handed down from person to person and somehow it arrives in my lap and they're like, Michael, you've got to now add a feature or maintain this thing.
And it's a big scrambled mess.
And the person who knows all about it is gone.
Maybe I just want to know when it does its job, does it ever even call this function?
Right.
Like there could be all sorts of code in there that is just nobody wanted to remove it because they didn't know for sure it was OK.
But if you can run the coverage and say, actually, no, it's never executed.
Let's delete it.
That's right.
So long as you are sure that you know how to fully exercise.
Yes.
Yeah.
You got it.
That is another another thing.
But I've spent hours trying to understand what a particular function does and how it influences like a big program just to realize that actually the reason any changes I'm making to this section or try to make it do a thing have no effect because it's not being called.
Right.
Yeah.
It's super frustrating.
Right.
So coverage can be used for that.
But like you say, the 99.9% use case for any code coverage tool, including coverage.py, is for it to observe your test suite being run and then to tell you about your product code, which lines were run and which lines weren't.
Right.
And the idea being that the lines that weren't, that's what you focus in on.
And you think about how can I write a test to make that line of code be run.
And you'll gradually increase the coverage.
And then you're right.
Or you make a conscious decision.
This part we don't care to test.
Right.
That's right.
You can also decide that.
Yes.
But I think the important thing is, even if you're in that place, there is a core reason your application exists.
There is a thing that it does and there's stuff that supports it doing that.
Right.
If you're writing a stock trading application, the stock decision engine had better have a good bit of coverage on it.
Yes.
Or you failed with your test.
Right.
Testing the login like crazy doesn't help the core engine do anything better.
Right.
You could decide you're going to increase coverage on the parts you care about.
And coverage doesn't really have any opinions about this.
It's designed to just tell you something about your code.
I've found over the years I am drawn to projects that are all about helping developers understand their world better.
And coverage is one of those ways.
Right.
You wrote something and you wrote something to test it and you thought it was testing it.
And, oh, is it?
Well, coverage can tell you whether it is.
Yeah.
I really love that, the way it works.
So I guess maybe we could talk a little bit about how you run it.
Like, is this something you put in continuous integration?
Is it a command line tool?
Like, where does it work?
Right.
So the simplest thing is it is a command line tool and you can run it in your continuous integration on Travis or something.
Or you can just run it from the command line.
The design is that the coverage command has a number of subcommands, one of which is run.
And when you type coverage run, anything you could put after the word Python, you could put after coverage run.
So if you used to run your program by saying Python prog.py, then you can say coverage run prog.py.
And it will run it the same way that Python would have run it, but under observation.
That will collect a bunch of data.
And then if you type coverage report, it will give you a report.
And for every line, every file that got executed, it will tell you things like how many statements there are, how many got executed, how many didn't get executed, and therefore what percentage of them were executed.
And then a big total at the end.
Yeah, that's really cool.
So I could say like coverage-m unit test in some file or something like this?
Yeah, coverage run-m.
Yeah, okay, gotcha.
Right, dash-m unit test.
Yes, I'll just leave it at that, yes.
And there's also a pytest plugin, right?
There are plugins for pytest and for Nose.
And I'll be perfectly honest with you, I'm not a huge fan of the plugins because it's just another bunch of code between you and me, sort of.
Like, I don't understand exactly how those plugins work, so it's hard for me to vouch for them doing what you want them to do.
But yes, there are plugins for pytest.
So there's pytest-Cov, and when you install it, you now have dash-dash-cov, a few, about half dozen dash-dash-cov options to pytest to say things like, I want you to only look at these modules, or I want you to produce this kind of report at the end.
So it's much more convenient in that you don't, you can get all of the coverage behavior in one pytest run, rather than having coverage run pytest, and then coverage producing a report as two separate commands.
But like I said, there's a trade-off there in that if the plugin isn't doing what you want, then it's a little bit trickier.
Now you've got to figure out, is it the plugin, or is it coverage, or is it my script that's doing it?
It's one more variable in there.
Yeah, it just makes it a little more complex.
Okay, interesting.
Another place that I really like to run coverage.py is from PyCharm.
I don't know if you've ever seen the integration there, but that is just incredible.
You right-click on some part of your code and say, run this with coverage, and then you get a report in PyCharm.
But PyCharm, the editor itself, actually colors each line based on the coverage, which I think is a really nice touch.
Yeah, PyCharm is really an amazing IDE, which I don't actually use because I'm old.
There's also a Vim plugin, I believe, to do similar things to get the coverage data and to display it to you in Vim and probably for Emacs as well.
Oh, that's cool.
Yeah, the more convenient we can make it for people to see the information, the better off it's going to be, the tighter feedback loop you've got.
And right there in the editor is the best place to see it because that's where you're going to have to be dealing with the code anyway.
You're not trying to correlate some report back to some file.
You just look at it like, oh, why is that red?
That should be green.
What's going on here?
Right, exactly.
And coverage will produce HTML reports that are colored red and green and actually have a little bit of interaction if you want to focus in on things.
But if other IDEs or whatever can produce displays that make it more convenient for people, then more power to them.
Coverage has an API, Python API, which is, I assume, what PyCharm is built on, although they could be also just doing subprocess launches and things like that.
So, yeah, I'm happy to have people get access to the power of that coverage measurement, however they're most happy with.
Yeah, that's really great.
So, one of the things that I kind of hinted at before with the core trading engine and certainly with, say, like your test code, you probably don't care about looking at the analysis of the coverage of your test code.
You would like to see the analysis of your code under test.
So, how do you exclude some bits of code from being analyzed?
All right.
So, I'll answer your question first, and then I will challenge the premise of the question.
Okay.
Sure.
Good.
So, coverage gives – there's a bunch of controls that you can get used with coverage to, say, basically to focus its attention on the code you want.
If you run coverage just the way I started with, it will tell you about a lot more than what you care about because it's going to tell you about every library you've imported, even if it's not your code.
And that's partly because that's the way it used to be and partly because it's hard to know what counts as a third-party library versus your code if you're running your code in an installed setting and so on and so forth.
So, there are controls to focus coverage in on the code you want.
The simplest is the source option.
And the source option says, I'm only interested in any source code that you find from this tree downwards.
And, for instance, source equals dot is often a very good choice.
choice because it means I'm in the current working directory.
Here's my code.
Don't tell me about any code you find anywhere else.
But even more than that, you can say things like omit these file paths or only include these file paths.
So, there's a lot of controls there to focus coverage in.
And that's because an automated tool is great unless you're constantly knowing more than the automated tool tells you.
Like, if the automated tool is like a noisy kindergartner yammering at you and you have to tell it, keep thinking to yourself, shut up about that.
I know that's not what I'm concerned about.
Then the tool is not useful.
Yeah.
This portion of Talk Python to Me is brought to you by Brilliant.org.
Many of you have come to software development and data science through paths that did not include a full-on computer science or mathematic degree.
Yet, in our technical field, you may find you need to learn exactly these topics.
You could go back to university.
But then again, this is the 21st century and we do have the internet.
Why not take some engaging online courses to quickly get just the skills that you need?
That's where Brilliant.org comes in.
They believe that effective learning is active.
So master the concepts you need by solving fun, challenging problems yourself.
Get started today.
Just visit talkpython.fm/brilliant and sign up for free.
And don't wait either.
If you decide to upgrade to a paid account for guided courses and more practice exercises,
the first 200 people that sign up from Talk Python will get an extra 20% off an annual premium subscription.
That's talkpython.fm/brilliant.
People will stop using it, right?
Like if it becomes too noisy and too annoying, then you're like, well, it would have been helpful.
But it's just so much noise.
Forget this thing.
Right.
Or there's actually useful information in it that they can't see or whatever.
So it shouldn't be that the way you use coverage is you run it, you get a report, and then you skip over all the stuff you know you don't care about to hopefully see the stuff you do care about.
So there's a lot of controls and coverage to let you be the smart one in the room and let it be the savant about the one thing that it is smarter than you about, which is what code got run.
So in addition to being able to exclude or include file paths and modules, you can actually put comments in your code to tell coverage, this line isn't run, but I don't care.
Like, don't tell me about this line anymore.
I'm okay with it not being run.
Yeah.
One of the examples you gave was the repper method, dunderrepper.
Right.
Do you really want to write a test that instantiates an object and just prints it just to get that function?
Right.
Like, no.
And I've got lots of dunderreppers in the coverage.py code, and they're only ever run when I'm debugging coverage.py.
I'm in a debugger, and I want to see what that is.
You want to see the string representation better than this type at that address.
That's right.
So I write a dunderrepper, and I don't want to be told that it's not being executed in my test suite because I don't run it in my test suite.
So – and in addition to putting comments on lines, what you can actually do with coverage is there's a coverage.rc file to configure coverage, and you can actually specify a list of regexes, and any line that matches one of those regexes will be excluded from coverage measurement.
And that's how the comment works.
The comment is a regex pattern by default in that setting.
But, for instance, when I run coverage, I put, like, def space dunderrepper as one of the regexes.
And that will match all my reppers, and then they'll all be excluded from coverage, and I don't need to worry about them anymore.
Yeah, that makes a lot of sense.
So you could put the comment hash, pragmical, and no cover, but you might not want that in your code all over the place.
Right.
You'd have to remember to put it in and et cetera.
Yeah.
People who don't care about it, they're like, why is this in this code?
Or, you know, they write their code and they don't add it.
Yeah, it's just – sometimes it's better to have it separate.
Yeah.
Right.
And what I actually have done, because I'm a little obsessive about it in coverage.py itself, is I have a half dozen or ten different comment syntaxes that I'll use to exclude lines from coverage because they're being excluded for different reasons.
Like a line that only runs on Jython, I will exclude from coverage because I'm not doing coverage measurement under Jython, even though I want to have a little bit of Jython support in the code.
And so I'll have a comment that says only Jython.
And then I know why it's been excluded.
That's cool.
Because I can just make a list of a dozen regexes and it's all there.
You put it into that configuration file.
Yeah.
Nice.
Another thing that I thought was interesting was sort of the converse of this is to explicitly include some files.
Because if you say, look here, and you run this one particular file, it only looks at the actual files that were loaded, the modules that were loaded, not the stuff laying next to it that maybe should have been reported on but nobody ever touched.
Right.
And that was one of the failings of early versions of coverage.py was that the only thing it knew about your code is code that got run.
And so, for instance, if there was a particular file in your source tree that was never executed at all, I mean, forget about lines not being executed.
The entire file was never executed.
It wouldn't show up in the coverage report because coverage had never heard about it.
Right.
If you run a line of code in a file, coverage knows about the file and then it can see all the lines that weren't run.
But if you never ran any of them, coverage never heard of the file.
So that feature is now in coverage.py.
If you give it a source option, then it has a tree to search and it can look for all of the importable Python files that it never heard of and tell you about those 0% files.
Right.
That's kind of like the example of me saying, here's this method that was never run.
And, like, not even the module is imported in the main bit of code.
Right.
And, by the way, there's a really cheap, low-tech way to do file-level coverage, which is you delete all your .pyc files and then you exercise all your code.
And any .py file that doesn't have a .pyc file was never imported.
Oh, right.
Yeah.
Okay.
That's pretty interesting.
Very low-tech.
It's quite low-tech.
I forgot to challenge the premise of your earlier question.
Yeah.
Let's get back to that.
Yeah, so you said you don't want to do coverage measurement of your tests, but it actually can be very useful to do coverage measurement of your tests, if only because the way test runners work, it's really easy to make two tests that accidentally have the same name.
You know, oh, I like that test.
I want to do one kind of like it.
I'll copy it and I'll paste it.
I'll forget to change the name.
And now I actually only have one test.
If I look in the code, it looks like there's two, but there's really only one.
Yeah, yeah.
It's so easy to do that.
And so if you do coverage measurement of your test files also, then you'll see those cases.
Interesting.
Okay.
I guess, yeah, I never really thought about that.
That's pretty valid.
You know, I was thinking more of like you might say like ignore a particular test and you don't want that to say break the build because it drops it below some percentage or something like that.
But yeah, that's a very – because I feel test code is probably some of the most copy and pasted code there is.
Exactly.
And you never actually use the function name directly.
So you'd never know.
Right.
The name of the test function doesn't matter.
Yeah, exactly.
It's really easy to lose a test.
All right.
I accept defeat on this one.
No, that's a really good point.
All right.
Well, spur one for me.
The other thing, when people say they want to exclude their tests from coverage, often it's because they've set a goal for their coverage measurement.
Like we need 75% coverage.
And those goals are really completely artificial.
Like how did you choose 75%?
Like why – like a quarter of your code doesn't need to be tested.
Why is that okay, right?
So the way I look at it, the number – the coverage number has no meaning except that lower numbers are worse.
That's the only meaning to the number.
So if someone says like how much coverage should I have, there's no right answer to that.
Yeah.
You know, I guess probably your test code has a pretty high coverage rate relative to your other codes.
Yes.
You're only helping yourself in that number.
That's right.
You can gain the system.
Exactly.
I guess one of the reasons I was thinking about excluding it is I just don't want to see it in the report.
Like I don't need to see the test coverage.
But your example of this copy and paste error actually is pretty valid, I think.
Well, another option in the coverage.py reports is to exclude files that are 100%.
Right.
Which, again, lets you focus in on where the problems are.
Like you don't need to think about files that have 100% coverage.
What you need to think about is the ones that are missing some coverage because you need to go and look at those lines and write some tests for those files.
So if all your tests have 100% coverage, then include them and exclude the 100% files from the report.
Right.
And hopefully that does a smaller list.
Yeah.
You can always use the pragma stuff on, say, your test code to say,
Well, these three parts I'm having a hard time getting to run for whatever reason.
And just, you know, tell it to not report on that.
It'll hit it to 100 and it drops out of the list, right?
Right.
Exactly.
Yeah.
The other thing about test code I find is that in a full, mature test suite, you've got a significant amount of engineering happening in your test.
If not in the tests themselves, in the helpers that you have written for your tests.
Yeah.
And not that there's code in there that isn't being run and should be run, but there might be code in there that isn't being run and therefore you can delete it.
Right.
Yeah.
That same conversation of like, let's, I do feel like a lot of times people treat their test code with less, what's the right word?
Kind of professionalism or attention.
They're like, well, this is test code.
So it doesn't matter that this big block of code was repeated 100 times.
Why would I ever extract the method to that?
Like there's just less attention to the architecture and patterns there.
And I feel like that would help.
Right.
And so I agree with you that repetition should be removed from tests as it is from elsewhere.
But I've also heard people feel passionately that tests should be repetitive, that each test should be readable all by itself.
So I'll, you know, give them the benefit of the doubt that maybe that's what they want.
And that's fine too.
Yeah, sure.
And if that's a conscious decision, then that's fine, I think.
But a lot of people just do it because, well, they wrote one test and then they copied it and then they edited it and they copied it and they edited it.
You know what I mean?
Yeah, exactly.
Right.
And they don't have time to make the tests nice because who cares about the tests?
Yeah, exactly.
Until you change the thing under test and all of a sudden it's so hard to get it to run because you had poor decision making around writing the test.
Then you claim unit testing is too hard because I don't want to do it and so on.
Yeah, writing tests is real engineering with different problems than writing your product code.
And those problems need to be paid attention to, which that's probably a whole other episode.
Yeah, I've certainly heard people make statements like that they don't really understand, you know, things like object oriented programming and other proper design patterns until they started writing tests and trying to make their code more flexible.
Like, how do I actually get in between the data access layer and my middle tier logic and test that without having a database and things like that?
Yeah, they really make you think.
Yeah, they do because it's a second use of your code.
And your code is going to be way better designed if you consider more than one use of the code.
Yeah, absolutely.
Testability is a great topic.
Yeah, I actually, yeah, I totally agree.
And I think that, what you just said right there is one of the main reasons to test is the architecture that comes out of it.
All right, so you spoke a little bit about these config files.
And in these config files, I can put regular expressions, which will, you know, limit these sections.
Oh, and one point I did want to make really quick about that.
When you put one of those comments, if you put that on like a branch or some kind of structure, like a class, like everything underneath that will be blocked.
You don't have to like put that on 100 lines, right?
You put it at the sort of root node.
Yeah, exactly.
And it's, so it's basically if you put it on a line with a colon, then that entire clause is excluded.
The one thing that sounds like it might be a generalization of that that doesn't work is to put one of those comments at the top of a file.
It doesn't exclude the entire file.
There's a request to do that.
That seems like a good idea, but we've never, haven't gotten to that yet.
Okay, cool.
So, but back to config files, there's more stuff that you can put in there.
Like what are useful things that are people are doing there beyond just exclusion?
Yeah, so there's some basic options.
Like one of the things that coverage.py can do is to measure not just which statements got executed, but which branches got taken.
So, for instance, if you write an if statement, it can tell you whether both the true case and the false case were executed.
And you might say, well, I can tell that by looking at the code in the true case and the code in the else clause, but not every if statement has an else clause.
So, if you have an if with a statement in it, but there's no else clause, you can't tell just by looking at individual lines that are executed whether the false case of the if was executed.
Right.
Did you effectively skip that if block?
Right.
Have you ever actually skipped that statement in the if?
Yeah.
And so, and branch coverage is what can do that.
And so, that's one of the options you can set in the config file as well as on the command line.
I mean, basically, just to go back to the plugins question, the original reason I wrote support for the coverage RC was because I was adding features to coverage and people were using the test runner plugins as their UI.
So, I didn't have any way to give them to try new features and coverage until the plugins were updated.
I see.
So, I added support for an RC file so you could control coverage even under a plugin from a thing that I could actually add features to.
So, all of the features of coverage are controllable from the RC file.
A big thing that gets put in there, one of the complicated scenarios for coverage is if you have tests that run in parallel.
Either because you're running them on separate machines or you're just running them in separate processes or because you ran the 2.7 tests separately from the 3.6 tests.
And then you want to take all that data and combine it back together to get one coverage report.
And under those scenarios, there's often cases where, oh, on my CI system, the code was in this directory.
But back home where I'm going to write the report, it's all in this directory.
And coverage has to somehow know that those two directories are the same in some way, that they're equivalent.
And those file path mappings go into the coverage RC file, for instance.
I see.
Okay, that's cool.
So, that during the combination process, it can remap file names to get everything to make sense.
Yeah, that makes a lot of sense.
So, speaking of stuff in parallel, what's the story around like threading, async.io, multiprocessing, or even if I just want to start a subprocess?
Yeah.
Does it have support for that?
Do I have to do things special for that?
Yeah, so that gets complicated, too.
So, there are four kinds of concurrency that coverage.py supports right out of the box.
Threading, multiprocessing, gevent, and green.
I forget which one of the other ones.
I forget what it's called.
And there's code in coverage.py that's specifically designed to see that that's happening and do the right thing.
You have to tell coverage which one you're going to use, but once you do that in your coverage RC, it knows how to do that.
If you're running your own subprocesses, it gets a little bit trickier because you've got Python code in your main process, and then you're going to spawn a subprocess, which is a whole new Python process that's sort of jumped outside of what coverage is watching.
There's a little bit of support for coverage getting started on that subprocess.
You have to do some manual setup, and that's covered in the docs.
I would like to make that more automatic, but it feels a little intrusive to sort of have coverage start on every Python process you ever start in the future.
I'd rather be conservative about that than suddenly be in the middle of something where people didn't expect it.
So, we try to support all those different concurrencies.
I just got a report that QT threads don't work, which doesn't surprise me because they're C threads, and so how do I get involved in that?
Right, it doesn't carry that process across, yeah.
Right, and it gets very fiddly.
Some of the worst code in coverage.py is in the way we support those kinds of concurrencies because we need to get involved at the very beginning of a thread or the very beginning of a process,
and there isn't always support for me to just say, hey, next time you start a process, why don't you run a little bit of my code first?
No, that's not your process.
Yeah, we do some very invasive things there.
If you're interested, go and look at multiproc.py.
Yeah, how interesting.
This portion of Talk Python to Me is brought to you by Manning.
Python has become one of the most essential programming languages in the world with a power, flexibility, and support that others can only dream of,
but it can be tough learning it when you're just starting out.
Luckily, there's an easy way to get involved.
Written by MIT lecturer Annabelle and published by Manning Publications,
Git Programming, learn to code with Python, is the perfect way to get started with Python.
Anna's experience as a teacher of Python really shines through as you get hands-on with the language without being drowned in confusing jargon or theory.
Filled with practical examples and step-by-step lessons, Git Programming is perfect for people who want to get started with Python.
Take advantage of a Talk Python exclusive 40% discount on Git Programming by Annabelle.
Just visit talkpython.fm/manning and use the code belltalkpy.
That's belltalkpy, all one word.
One thing that the documentation talks about is this thing called sitecustomize.py.
Right, exactly.
I had never even heard of this.
You're like, oh, you just put it in here.
I'm like, what is this?
Tell them about this.
It's all sort of the start of the initialization of Python itself, right?
Right, exactly.
So this is about how do, if you're going to run a Python program, how can I make it so that my code runs before your program even starts?
Right, because if you're going to run a subprocess and it's a Python program, I want coverage to start before your code starts.
One way to do that is for you to change your code so that instead of launching your Python program, you launch it with coverage.
But that gets very invasive.
No one wants to do that.
You're not going to run your product by launching coverage, so your test would have to be different.
Probably not.
Product works.
Yeah.
So I looked around for ways to have code run before your main program.
And there's no sort of built-in support for it.
Perl actually has a command line switch that says run this program, but first run something else.
Python doesn't.
So there are two ways to do it.
One is that when you run a program in Python, if it finds a file called site, customize.py, it will run it.
And I don't know exactly what that file is for.
It sounds like the kind of thing that would be recommended against these days.
Seems weird.
Like if you need something for your program, right?
Don't put it into a weird file in your site packages.
Somehow put that in your main program.
But it's there.
So one of the ways to get coverage to run before your subprocess starts is by putting something inside
customize.py.
And can you make that just like a file alongside your working directory, the top level of your
working directory?
Or has it got to go somewhere else?
Honestly, I'm not quite sure.
When I have to do this, I use the second technique for doing it because changing a file is scarier
than creating a new file.
And so the second way to do it only involves creating a new file, even though it's perhaps
an even more obscure way to get code to run before the start of Python.
Now, we don't have to go through all the details.
But if you go into your site packages directory and you look for .pth files, path files, they
have this very bizarre semantic, which is if a line in that file starts with the word import,
then it will execute that line, even if the line has way more stuff than just an import
statement.
Weird.
Yeah, it's super weird.
It's super weird.
And you can name it anything?
Just like?
Yeah, anything .pth.
Yeah.
This is all part of how site packages and the path gets set up.
Just a little bit of backstory.
Working at Coverage is fascinating because just programming technology is interesting,
but you also get to discover all sorts of really weird, dark corners of the Python world.
And I've gotten in the habit of every time a new alpha of CPython is announced, I get it
as soon as I can, and I build it, and I run the Coverage test suite.
And some of the core developers are used to, like, we better get the alpha out there so that
Ned can run the Coverage test suite and tell us what we broke.
So I think 3.6 RC3 was because of me.
Oh, wow.
Because of a bug I reported from the Coverage.py test suite.
Yeah, there's a lot of weird, dark corners and a lot of kind of gross hacks to make things
work.
It's very difficult.
So for instance, I said Coverage run works just like Python.
Well, it's not easy to make a program that will run your Python program just the way Python
runs your Python program.
And there's some extensive tests in the Coverage.py test suite that actually do run the two side
by side and then do a bunch of comparisons of the environment in which the code finds itself
to try to assert that it does run it the same way.
Yeah.
I don't envy the job of keeping that compatibility working.
Yeah.
Well, luckily, we have a good test suite.
Yeah.
Well, I bet it has good code coverage as well.
Yeah.
Well, the sad thing is that there's code inside Coverage.py that because it's at the very
heart of how coverage measurement gets done cannot itself be coverage measured.
So my code coverage is at about 94%.
That's still pretty good.
But yeah, that's still pretty good.
That's quite ironic.
It is ironic.
You'd think like me of all people, I'd have 100% test coverage.
But and I've thought about really weird, hacky ways of trying to get at that stuff.
But it's just not worth it.
Yeah, I totally hear.
So you spoke a little bit about the complexities and challenges of making it work.
Let's dive in a little bit to how this actually works, right?
Yeah.
It seems like magic that I type your command line thing and then numbers all spit out of,
well, here's exactly how your code ran.
Like, how does that work?
Yeah.
So at the very heart of it is a feature of CPython called the trace function.
So if you look in the sys module, there's a function called set trace.
And you give it a function and it will call your function for every line of Python that gets
executed.
And that function can do whatever it wants.
It is the basis for debuggers, for profilers, and for code measurement tools.
And for some other things like tools that will let you run your program and have it just print
every line of code that gets run.
So you can sort of get a like a global log of everything that happened.
Right.
Some of those like sort of inspectors that will print out like an execution of your code as
it runs, like the actual lines that are running.
Yeah, those are pretty neat.
Yeah, exactly.
Yeah.
Doug Hellman wrote a cool one called Smiley.
Smiley.
Okay.
It does some cool things.
Yeah.
It spies on your code.
So at the heart of it is that trace function.
And for instance, if you've ever had to break into a debugger from your code and you type
import PDB, PDB.setTrace, the reason it's called .setTrace instead of what it should be called,
you know, break into the debugger, is because that's the function where PDB calls setTrace to get its trace
function in place so that it will get told as lines get executed and then you're debugging.
So that's the core CPython feature.
And you can go and write, you know, the 10 lines of code that do interesting stuff with the trace function.
It's actually pretty simple.
Coverage.py has way more lines of code than that.
But, you know, at its heart, that's what it's doing.
It sets a trace function and then it runs your code.
And as its trace function gets invoked with, you know, this line got executed, this line get executed, etc., etc.,
it records all that data and dumps it into a data file.
And that's the run and out phase of Coverage.py.
All that raw data then gets picked up when you say Coverage report.
It reads that data.
It looks at your source to analyze the source to figure out what could have been run.
Right in that first phase, all we hear is what got run.
The analysis phase is let's look at the source code.
How many lines are there?
What could get run?
And then essentially it just does a big set subtraction.
Here's the lines that could have been run.
There are the lines that did get run.
What's left over are the lines that didn't get run.
I mean, conceptually.
One thing that you did talk about in how it works is in the documentation is that you actually use the PYC files as part of this, right?
You've got to look in there for the line numbers and stuff, which is kind of interesting.
And that's one of the things that's actually changed a number of times over the course of Coverage.py's life is, well, how do we know what could have been executed?
The question of what did get executed is kind of straightforward because we get it from the trace function.
There's not much we can do there.
I mean, there's a lot to do there.
But there's essentially only one way to know what got run.
Figuring out what could have been run, there's a bunch of different ways.
As you mentioned, .pyc files compiled into them are a line number table that tells Python which lines are executable.
And that's how Python actually decides when a next line has been run.
The trace function, it's executing bytecodes.
It calls the trace function when the line number table says this bytecode is the first bytecode on a new line.
Right, because if I write like print, you know, quote, ned, comma, quote, batch elder, that actually is like multiple steps of the instruction.
If I disassembled that, that would be like load the first string onto the stack, load the second string, execute the function, get the return value, all that type of stuff, right?
That's right.
So the trace function only gets called for lines.
And so we're looking at the same information the Python interpreter is for deciding where the lines are.
It gets more involved in that because, for instance, for branch coverage, we have to decide where the branches are.
And we do that by analyzing the abstract syntax tree.
We used to do it by analyzing the bytecode back when I thought, oh, well, the bytecode will have all the ifs and jumps I need.
But it doesn't, and it never did.
And when the async keywords got added, they were completely different than everything else.
And I said, OK, fine, we're getting rid of all this bytecode analysis.
And I'm going to do an AST analysis because I understand the AST when I don't understand the bytecode.
All right.
So it does support async and await?
Yeah.
You know, I have to be honest.
When that first came out, I didn't know anything about async and await.
I didn't understand how to write programs that use them.
And I sort of took the low-tech, lazy maintainers approach, which is I'll wait for the bug reports to come in about how it doesn't work for those things.
And no one has ever said it doesn't work.
So I guess it works.
Yeah.
All right.
That'll do it.
Yeah.
Cool.
So I guess one question is calling functions in Python is not one of the fastest things that Python does relative to, say, other languages.
So if you're calling this set trace function on every single line, how does that not just stop the execution, basically?
Yeah.
So, well, so the really simple answer is that if you write a Python function and set it as your trace function, then, yeah, you're going to have a significant performance hit.
Because it's going to call a Python function for every line of your code.
And if that Python function does anything interesting at all, then it's going to do a lot of work for every line of your code.
Inside Coverage, we actually have two implementations of the trace function.
One in Python and one in C.
And the one that's in C is there to make it go faster.
So there's a bunch of work to try to do as little work as possible on each call.
So, for instance, when we get told that we're calling a new function, we do a bunch of work to figure out is this function or is this file, rather, a file that we're interested in at all.
Have you already figured out how much coverage it has?
Like if it's 100, right, then don't do it again.
Well, we don't know.
At this point, we're in the run phase.
We have no idea about numbers like that.
All we know is this file is getting executed.
Is our current configuration telling us that that file is interesting or it's not interesting?
And if it's not interesting, then we try to quickly get out of that function and set some bookkeeping so that the function doesn't get called again until we return from the function, for instance.
So there's a lot of work to try to make the trace function as fast as possible.
Because especially for mature test suites for the types of people that are running coverage.
The one thing I can tell you about all of those test suites is that developers wish they ran faster.
Like I don't need to know anything about your test suite.
If you've got a lot of tests, you want them to run faster.
So the last thing I want to do is make them go even slower.
And I don't know what the typical overhead is of coverage on a test suite because it's going to vary so much depending on what else those tests are doing.
But the C code is there to try to make it go as fast as possible.
I see.
Yeah, that makes a lot of sense.
The pure Python implementation is there for, for instance, PyPy, which can't run C extensions.
Right.
But after a while, it'll start to speed up as they maybe consider it has to be jitted after like a thousand lines or something.
I guess so.
I haven't.
I don't know.
PyPy is still under the magic category for me.
And I don't know whether a trace function will actually get the jit applied.
That's the thing I am used to is that a trace function is not run like regular code.
And I don't know if PyPy can jit a trace function, for instance.
I have no idea either.
Yeah, I don't know.
Interesting.
There are a few speed ups in there specifically for PyPy.
Basically, when a PyPy developer tells me, hey, why don't you say this magic thing that you don't understand?
And PyPy will go a little bit faster.
And I'm like, great.
Sure, I'll put it in.
Yeah, PyPy is interesting.
So I guess, you know, that's a good segue over to which runtimes are supported.
Like, you mentioned jiton, you mentioned PyPy, obviously cpython.
What versions of cpython?
What else?
Yeah, so the current released version of coverage.py is 4.5.1.
And that supports Python 2.6 and Python 3.3 and up.
So 2627, 33, 34, 35, 36, 37.
The tip of the repo now, I'm already working on coverage 5.0 alphas.
And I've dropped support for 2.6 and for 3.3.
But the released version can do 2.6.
For a while, I was very proud of supporting 2.3 through 3.3, which is kind of an interesting trick.
But I don't have to do that anymore.
So PyPy, both 2 and 3, are supported.
I have run tests on jiton and IronPython.
They can't do all of it because they can't do the analysis phase because they don't have AST.
But you can run your code under jiton and IronPython for the measurement phase.
And then you use cpython to do the analysis phase.
I see.
I don't know how well that works.
I haven't heard a lot from people about it.
I don't know how much those jiton and IronPython are getting used these days.
There's another one called python.net.
I actually haven't used it for anything.
But I think it's a successor type of thing.
A little bit different than IronPython, but similar.
Is it?
I need to look into this.
I need more complication in my life.
Yeah, I haven't done a lot with it.
But python.net seems like a more maintained, different take on what IronPython was.
But I pretty much have exhausted my knowledge of it now.
All right.
Cool.
My question earlier was, what are your plans on supporting old versions?
So if cpython stops supporting 2.7, are you going to stop supporting it?
Yeah, that's certainly going to happen soon enough.
Or say 3.4, what's the timing?
Do you try to stay farther behind?
What's the thing?
Like I said at the top of the show, I'm pretty inertial.
So I tend to support things for longer.
So my philosophy about coverage.py development is that I want coverage.py to be available
for anyone who's trying to make their code work better for Python.
And the environments they care about, I will care about.
So for instance, I first ported coverage.py to 3.0 in 2009, which was pretty early.
And it was pretty rough around the edges.
But I thought, you know, if people are going to start porting their libraries to Python 3,
it'd be super useful if coverage.py were there to help them understand how their tests were doing.
So I tried very hard to get ahead of it.
And for that reason also, coverage.py has no installation prerequisites.
Because if I prerequire a library, then that library has to get into a new environment before coverage.py can.
And then we've got a chicken and egg problem.
Yeah.
It also gives me a principled reason to have written my own HTML template engine, which is fun.
That sounds like a pretty challenging thing.
Also challenging.
Another runtime, if you want to call it that, I guess, that seems interesting is Cython.
Does this have any...
But it seems to me my first guess is it wouldn't work with Cython.
But what's the story there?
I should really know more about Cython.
And in fact, it's been proposed to me that instead of writing C code for the trace function,
I should write Cython for the trace function, which I've just been resistant to because, like I said, I'm inertial.
So my understanding of Cython is that it compiles to C code.
And then when you're running, you're running C code.
So you need a C coverage measurement tool in order to understand what's happening there.
Yeah.
It compiles to a C file, which then compiles, like at least on Mac, to a .so file.
And the .so file is running.
Yeah.
Right.
Coverage.py won't be able to see any of that execution.
A C coverage tool could.
But then you'd have to figure out how to map that back to the Cython code that you're actually interested in.
Which actually brings up an interesting point.
One of the things that coverage.py has gotten in its 4.0 releases is plugins so that you can do coverage measurement of things that are not directly Python but result in Python.
So, for instance, there's a Django coverage plugin that can measure the coverage of your Django templates.
Because Django templates themselves have if statements and for loops in them.
So that's code.
You'd like to know if there's coverage there.
And so there's a plugin, the Django coverage plugin, that works with coverage.py to basically understand how Django executes the templates.
And take the raw Python information about the template execution and map it back to the lines in the Django template.
So that you can get sort of a red-green Christmas tree of your Django template.
Oh, that's cool.
Yeah, it's very cool.
And it was getting a little stale, but I've just gotten a new maintainer to take it over.
And so they just had a release today, actually.
Oh, great.
Yeah.
All right.
That's interesting.
It seems to me like there's probably enough information there, but that it's really not super straightforward.
For Cython.
But maybe plugins would do it.
Yeah.
I mean, the C file is a huge, massive thing relative to the size of the Python code.
But I do believe it has line numbers that map back to the Python.
So it's possible, I guess.
The reason that reminded me of the plugins was because I wanted to also make a Mako plugin.
So Mako is another HTML templating engine.
And Mako and Django work completely differently.
Mako actually compiles your template to a Python file and then runs the Python file.
Whereas Django templates, there's Python code that's actually running on an abstract syntax
tree of the template.
It's sort of.
So in that sense, Mako is kind of compiled to Python and Django is kind of interpreted in
Python.
And so the plugin technologies had to be completely different.
And the Mako template has pointers back to the lines in the template, but they're a little
bit inaccurate.
And so I was frustrated at not being able to sort of close that loop because of a limitation
in Mako.
And Mako seems kind of unmaintained at this point.
So it kind of didn't go anywhere.
Yeah.
Interesting.
It's cool that those are there, though.
People want to use them.
Yeah.
It's very gratifying.
Yeah.
So last question before we run out of time, because that's where we are.
OK.
What's next?
What are the upcoming features?
Right.
So I mentioned that I've got some alpha.
I've already released 5.0 alpha 2.
The big feature that's coming up is something that I colloquially call who tests what, which is
instead of just telling me that that line got run, tell me which tests actually
ran that line.
And people are interested in this for all sorts of reasons.
But it can require some significant changes to the core of coverage.py.
It's going to present some challenges in that if you have 1,000 tests, then you have to collect
roughly 1,000 times as much data now because you're essentially – it's as much data as
if you did a separate coverage run for every single one of your tests.
And there are some tools out there that kind of do this already by doing exactly that, running
coverage independently around each test.
But coverage will do it differently because it's all bundled together.
So there's an alpha out already now, alpha 2, which has switched the data format from a
JSON file to a SQLite database.
And I needed the SQLite database because if I'm going to dump 1,000 times more data, I want to
dribble it into a database.
And I want to give you a database you can query because I don't know how people are going to use
the data.
Do indexes on it and all that stuff.
I have no idea how to report on this data because I'm not going to make an HTML file that for every
line of your source code has the names of the 200 tests that ran it.
Yeah.
So we need to get this out there and into people's hands and start seeing what people
do with it.
That's the big feature that's coming up.
That's cool.
One of the most interesting things I think you could do with that, just listening to you
describe it, would be if I have 1,000 tests and I change a little bit of my Python code and
only three of those tests should actually interact with those lines, theoretically, I could just
run those three tests to retest it, not all 1,000, which would be dramatically awesome.
Right.
Exactly.
There are also some tools out there that do that now, too.
Kirk Strausser has one, and the name of the tool escapes me at the moment, but he's got
his own trace function that essentially does a mini coverage measurement of his own to get
at that information.
The thing I'd be interested to experiment with that information is, what if my code is over
tested?
If my test suite is taking too long, maybe that's because I've got 100 tests all getting
at the same information, and if I could reduce it to 10 tests, then it would take a tenth
of the time.
And I honestly have no idea whether who tests what will give me the information that I'd
need for that, but it'd be interesting to play around with.
Yeah, it sounds really positive.
Yeah, I'm looking forward to having the data, at least.
Yeah, cool.
All right, let me ask you the two final questions before you get out of here.
Okay.
You hinted at this at the beginning.
If you're going to write some Python code, what editor do you use?
I use Vim.
I mentioned earlier that I am old, and I do not use Vim because I'm old.
I've only been using Vim for about 10 years.
So I got to it late in life, but it really suits my low-tech mentality, I think.
Beautiful.
And beyond coverage.py, what is a notable Python PyPI package that maybe people haven't heard
of being like, this is awesome, you should know about this?
Yeah, so I'll tell you the one.
So this is the very last thing that happened to me at PyCon this year.
I was literally almost dragging my suitcase out of the convention center to go catch a
plane when I stopped by the PyLint sprint and heard about a package called checkmanifest.
And it only does one little thing, but it's a thing that no one really cares about well
enough to get it right themselves.
So it's great to have a helper, which is it tells you whether the manifest.in that you wrote
for your setup.py has all of the files from your working tree or not.
Okay.
So really sort of a check on your package before you ship it off.
Exactly.
And packaging is one of those things that everyone hates.
It's no one's first love.
No one wants to think about it.
It's very confusing.
What's a manifest.in?
Why is that different than package data?
I don't get it.
And so checkmanifest, it just does one little thing and it's beautiful.
And I had never heard of it before.
And it seems like people should be screaming it from the rooftops.
Yeah, awesome.
Well, that's exactly the thing I'm looking for.
Thanks for sharing that.
That's right.
A second library is tqdm, tqdm, which is a progress bar library, which is very cool.
Yeah, I really like that one.
I've been using those types of progress bars lately and they're pretty cool.
Yeah.
Nice.
Okay.
Final call to action.
People are excited about coverage.
Maybe even they're excited about edX.
What do you want to leave folks with?
Read about coverage.
I've got some docs that I think are good, but that's because I wrote them.
So I can confirm that.
They were very good.
I went through the docs to do a lot of research for this show and they were at the right level.
What I wanted to know, but not so much.
I couldn't get through them.
It was perfect.
Good.
That's good to hear.
I hang out.
I hang out on the Python IRC channel and I love to see people there.
I think it's a great way to connect with people.
I like to think of it as a nice IRC channel.
So if you've been to IRC and didn't like it in the past, try the Python IRC channel on Freenode.
It's openedx.com.
Openedx.org.
I'm nedbatt on Twitter.
You can follow me.
I've got a blog that I've been running for far too long.
If you want to read what I thought about 16 years ago, get in touch.
You know, I like hearing from people.
Yeah, that's awesome.
The internet is written in ink, right?
All that stuff's still there.
So I definitely find your blog interesting.
There's some topics in there I would love to have you back on to talk about.
But for this one, we're going to have to just leave it here, I think.
So thanks for being on the show, Ned.
Sure.
Thank you, Michael.
This is great.
You bet.
Bye.
This has been another episode of Talk Python to Me.
Our guest on this episode has been Ned Batchelder, and it's been brought to you by Brilliant.org
and Manning.
Brilliant.org wants to help you level up your math and science through fun, guided problem
solving.
Get started for free at talkpython.fm/brilliant.
Learning Python doesn't have to be overwhelming or intimidating.
Check out Get Programming by Annabelle from Manning.
Just visit talkpython.fm/manning and use the code belltalkpy to get 40% off.
Want to level up your Python?
If you're just getting started, try my Python jumpstart by building 10 apps or our brand new
100 days of code in Python.
And if you're interested in more than one course, be sure to check out the Everything
Bundle.
It's like a subscription that never expires.
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.
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 guys next time.