What Test-Driven Development Is
00:00 So far, Martin created the test after he wrote the code. You heard Martin and me talk about test-driven development now and then, and now you finally see test-driven development in action.
00:11 Martin will create the test first and then the code afterward.
00:17 So Martin, you were talking about test-driven development, so now you were creating tests after you created your function, and you were talking about TDD, which is short for test-driven development.
00:29 How would it be different than what you did just right now?
00:34 If I were to approach this in a test-driven development way, I wouldn’t start off by writing the function that actually does the work that we want to be done—in this case, doubling the characters—but I’d start off with a test file. I would probably make a plain function
00:50 without anything happening in there that just gives my intentions of what do I want it to do. And then I would start off by creating the tests first. So I would write this test and give my intentions that says, if I want to execute this function, I want it to double each character in the word that I’m passing to the function as an argument, right? This is what I’m defining in here, more or less.
01:13 So you use the test to give your intentions of what is your code supposed to do, and then you go back here and you would actually write this code. Yeah.
01:23 Maybe you want to put a TDD approach to this, because you don’t have to start with TDD. You can actually, like, implement it at some point. Let me expand my instructions for you. All right.
01:35
I want that your function throws an error if you add in an empty string. So you expect some strings for double_characters()
, but if the string is empty, I want you to throw an error, and I want you to program it in an TDD way, a test-driven development way. Nice. All right, so, I’ll go ahead and create a new function in here.
02:00 I’ll give my intentions,
02:04
test_double_characters_fails_on_empty_string()
.
02:10 That’s again, it’s a very verbose name, which exactly says what you intend to to test.
02:17
And then I’m going to say self.assertRaises
. So this is one of those asserts that are in there, assert
methods that come with the unittest TestCase
.
02:29
I want to make sure that it raises, let’s say a ValueError
,
02:34
if I use double_characters()
. And you define this a little differently: you pass in first the error that you want to happen, then the function object that you want to call.
02:46 And after that you pass in the arguments that you’d be passing to the function. So in this case, it would be an empty string. Okay? Yeah, so—This is essentially me calling the function like this. Yeah. Right.
03:00
Okay. But since there is some errors happening, the syntax is a bit different for this .assertRaises()
method. This is the way that this method is built, yeah. Okay.
03:13 Now I can go in here and run this test case,
03:18
and you would see that it ran two tests, and we have a failure in there. Okay. First of all, you can see that the first test passed. This is still a little dot. This is test_double_characters_doubles_characters()
that went well. But then I got a big F
here, which is that it failed, and now you can see the advantage of having this verbose naming, test_double_characters_fails_on_empty_string()
. So I know already what went wrong here,
03:43
and I get more information that says AssertionError: ValueError not
raised by double_characters
function, and that’s to be expected. Okay?
03:53 I mean, so far your function works with an empty string, but we expect that an error should occur when an empty string is passed in. And I can try this out too, right?
04:02
I can go ahead and say from double import
double_characters()
. double_characters()
, And if I pass in an empty string, I just get an empty string as an output.
04:16 You get an empty string returns, which might in some cases be what you want, but in our case, that’s not acceptable to me. I want an error. Unacceptable. Unacceptable.
04:29
So now when I go back over to the actual module where we’re writing double_characters()
, my next step is to implement these intentions that I wrote in the test and put it into this double_characters()
function.
04:41
Now I know that I want to raise a ValueError
, and I know when I want to do it, so I’m just going to say, if not word:
and
04:51
then I want to raise ValueError
and I could give it also some string as information—maybe "Input can't be empty"
, for example.
05:06
So now I have this check at the beginning that takes a look at the passed argument and raises a ValueError
if it’s empty. So if not word
, you could also write if word == ""
.
05:20
Maybe that’s even— Is there an advantage to one or the other? I don’t really think there’s an advantage to either. I think people prefer to write Python like this because it implicitly checks that word
is a falsy value, an empty string.
05:35
So if not word
is going evaluate to True
if the word is empty. That’s a double negation, right? I don’t know, it might actually be easier to read if we do this. So for readability, maybe that’s easier. Okay. So let’s leave it like that.
05:50 The functionality is going to be the same. And now, after implementing these intentions in code, I would go back and double-check that my tests are now passing.
06:03
And now I get the second dot. Cool. Which means I can be confident that the double_characters()
function raises a ValueError
if there’s an empty string as an input.
06:13 Let’s give it a go just to round it off.
06:19
So now you’re starting an IPython session, and you’re running double_characters()
with an empty string. Correct. And it worked, and now you can see that we get the ValueError
and even with the message that I passed in here before. Cool. Okay, so that means test-driven development means that you are first writing a test.
06:40 You are expecting the test to fail because you haven’t created the code yet to match the test. So then you’re running your test, the test fails, and then you create the code to make this test pass.
06:55 Exactly. And you basically repeat this until you are at this point where you want to end up. Yep. And this kind of differentiates to the approach you had at the beginning of our session today, where you created the code and then afterward created a test to match the code and to check if it works like you wanted to, and maybe you would spot some issues with it, and then you kind of like stumble into test-driven development, but this was kind of like the other way around.
07:27 So both ways are fine because I think the advantage is that you have tests in the end, and that’s always a plus, but which way you are doing it also depends on how you prefer coding.
07:40 Or is there a way, Martin, before we end the session, where you say like, this way is, in your opinion, better than the other or you like it more than the other one?
07:50 I don’t think I have, like, a strong preference either way. I think sometimes it’s fun to start with the code, and sometimes it can be helpful to start with the test, just like it happened in this session.
08:00 I might want to get the functionality, the first functionality down, and then maybe I can think of edge cases of when might it fail. Like I didn’t even necessarily know that it would not fail if it’s a more complex function.
08:13 And I don’t exactly know what happens when I try to double a string. Right. Then I wouldn’t necessarily know that it fails, but here I’m just saying I want it to fail, and then I can go back and implement that. Yeah.
08:28 So you’re happy with the code I wrote here? Does it answer your task? I am very happy.
08:36
And this is the final code that Martin came up with. In the last bit, he used test-driven development to create more tests to verify that the double_characters()
function works as expected.
08:50
So this time he wanted to make sure that the double_characters()
function throws a ValueError
When an empty string is provided. To do this, he followed the test-driven development approach.
09:05 So he first wrote the test for the feature. Then he expected the test to fail. Then he implemented the feature until the test case passes.
09:15 If you do test-driven development, you basically repeat this approach until your app contains all the features you need. Just as the name suggests, the tests drive your development.
09:28 In the next part, Martin and I will recap the session. We’ll share our impressions about how things went and how the session maybe compared to a coding interview and what I particularly liked about the way that he was approaching this coding challenge.
Become a Member to join the conversation.