267 lines
20 KiB
Plaintext
267 lines
20 KiB
Plaintext
|
|
Episode: 4091
|
||
|
|
Title: HPR4091: Test Driven Development Demo
|
||
|
|
Source: https://hub.hackerpublicradio.org/ccdn.php?filename=/eps/hpr4091/hpr4091.mp3
|
||
|
|
Transcribed: 2025-10-25 19:31:21
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
This is Hacker Public Radio episode 4,091 from Monday 8th of April 2024.
|
||
|
|
Today's show is entitled Test Driven Development Demo.
|
||
|
|
It is the 20th show of Norrist, and is about 27 minutes long.
|
||
|
|
It carries a clean flag.
|
||
|
|
The summary is Norrist uses Pytist to demonstrate TDD, with a Trivel HP R&F O-A.
|
||
|
|
So I've done a few episodes where I'll write something, some software and Python, and talk
|
||
|
|
a little bit about it.
|
||
|
|
At least a few times I've mentioned using Test Driven Development while I'm doing it.
|
||
|
|
So I thought I'd just give a quick example using a Tribule application about maybe kind
|
||
|
|
of demonstrating a method of doing Test Driven Development.
|
||
|
|
So my last episode was 4075, and I talked about making a Pomodoro timer using Circuit
|
||
|
|
Python, and in that episode I talked a lot about why I like to use Test Driven Development
|
||
|
|
or how it helps me, and I sort of explained what it is if you want to hear that or if
|
||
|
|
you missed it, you can go back and listen, 4075, but just as a quick refresher, what is
|
||
|
|
Test Driven Development, it's where you think about what you're going to do, what you want
|
||
|
|
your code to do, what you want your project to do, and before you write any code, you write
|
||
|
|
a test.
|
||
|
|
And you can do it in a little chunk, you don't have to test the whole application, but you
|
||
|
|
think about a small thing that you want your code to do, and you write a test for it, and
|
||
|
|
you run the test, and obviously the test is going to fail because you haven't written any
|
||
|
|
code yet.
|
||
|
|
So then you write the minimum code to get the test to pass, run the test again, see that
|
||
|
|
it works, and then optionally after you get the test passing, you can rewrite, rerun the
|
||
|
|
code, just make sure the test continues to pass while you're doing the rewrite.
|
||
|
|
So we're going to do our testing with a Python framework called PyTest, P-Y-T-E-S-T.
|
||
|
|
PyTest is just a framework for testing software, it's usually used to test other Python
|
||
|
|
projects, but it doesn't necessarily have to test Python, it can test anything that
|
||
|
|
returns an input.
|
||
|
|
And the good thing about PyTest is it's just Python, so the test is written in Python,
|
||
|
|
so if you understand, or if you know enough Python to write a script or something like that
|
||
|
|
in Python, then you probably know just enough Python to write the PyTest.
|
||
|
|
So it's easy to use if you already know a little bit of Python.
|
||
|
|
It makes heavy use of the Python assert statement, so in Python you can test that something is
|
||
|
|
true by using assert.
|
||
|
|
And I'll show it, I'll have examples later, but if you want to test a variable equals
|
||
|
|
one, you can just say assert this variable equals one.
|
||
|
|
And if it passes, it will, the script will continue, if it fails, the script will abort.
|
||
|
|
And then PyTest, if you use an assert statement, and it passes, your test passes, and if you
|
||
|
|
use an assert statement, your assert fails, obviously your test fails.
|
||
|
|
So PyTest automatically discovers tests, and it's not going to make a lot of sense right
|
||
|
|
now because I haven't even told you what test are or anything.
|
||
|
|
But whenever you go into a directory where your Python project is, you can just run the
|
||
|
|
command PyTest, just like that, you don't have to specify a file pass or anything.
|
||
|
|
And what it will do is it will look for files that start with the name test, and if
|
||
|
|
it finds one that will run it, or it will look for, it will look into the files and look
|
||
|
|
for functions, a Python function that starts with the name test, and it executes those.
|
||
|
|
So what we're going to do is we're going to make two files, one that contains the application
|
||
|
|
that we want to, our trivial application that we're going to be working on, and then
|
||
|
|
a separate file called test underscore something that we'll put our test in.
|
||
|
|
So let's say just as an example project, we want to write a Python script that will print
|
||
|
|
some summary information for the latest published HPR episode.
|
||
|
|
So let's say we want to print the title, the hosts or the correspondence, the date, and
|
||
|
|
the path to the audio file.
|
||
|
|
So if we want to print all this stuff out, we can click on a link and it will take us
|
||
|
|
to the MP3 or AUG or whatever for the file.
|
||
|
|
So we can plan ahead and think about what we might need to do that.
|
||
|
|
So in my mind, the easiest way to do that, and there's a couple ways to do it, but you
|
||
|
|
could scrape the website or something like that, but in my mind, that information is already
|
||
|
|
published for you in a format that's easy to parse in the RSS feed.
|
||
|
|
So what we need to do for our app is we need to figure out how to get the RSS feed, and
|
||
|
|
then we need to figure out how to parse the RSS feed, and we need to know what the URL
|
||
|
|
is.
|
||
|
|
Okay, so let's say our script that's going to pull down the information from the HPR
|
||
|
|
feed.
|
||
|
|
We'll name it hpr-unterscore-info.py, and then we'll make a test, another Python script
|
||
|
|
that's going to contain all our tests, and we'll name that test-unterscore-hpr-unterscore-info.py.
|
||
|
|
Now that's, I like to have my test, the name of the test file reference what it's going
|
||
|
|
to be testing.
|
||
|
|
That's not a requirement, just something I do, it seems to be convention, a lot of people
|
||
|
|
I've seen do it, but it's not a requirement, but it's kind of best practice that way.
|
||
|
|
If you have a bunch of files that start with test, you can just look at the file name and
|
||
|
|
know what it's doing.
|
||
|
|
Okay, so remember with test-driven development, before we even write code, we're going to
|
||
|
|
write our test.
|
||
|
|
So we'll create the file, test-unterscore-hpr-unterscore-info.py, and then we'll put one line in it, and
|
||
|
|
that one line will be import, space, hpr-unterscore-info.
|
||
|
|
That's it.
|
||
|
|
So remember, we're going to name our actual project, hpr-unterscore-info.
|
||
|
|
So in Python, and therefore in Pytest, when you want to reference some code that's in
|
||
|
|
a different file, use the import statement to do that.
|
||
|
|
So whenever we say import-hpr-info, what we're asking it to do is look for a file named
|
||
|
|
hpr-info, and so later we can reference variables or functions or other things that are in
|
||
|
|
that file to do the testing.
|
||
|
|
So we've got our test file with a single import statement in it.
|
||
|
|
Now we can run Pytest.
|
||
|
|
So again, when you run Pytest, you don't have to do anything, specify file pass or anything
|
||
|
|
like that.
|
||
|
|
As long as you're in the directory, it will find the file containing the test, if you
|
||
|
|
named it correctly.
|
||
|
|
So we just run the directory, we just run the tip command, Pytest, and we get an error.
|
||
|
|
The first error we get is module not found, no module named hpr-info.
|
||
|
|
So what it's telling you is it's trying to import hpr-info, but it can't because that
|
||
|
|
file doesn't exist.
|
||
|
|
So we've written our first test, our first test failed.
|
||
|
|
So we need to create the minimum code to get that test to pass.
|
||
|
|
And in this case, the minimum code to get that test to pass will be creating an empty file
|
||
|
|
named hpr-info.py.
|
||
|
|
So we'll just run the touch command and touch hpr-undescore-info.py.
|
||
|
|
Now we run Pytest again, it doesn't really output anything, it doesn't say that we passed
|
||
|
|
or failed, but it doesn't error.
|
||
|
|
So that's good, so we haven't really written a test, but we kind of did the minimum test
|
||
|
|
by importing a file that doesn't exist.
|
||
|
|
We ran the test, it failed, we created an empty file, ran the test again, it passed.
|
||
|
|
Congratulations, we're doing TDD.
|
||
|
|
What we've done is we have confirmed that Pytest, whenever we run Pytest without any file
|
||
|
|
pass or anything, we've confirmed it found the Pytest, underscore hpr-undescore-info.py.
|
||
|
|
Confirm that Pytest found that test file, and we've also confirmed that the test file
|
||
|
|
is trying to import hpr-info.py since that error went away when we created the file.
|
||
|
|
We know that it's looking in the correct place to do the testing.
|
||
|
|
So we'll start writing some tests, some actual software tests.
|
||
|
|
Before I do that, we're going to use the assert statement quite a bit, so just as a quick
|
||
|
|
review for the Python Asserts, you say assert, and then you give it a comparison.
|
||
|
|
So for example, you could say assert1 equals 1, obviously that's true, so that'll pass.
|
||
|
|
Something else you can do is you can say assert a specific function, and you can say assert
|
||
|
|
a specific function equals and then desired output.
|
||
|
|
So what that means is later when we want to test our hpr-info app, we can assert the output
|
||
|
|
of let's say we have a function that prints the hostname, we can say assert the hostname
|
||
|
|
function equals whatever we want it to be.
|
||
|
|
We can also use assert without a comparison operator, and what that does is just verifies
|
||
|
|
that something exists.
|
||
|
|
So if we say assert1, it would equal true, so that assert would pass.
|
||
|
|
The way we can use that is if we have, if we want to verify that something exists, then
|
||
|
|
we can say assert, let's say there's a dictionary that has some files, and we want to assert
|
||
|
|
that it has a specific key in there, we can just say assert dictionary.key, just to make
|
||
|
|
sure that that exists.
|
||
|
|
Okay, so remember one of the first things we actually need to do to figure out, you know,
|
||
|
|
to get the host information for the latest hpr episode, we need to know hpr-feed.
|
||
|
|
So let's let's try to test for that.
|
||
|
|
Let's try to test that we have at least some value for the hpr-feed.
|
||
|
|
So very simple test, we'll just say asserthprinfo.hpr-feed, and this is assuming that we're going
|
||
|
|
to add a variable later called hpr-feed.
|
||
|
|
So when you, the way you reference something that exists in a different module and Python,
|
||
|
|
as you put the module name, and then a dot, and then the object you're referencing.
|
||
|
|
So remember we imported hpr underscore info, that's going to be the script we're writing.
|
||
|
|
So we just write a simple test, asserthprinfo.hpr-feed, then we can run by test.
|
||
|
|
That test will fail because that the object, hprinfo.hpr-feed, doesn't exist yet.
|
||
|
|
So what we can do is we can add in our hpr-feed info, we can add at the very top of the file,
|
||
|
|
we can just put hpr-feed equals, and then whatever the value for the hpr-feed is.
|
||
|
|
And again, we can run by test, this time the test will pass because the hpr-feed variable
|
||
|
|
exists.
|
||
|
|
And we could, if we wanted to, we could even update the test to say, assert that the hprinfo.hpr-feed
|
||
|
|
equals whatever value we want it to be, let's see, if we wanted to be the ockfeed and p3-feed
|
||
|
|
or whatever, we could do that too.
|
||
|
|
So we could, we could test that it exists or we could test that it is a specific value.
|
||
|
|
And then now, in the, when we run by test, it will detect that we've written the test,
|
||
|
|
now we have, and so when we run by test, we can start running it with the dash v flag that
|
||
|
|
usually gives you a little more information.
|
||
|
|
But this time it will say, tell us that it found one test and that test passed.
|
||
|
|
Okay, so now let's plan a function that pulls the hpr-feed and returns some feed data.
|
||
|
|
And we want to test that we get a http200 whenever we pull the hpr-feed.
|
||
|
|
So in our test file, we can write a simple Python function named test underscore, get underscore,
|
||
|
|
show underscore data.
|
||
|
|
So we're testing if we can get the show data.
|
||
|
|
We can define show data equals, and then we're going to say show data equals hpr dot get show data.
|
||
|
|
So what that, what that means is it's going to try to execute a function in hpr underscore info
|
||
|
|
called get show data.
|
||
|
|
And so after we say show data equals, underneath that, we'll have an assert statement that says
|
||
|
|
show data dot status equals 200.
|
||
|
|
So what this means is we're going to run that function and then we're going to expect some
|
||
|
|
information back. Part of that information back is going to be the status.
|
||
|
|
And then when we're asserting that the status is going to equal 200.
|
||
|
|
So now we have another test.
|
||
|
|
We don't have any code yet, but that's okay.
|
||
|
|
We run pi test.
|
||
|
|
We get one test failed and one test passed.
|
||
|
|
So our first test is still passing, that's good, but our new test is failing.
|
||
|
|
That's also good because that's what we expect.
|
||
|
|
You know, part of doing test driven development this way is not just writing and checking
|
||
|
|
passing tests. It's also making sure that you have failing tests.
|
||
|
|
When you expect a test to fail, I'm going to imagine if we had written this now to test
|
||
|
|
that the result is a 200 and we ran the test, expecting it to fail and it passed.
|
||
|
|
We would know that there was something really, really off.
|
||
|
|
So part of the reason you run the test first is to ensure it fails.
|
||
|
|
That way you know that you're actually testing the right thing.
|
||
|
|
Okay, so now that we've written a test to
|
||
|
|
verify the status of the 200, we've verified that test fails.
|
||
|
|
Now we can write, again, the smallest amount of code to get that to pass.
|
||
|
|
So in this episode, I'm not going to review the specific Python code.
|
||
|
|
If you want to see the Python script that we're testing against,
|
||
|
|
kind of been its final result, as well as the test files and its final result.
|
||
|
|
All this stuff will be checked in to my GitLab and links in the show notes.
|
||
|
|
But for now, let's just say through the magic of me doing this two weeks ago, there's a function
|
||
|
|
that pulls down the HPR on feed, gets all the data, and returns it status, and the status is
|
||
|
|
200. So now we can run the test again, and through the magic of, again,
|
||
|
|
having done this a couple of weeks ago, passes. Now we have two tests passing.
|
||
|
|
Like I said, if you can always just run PyTest without any arguments,
|
||
|
|
but if you want some extra information, you can run PyTest with a dash V flag,
|
||
|
|
and it will show you each individual test, pass or failed, and it'll also give you a percentage.
|
||
|
|
Right now we have two tests, so when one passes, that's 50% when the second one passes,
|
||
|
|
that's 100%. But you can imagine that would be more useful if you have a lot of tests.
|
||
|
|
Okay, so now that we have the feed, and we can assert that we're getting a good result
|
||
|
|
from pulling the feed, let's see if we can figure out if we can get the first episode.
|
||
|
|
So in the HPR underscore info script, we're going to use something called feed parser,
|
||
|
|
Python library called feed parser, and what it does is it returns data as Python dictionaries.
|
||
|
|
So what we want to do is we want to test that we can create a function called get latest entry,
|
||
|
|
and that it will return a dictionary that has a title and a published date.
|
||
|
|
So we'll write a new function in our test file called test underscore get latest entry,
|
||
|
|
and then we'll say latest entry equals HPR info dot get latest entry. So this is assuming
|
||
|
|
that we're going to write a function called get latest entry, and then we're going to assume
|
||
|
|
that that function returns a dictionary. So whenever we run latest entry equals and then the
|
||
|
|
function latest entry will now be a Python dictionary. So then we can assert that there is a
|
||
|
|
dictionary key called title and a dictionary key called published. We don't have to test the
|
||
|
|
values because depending on what day the values will be different, but we want to at least test that
|
||
|
|
the values exist. And we can do that with just kind of a bear assert statement.
|
||
|
|
So we run the test, the test fails. We write the code that actually gets the latest information
|
||
|
|
returns a dictionary. Then we run python-v again. We can see now we have three passing tests.
|
||
|
|
So we'll write one more function to do some testing. Let's say in our actual script,
|
||
|
|
we verify that we can get the information from the HPR feed. So let's kind of write a final
|
||
|
|
test that includes looking for all the information that we actually want to print out. So again,
|
||
|
|
we'll assume that the HPR info script has a function called getentry data.
|
||
|
|
And that function called getentry data, we can pass it the information that we got when we pull
|
||
|
|
the HPR feed. So then what we'll do is we'll say entry data equals, we'll write a test in our
|
||
|
|
test script, entry data equals, and then we'll call the entry data function and pass it the latest
|
||
|
|
entry data. And again, we're expecting a dictionary back that contains, well it contains what
|
||
|
|
we want to test for. So we want to test that there's a title, a host, a published date, and a file.
|
||
|
|
And remember the file is going to be like the path to the archive.org download.
|
||
|
|
And then as a reminder, you know, all the code, if you want to see the code about how this
|
||
|
|
actually works, I'll have a few code snippets in the show notes, but full Python scripts as well
|
||
|
|
as all the tests, all the links to that in my gala project. One little bit of code I will talk
|
||
|
|
about real quick, is at the bottom of the HPR info script, HPR underscore info script.
|
||
|
|
There's a section that starts with if underscore name equals underscore main. And that's real
|
||
|
|
common in Python to have something like that at the bottom of the script. And the reason you do
|
||
|
|
that is you want to differentiate between when you import the script and when you run the script.
|
||
|
|
So what I mean by that is when you import a script from, you know, like from your test file and
|
||
|
|
your importing HPR underscore info, if there's something like print statements or something like
|
||
|
|
that, those things will probably execute just from importing. So what you want to do is you want
|
||
|
|
to separate what you want to happen when you call the script directly versus when you just import it
|
||
|
|
from something else. So it's a little bit of magic. There's reasons, you know,
|
||
|
|
behind all this, which I understand, but I'm not necessarily going to get into here. But
|
||
|
|
you know, the way to do that, the way to separate the code that you want to execute when you call
|
||
|
|
the script, is you put it underneath this if name equals main function. So you'll see that in there.
|
||
|
|
If you look at the code, well, it'll be in the show notes, or if you look at the code on get lab.
|
||
|
|
But the reason it's like that is, you know, that's, this is the bit that calls when you actually
|
||
|
|
execute the script. And what it's going to do, it's going to go through and run code that was in
|
||
|
|
the functions that we've been testing with TDD. So a lot of times that'll happen with TDD. You'll
|
||
|
|
write a whole bunch of functions you want to test. And then finally at the end, the last bit of
|
||
|
|
code you write is tying all these functions you've tested, tying all that together to get the
|
||
|
|
output that you want it. Okay, that's it. It's all I got. Sort of in summary, you know, test TDD,
|
||
|
|
test driven development. It's a programming method where you write tests prior to writing the code.
|
||
|
|
You know, primarily because I like it because it makes me write smaller, easier to understand,
|
||
|
|
more modular code. I'll have links in the show notes. I don't remember to add those. They're
|
||
|
|
not in here. They're not in my notes yet. So as long as I can remember to add them, I'll have
|
||
|
|
links in the show notes. And then just sort of as an exercise, if this is something you're interested in,
|
||
|
|
you can just sort of part two do this. If you want to go do some homework, you can take the script,
|
||
|
|
take the test. And then see if you can figure out how to write a test to verify that whenever
|
||
|
|
the script returns a date, that it's a possible date, that it's a date that makes sense. You can
|
||
|
|
test that it's actually a weekday. You know, HBR only comes out Monday through Friday, so you can test.
|
||
|
|
It wouldn't be hard if you know a little bit of Python to write a test to verify that the date
|
||
|
|
that's being returned is actually a weekday. Or you could test, maybe you could figure out how to
|
||
|
|
pull in the correspondence page from HBR, do a little bit of web parsing, and then you can verify
|
||
|
|
that the script that we wrote when it returns a host, that that host is can verify that it's
|
||
|
|
on the correspondence page. There's all kinds of other things you can do. Again, this was just an
|
||
|
|
example. I made up, I thought it would be a good way to kind of demo test driven development.
|
||
|
|
That's it, that's all I got. I'll see you guys next time.
|
||
|
|
You have been listening to Hacker Public Radio at Hacker Public Radio does work. Today's show was
|
||
|
|
contributed by a HBR listener like yourself. If you ever thought of recording broadcast,
|
||
|
|
you click on our contribute link to find out how easy it really is. Hosting for HBR has been
|
||
|
|
kindly provided by an onsthost.com, the Internet Archive and our Sync.net. On the Satellite
|
||
|
|
Stated, today's show is released under Creative Commons, Attribution 4.0 International License.
|