Have you ever written a single file Python application or script? Have you written tests for it? Do you check code coverage? This is the topic of this weeks episode, spurred on by a listener question.

Transcript for episode 147 of the Test & Code Podcast

This transcript starts as an auto generated transcript.
PRs welcome if you want to help fix any errors.

00:00:01 Have you ever written a single file Python application? Have you written tests for it? Do you check code coverage? This is the topic of this week’s episode, spurred on by a listener question.

00:00:14 Thank you, Python, for sponsoring this episode. Even if you are comfortable with another editor, I encourage you to try PyCharm for use with your test code. With PyCharm, tests are easy to run, debug, and maintain. So test functions. When you look at them in the code, they have a little green play button next to them that just begs you to click it. And then once you do run it, the output window just automatically appears. And if there’s any failures at all, Pytram puts links in the output that you can just click on and go right to the error in your code. It’s super cool and super fast. Try it yourself by going to Testingcode PyCharm and sign up for a free four month trial of the Pro Edition.

00:01:10 Welcome to Testing Code.

00:01:19 I think of single file applications as scripts. I’m not sure if it’s because it’s a single file or because it’s often a focus thing that I used to use Bash or Pearl for back in the day build scripts and such like that, and I’m used to thinking of those things as scripts. I went through a phase where I was offended when someone referred to any Python program as a script, but apparently I must have gotten over that since now I do refer to these single file applications as scripts. Anyway, that term isn’t what we’re talking about today, what we’re trying to answer as a listener question, and the results surprise me a little bit. So here’s the question.

00:02:00 I write a lot of single file command line scripts. I’d like to have test code included right there in the file. Can I do that with pytest? And then a follow up question? If I can, can I use code coverage to test this? So that’s where things get interesting. Let’s start with the first question first and then get to the coverage question. The answer to can you put the test code right in the single file? The answer is yes, you can put test code right into it. But I have got a couple of recommendations about this. So first, try to keep all of your code in functions, not code that runs just outside of a function, and then have a main function that calls your other functions or maybe all of your codes in the main function. And then you call that main function using a if done name equals Dunder main block.

00:02:57 So far, this is common standard practice for a lot of people, but it might be new to some new people new to Python. I’ll have an example. I’m going to put this entire example in the show Notes, so you can take a look at it there, or you can look at it right now while you’re listening now personally, if I was going to stick test code right into my script, I think I’d probably put it at the bottom. I mean you could interleave test code within the rest of your functions. Like let’s say you’ve got a Foo function, you could have test food right underneath it, right above it doesn’t really matter. But I’ve got a reason why I think I’m going to stick it way at the bottom. So after my if done or name equals Dunder main, I’m going to put probably a comment divider that says test code and maybe some comments about how to run the test code and then my test functions. So it’s just going to be a bunch of test underscore functions. The cool thing about this is my main code isn’t going to call any of that.

00:03:56 The script isn’t going to call it. So when I’m running the script as normal, just like pythonscript PY, it’s going to run the rest of the stuff. And those are just like dead functions that aren’t going to be run, but pytest will see them. So if I run the whole thing with pytest, if I say pytestscript Pi instead of pythonscript. Py, well then it’s going to ignore all the rest of the stuff at first and run the test code. It’s going to run all the test underscore functions. So that’s easy. So yes, we can put test code in our file. And actually it’s a cool idea. I never really thought of doing that before. I probably would have put them in a separate file. But if I’m sharing scripts around and they get copied and moved around and stuff and other people make modifications for their own reasons or maybe just have a bunch of them somewhere and not packages, this is a cool idea to just keep the test code right in the file. So now let’s add the coverage idea. So how do we coverage to work with this? Now I thought this was going to be trivial and it’s not difficult, but it’s not obvious to me either. So my first attempt was to just have it in a directory with a bunch of other stuff and just call actually I like using coverage with the test and code plugin. It also does the coverage report, the text based report right after it, which is kind of cool. So if I do a Pip install pytest. Cove then I can run pytest and then I have to give it a path to the source. So let’s say PY test.

00:05:33 Cove equals the dot. So like current directory and then I just run script. Pi afterwards. This will run all the test and code a coverage report and it will analyze everything in the current directory. Well if there’s a whole bunch of stuff in there, that’s more than I want. So okay, so let’s isolate that first. So let’s say I’ve got a food directory and you just need a directory. So you can put the script in by itself. So I put the script in a Foo directory. Now from the above directory I can call Pytes Cove equals Foo and then Foo script Pi. So I’m telling coverage where the source is, the source is in Foo and then I call Foo Foo script Pi. Now this all works.

00:06:29 It’s going to run coverage over my code.

00:06:33 It’s not going to be 100% though. And the thing it’s going to miss is it’s going to miss even if I do have 100% coverage, it’s going to have that Dunder name equals Dunder main block. That’s not going to get hit because I’m importing pytest will import this project. It won’t run it. So the name equals main is never going to get true. So it’s not pretty. But I’m going to throw up comment in there. The Pragma no cover around the Dunder name equals Dunder main. That’s kind of neat. Another cool aspect of this and this is one of the reasons why I wanted all my test codes. At the bottom is I know that in my example I’ve got my test code starts at line 13. Yeah, it’s a really small script so I can actually run coverage and do a coverage report and look at all the missed lines and you can tell coverage to show the line numbers. And any line after 13 is going to be tested. So any line before it is going to not be test code. This is kind of cool. Why does that help me? Well, if it’s after 13 something went wrong and I’m not running all the tests and if it’s before 13 I’m missing some of my source code and then I can take a look deeper. Now with coverage you can also just specifically and pipes, you can run coverage on everything. And just like let’s say I’ve got a little example, I’ve got Foo as a function and then I have a main function that calls Foo and then in my Dunder name equals main. I’m just calling main. Well if I have a test for Foo and a test for main, I might get more coverage even for functions that aren’t being used. But I could specifically say what’s the coverage of my code if I just run the test main, just one of the top level test function.

00:08:34 This is kind of a neat way to just see what code’s really being used.

00:08:39 Anyway, that all works great and I love it. So I’ve got to throw the Pragma in. It unintentionally. I have to put it in a directory. Now I really would have liked to be able to just say tell coverage.

00:08:53 This file is the only source, but the way you pass it where the source is, it expects a directory. It won’t accept just a single file, whatever is the way it is. One of the things I do want to point out is for some reason I have this notion that I probably shouldn’t import pytest in the code and I haven’t had two so far but even without importing pytest I have access to all the built in fixtures which is cool. So for instance I’ve got a main that just prints some stuff out and I want to test that. I can use PY tests Capsis fixture to capture the output standard out and test against that. So yes, within a single file script without any reference to Pytest at all I can write test code that can read the capture the output and check against that. It’s so cool if I do want to import Pytest, I haven’t tried this part of it. I wouldn’t want to just import Pytest right here because the user of the script might not have Pytest installed and it should still work fine. So I might throw the import of Pytest into a try accept block. So like try import Pytest, accept import error and why would I do that?

00:10:10 You need to import pytest if you’re using parameterization or markers or there’s a whole bunch of other reasons why you might have to import pytest but you can get a lot done without importing Pytest. Anyway, take a look at the example. I’d love to hear what you think and I’d like to hear if you test your single file applications. Also, do you call them scripts like I do or do you call them single file applications?

00:10:35 Anyway, if anybody else has some cool ideas please let me know. I’d also like to know if there’s a better way to get around the whole isolating the code thing other than putting it in its own directory.

00:10:52 Thank you PyCharm for sponsoring this show try Pycharmrself at testandcode.com PyCharm. Thank you Patreon. Supporters join them at testingco.com support show notes for this episode including the example that I’m talking about are@testandcode.com 147. That’s all for now. Now go ahead and say something. Maybe as the single file application or script or program or whatever you want to call it.