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.
00:19
You solved the instructions, so you have a function. You showed me that it works with "Martin"
, it works with "Philipp"
. It works with a "Tree"
, it returns the string.
00:28
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 test_double
.
00:42
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:25 Then you can just have a test suite that runs every time you make some change, and you notice if something breaks or if everything still works as expected.
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.
02:40
We’ll get to TDD a bit later, but now he’ll create a test to verify that the double_characters()
function really works as expected. So he will create tests after he wrote the code.
02:54 Okay. So I jump over to my test file, and we’re not just going to print this out,
03:01
but I will use unittest
for this because it’s built into Python. For this, I just need to import the unittest
module. And then I’m also going to work with the function that I created.
03:13
So and then I make a class and call this one TestDouble
03:19
and then inherit from unittest.TestCase
.
03:24
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?
03:36
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.
04:07
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?
04:33
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 test_
.
04:57 Then it automatically executes these tests. We don’t have one here at the moment. That’s why it’s not running anything, but let’s give it a try. Okay. If I go ahead and call this
05:12
test_double_characters_doubles_characters()
,
05:20 and then, I’ll define it.
05:26
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.
05:48 And now you see that it ran one test in no time at all, and the test passed. This is when you see the dot, it means that the test passed successfully, which is—I didn’t write anything.
05:59
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.
06:13
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.
06:31
But on the automatic discovery, if I went to rename this, and I called it fest
,
06:40
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.
06:53
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.
07:21
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:43
All right. So now you’ve created the TestDouble
class. You created the method test_double_characters_doubles_characters()
with a pass
statement.
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.
08:21
So this is why this is so verbose. Yeah. Okay, so what I want to do in here is I want to check that the double_characters()
function from my double
module works as expected.
08:35
And for this can use self.assertEqual()
.
08:42
unittest
has a lot of these .assertSomething
—oops, I got the wrong one, I want .assertEqual()
. This takes two arguments.
08:50
So I want to pass in, first of all, a call to the function. I want to see if I call double_characters()
with some input,
09:01
stick with the "Tree"
, then I want to get as an output TTrreeee
. So I pass in first—
09:11
all that .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.
09:31 So now that’s already it, that would be a test. I can go in here and
09:41
And there we go, we get an AssertionError
because looks like I did something wrong. Yeah. I did do something wrong. So that’s good for tests. Exactly.
09:52
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.
10:28 So there was the human component of things still. Right.
10:34 Okay, so, and now I’ve added these extra two characters here. And when I go ahead and execute the tests again,
10:43
now you can see that the test passes. I get this little happy dot that tells me that everything went fine and unittest
ran one test and everything is okay. That’s awesome.
10:53
And so basically, if you wanted to try different words now, you would then create a for
loop inside of this test, or like, how would you go? You could just add more statements like this.
11:07 You could just add another one in here. That still counts as one test.
11:12 Now I’m not going do another double letter. I’ll mess up again.
11:21 Okay. Okay. And so this is still going be one test if I run this, and it just checks both of them. If there’s an error in any of them, then the test is going to fail.
11:31
Cool. And it also gives me this verbose output where it tells me this is what the first part of what running double_characters()
got, and this is what was expected. Cool. Yeah.
11:42
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:10
It’s okay in this case. Yeah. Okay. And now, let’s say you’re my collaborator, and you make this change over here to the double
function. Well, let me, because I am.
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.
12:38
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, Hi Philipp
.
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:00 Right. So it’s part of an automated pipeline that run all the time on your code.
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.
14:39
The 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.
14:53
Inside of it, he imports unittest
and the function he wants to test. And then he creates a subclass of TestCase
and implements his test methods.
15:04
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
.
15:29
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.