How to Create Tests
00:00 Although Martin already solved the challenge, he didn’t want to stop there. He wants to create some tests for the function that he created to verify that the function really works as expected. You will see how he creates unittests, and he will explain why it’s useful to have tests after all.
So basically all requirements are met, but now you also mentioned that when you said that you would create a function that it’s easily testable, you already created a file that’s called
Why don’t you call it a day? Like, why do you think it makes sense to create another file and write some tests? Yeah, so tests are helpful to make sure that your code doesn’t break if there’s any sort of refactoring. In this small example, you might notice it if, like, someone goes in here of your contributors and puts a
3 instead of the
2, for example, right?
01:03 But if there’s a larger codebase, and you make some change in one function, it might affect something in a different place in your codebase. And those errors are kind of hard to spot. So one of the advantages of writing your code in functions is that you get pieces of code where you exactly know what the input is going to be and what you want to get back from it, and that gives you a chance to also test them.
01:33 Okay, yeah, that makes sense. There is sometimes this approach that you write tests first, and then you write your code. So that’s more like a test-driven development, but for this case, it also makes sense to just write your function, like how you think it works, and then test it basically afterwards.
01:53 So it can be either you write test beforehand or afterhand. It’s okay either way, right? Yeah. Yeah. And that can be a mix. I think we can do both. I can write a test for what I have now, and then maybe we can develop an additional small feature in here writing it in the TDD way, test-driven development, where you first write the test and then yeah, let’s do that. Okay. Before we move on, let me quickly recap the two approaches that Martin and I are talking about.
02:24 The one is testing the code after you wrote it, and the other one is creating tests and then writing the code to match the tests. This is commonly known as tests-first or test-driven development—in short, TDD.
And so in here, now I’m going to write my test function. So this is something you should always do when you test a file: you name it
Test and then whatever your file is called, your module is called, right?
And you inherit from this
TestCase. This just gives you access to useful things that are defined inside of the
unittest.TestCase. Yeah, so now with
TestDouble, you have a subclass of
unittest.TestCase. So this is a test case, and like you just said, there is this convention of having it named with
Test upfront, similar like with the filename, that if it’s a small test suite, like right now that you just have a file with
test_ and then the file that you want to test with laying next to the other file.
So this is something you— And the naming is actually important so that
unittest can discover the test that you wrote. Okay. So it’s not just, like, that we as programmers agreed that it’s cool to call them like this, but actually it’s something that’s— I guess someone agreed it’s cool, and then the—but the
unittest module uses the naming also to discover the test. Okay. What do you mean by discover the test?
For example, if I run
python3 -m unittest in this
interviews/ folder, then
unittest calls something called
discover, where it looks for files that are named
test_, and then inside of those files, looks for classes called
Test, and then inside of those classes, looks for functions that are again called
And now, around this
discover, you see that it found the test automatically. I didn’t need to pass it which file is
unittest supposed to check out, but it automatically looked into the ones that’s called
test_, found the
Test class, and then found an actual test function, like a method of this
Test class that it ran.
Can you quickly show the command that you used to run the tests in the terminal? So that’s
python3 -m unittest and then
discover, but discover is implicit. You don’t actually need to write this.
So it’s just what
unittest does. Okay, so this can come in really handy because you could potentially just run this test file on its own, but then maybe you can—I could do this,
test_double and then it would execute it too.
I believe that this shouldn’t work now, if I run
unittest. Yeah, so it doesn’t find the test file. It ran zero tests in zero seconds because now I didn’t follow the naming convention that
unittest uses for automatic test discovery.
You could still execute it by passing the filename directly. And yeah,
unittest knows what to do with it, runs the test. But yeah, I can’t just use the automatic test discovery. Does that make sense?
07:07 That totally makes makes sense, and if you think about, like, currently we would be at the start of a bigger project, and currently our files are not that many, they’re all in the same folder, but there can be quite many, many files at some point.
So it makes sense to not have to write them out specifically. And also, if you think about sharing this code with others, if you stick to this convention of calling files like this, others know what they can expect inside of this file, if a file is called
test_something. Yeah, exactly.
07:53 So I expect you to write something in line 9. Yeah. And just as a quick note, because this sounds like a slightly absurd name, but when you’re testing, you actually want to be as descriptive as possible for your names of the methods that you write. So you really try to describe it, and the reason for that is too that in the output, if your tests fail, it gives you the name of the function, and then you already get some sort of idea of what went wrong in your codebase.
.assertEqual() does is that it checks that the two arguments are the same, right? And the first argument I’m producing here by calling the function that I imported from my module, the one that I want to test, and then the second argument that I provide is going to be the expected output.
And you can see here, for example, this is a failing test. It says in
test_double_characters_doubles_characters(), I have a descriptive function, and it tells me I got an
AssertionError, it found like this is what my function returned.
10:05 And this is what my expected output is, which is not actually my expected output because I forgot to double the letter that was already doubled. Okay, that’s a, that’s a really interesting point because tests don’t save you from making errors on your own. Like, if you write a test statement the wrong way, then your tests might fail although your code works correctly.
And I think that’s also also an interesting point about tests, what you just mentioned before about naming the test method, in this case, that also repeating yourself inside of tests is accepted. It’s not like in normal code where you’re seeing like, okay, I have two lines that look basically the same, I could put this in a
for loop. For tests, it might make sense to be more verbose and just repeat yourself.
12:21 I secretly change this function because I think it’s cooler if there are three characters repeated. Right, and I come back to this code after a weekend. I go ahead and see there were some changes, see if everything still works and then I can get an error.
So it tells me this is what it got back, and this is what was expected. And this
double_characters(), they’re going to—all right, so with the
T, I can see that there’s three of them, but I’m expecting there to be only two, right? Yeah. So I can go back to the function, check it out,
double_characters(), see, oh, there’s a
3 in here and say,
13:03 That’s cool. Yeah. So, having tests with your code, even if it’s simple code kind of provides you a safety net when you come back to your code at some point. You can do this with comments and stuff like this, but with tests, you’re basically more safe. So if you say like, okay, today I will continue working on my little project that I do every other day, you can run your tests first.
13:27 And if the tests run fine, you can be sure that you can build up on your codebase that you already have, but if something goes wrong, like in this time, because somebody did something with your code while you were away from your keyboard, then the tests show you like, hey, it’s not the way like you expected it to be anymore. And very commonly, these types of tests are then integrated in some automated testing where every time someone commits to a repository, the tests first need to pass, and otherwise, they can’t even commit the changes.
14:06 Cool. Yeah. In its own, that’s really cool, and that’s a perfectly fine example. Next, I would like you to elaborate a little bit on what you mentioned before, when you said test-driven development. Okay.
14:23 With a test in place, Martin can be confident that his code works as expected. If he would ever want to refactor his code, or if somebody else would touch his code, he can check if nothing got broken by running his unittests.
unittest module is a built-in module in Python, so it’s ready for you to use. Martin showed a basic testing setup. He created a file with a
test prefix next to the file that he wants to test.
The methods that he created are very verbose in the naming on purpose because they will show in the terminal when running the tests. Again, the
test prefix here is important in the method names. With these naming patterns, Python’s
unittest will be able to discover a test and run them when you use the module with the command
python3 -m unittest.
In the next part, I will expand my challenge so the
double_characters() function throws an error hen you provide an empty string. Martin will use a test-driven development approach to implement this feature.
Become a Member to join the conversation.