TDD is not about tests!
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Title of Series | ||
Part Number | 122 | |
Number of Parts | 173 | |
Author | ||
License | CC Attribution - NonCommercial - ShareAlike 3.0 Unported: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this | |
Identifiers | 10.5446/20110 (DOI) | |
Publisher | ||
Release Date | ||
Language | ||
Production Place | Bilbao, Euskadi, Spain |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
| |
Keywords |
EuroPython 2015122 / 173
5
6
7
9
21
27
30
32
36
37
41
43
44
45
47
51
52
54
55
58
63
66
67
68
69
72
74
75
77
79
82
89
92
93
96
97
98
99
101
104
108
111
112
119
121
122
123
131
134
137
138
139
150
160
165
167
173
00:00
SoftwareSoftware testingCodeMountain passDensity of statesDreiecksnetzTuring testRegulärer Ausdruck <Textverarbeitung>Computer architectureSoftware testingInheritance (object-oriented programming)Cycle (graph theory)CodePoint (geometry)BitFunctional (mathematics)Phase transitionLogicMaxima and minimaCode refactoringSoftware developerMultiplication signTheoryWave packetTest-driven developmentGroup actionTriangulation (psychology)MathematicsChemical equationDivisorCASE <Informatik>Order (biology)Message passingSquare numberConfidence intervalProcess (computing)Right angleCore dumpMechanism designPairwise comparisonMereologyTheory of relativityHypermediaSystem callAuthorizationTranslation (relic)Integrated development environmentMixed realityAngleBeat (acoustics)Degree (graph theory)Loop (music)NumberSet (mathematics)Computer animation
07:21
Density of statesSoftware testingCodeNP-hardSample (statistics)Real numberMetropolitan area networkDigital filterAbstract syntax treeAsynchronous Transfer ModePort scannerEmailPoint (geometry)TheoryCodeSoftware testingObject (grammar)BitData structureFunctional (mathematics)Group actionVarianceView (database)Level (video gaming)Serial portCalculationMultiplication signMachine codeMereologyOrder (biology)Goodness of fit1 (number)Human migrationPercolation theoryTask (computing)Revision controlPhysical lawWebsiteDistanceExecution unitWritingDirectory serviceRight angleTraffic reportingReverse engineeringType theoryRepository (publishing)Projective planeoutputPhysical systemINTEGRALComplete metric spaceService (economics)Term (mathematics)Network topologyCore dumpBranch (computer science)Proof theoryCartesian coordinate systemFlow separationEndliche ModelltheorieConfidence intervalProduct (business)Set (mathematics)Client (computing)Category of beingComputer architectureUnit testingVideo gameControl flowMathematicsBoundary value problemLine (geometry)CASE <Informatik>Food energyStudent's t-testTwitterSoftware bugInterface (computing)Sound effectTest-driven developmentNumberDatabasePerfect groupReal numberQuicksortInsertion lossSolid geometrySynchronizationMessage passingAsynchronous Transfer ModeLoop (music)Template (C++)Code refactoringComputer animation
Transcript: English(auto-generated)
00:00
is the driven, that's the important bit because that's what actually changes when you actually do it like that. So it's a software development process that is based on a very very short cycle that you repeat very often. You start by writing a test and then you make it run
00:22
and it fails and this is called the red phase and then you write the minimum amount of code to make the test pass and then you write the test and the test passes and this is called the green phase and then because you have written the minimum amount of code, maybe also a rough code, you want to refactor. You can refactor both the code and the tests,
00:46
I suggest not at the same time because it's not wise and when you start with it, it's really useful if you take very small steps, so you write a little bit of test code
01:01
and then a little bit of code to make the test pass, you refactor, you go back, you add another little bit, you make that pass, you refactor and so on and so forth. And the more you do this, the more in your brain, basically you learn how to work with this kind of back and forth between tests and code, which means that at some point you're going to be able to take
01:24
longer steps and then you go like, I'm taking longer steps and you take them really long and at some point you're in trouble and so you go back to short steps, you fix whatever you have to fix and then you try and increase a little bit more until you get some comfortable size for you to work with. But where do we start? It all starts with
01:47
the business requirement, so that's very important, you have to understand the business requirement and then when you are clear on the business requirement, you enter the TDD cycle, you start writing a test, you make it pass, you refactor, test, pass, refactor and so on and so forth
02:02
until that business requirement is fulfilled. And then you get to the next business requirement. The frog at this point thought the training was completed, but the masters disagreed and they kept giving examples. They said, this is just the mechanics, if you know how to use
02:21
a steering wheel, the gear switch, accelerators and brain, but you've never driven a car, would you call yourself a pilot? Of course not. And I've talked to some people, some coders, they say, I know all about TDD, I read an article, but have you ever tried it? Nah, it's not really for me. Well, if you don't try it, you don't know, so you need to try.
02:42
What changes in your brain when you use TDD? Well, without TDD, you go from the business requirement to the code. And when you think about the code, you have to take care of two things, what the code needs to do, we need to deliver this functionality and how it needs to do that.
03:02
We need to cycle using a for loop, we need to call this function, so it's two very different things, the what and the how, and we take care of both of them at the same time, so our brain is fully concentrating on two things. On the other hand, when you use TDD, you basically mostly take care of the what when you're writing your tests, because the tests
03:25
are testing what should happen in the code. And then you take care of the how when you're writing the code itself, so you have your brain concentrating on each of those at two different times, which means it's like having two brains. It's much more, you become much
03:48
more powerful, your code changes immediately. There are some common aspects to working with TDD, the case principle, keep it simple stupid, which means basically by forcing yourself
04:03
to have a test that represents something that your code needs to do, and to again force yourself to write the minimum amount of code in order to deliver that, the code basically remains simple. You cannot go and take care of super architectural things or whatever.
04:22
The code automatically stays simple, which is simple is really, really important. And then the YAGNI principle, you ain't gonna need it. If you're focusing on understanding the business requirement, writing a test and making it pass, it's not likely that you're gonna over engineer your code, because you don't have the freedom of a code that says,
04:43
I need to do this. And then while you're coding, there's nothing telling you what that code should do. It's just in your mind and you go like, oh, I'm also gonna add this because maybe tomorrow I need it, which is something that with TDD you don't do. Three strikes and refactor. I've taken this off the test driven development with
05:01
Python from Percival, which is a very nice book. I just read it a few months ago. It basically says when you're in the refactor phase and you find that you've got one functionality and basically the same functionality, just wait for the third occurrence of something really similar, because if you group these and factor
05:23
out the mixin or whatever too soon, when the third occurrence comes out, you may realize it's not as easy to make that work with the mixin that you just done. So you may also have to do a lot of refactoring work. On the other hand, going for four or five, that's not good. So three strikes and
05:42
refactoring is a nice balance to achieve. You can do all the architecture design when you refactor the code. And the beauty of it is that because you have the tests, you can refactor with confidence. And then you do triangulation. Triangulation was puzzling for the frog. Let's see an
06:04
example. Let's say that you're writing a test and you're just making sure that your square function that takes minus two is producing number four. If you think about it, at this point, all we have in the code base is to
06:21
fulfill that requirement, which basically says I just want to know that your function returns four. So you can do this, you can cheat. It's actually suggested by TDD authors. And it's called fake it till you make it, but of course we don't want to have that code in our code base because that's
06:43
not correct. So we do triangulation, which means we pinpoint to the same function from two different angles, which would cause us to have to fake it in two different ways, which is not compatible. At that point, we need to write the real logic. So you write the actual logic when you
07:01
have triangulation like that. And this is very nice, not just because it's like a theoretical thing, but it's because you get something done, you get a test that basically is there. And then when you triangulate, you already have a test there. It builds on your test
07:20
base. So the main benefits are you can factor with confidence because you have a set of tests that when you touch the code and you change something, like in the first example, we had the boundary that was jiggling a bit, you have a test that fails. Readability, because it's
07:45
much easier to see code that was designed with tests first, because basically, even though it's like a given, you take care of the design part when you're writing your tests, because when you're
08:01
writing something, unless you're writing integration tests, but if you're unit testing, you have to test a unit of code. So you have to think, how do I structure this code? You can't just write something. You have to give it a thought, which means you're thinking twice about the code when you test and when you
08:20
actually write it, which means it comes out much better, much more readable. It's more loose coupled. It's easier to test and maintain. It's easier to test, of course, because it's coming from the test and it's easier to maintain because it's more readable structure. And when you do tests first, you also have a better understanding of the business requirement, because
08:43
in order to start writing your tests, then you have to have very, very clear in your mind what is the business requirement that will drive the design of those tests. If you're not clear, you will find yourself blocked at the test level, which basically will prompt you to go back and try
09:04
and understand better the business requirements. And then by testing in small units, it will be much easier to debug. And also, you will have the perks of having the tests act as documentation, because by having small tests, easier to
09:21
read, you just go through it very, very quickly and you say, oh, okay, my code does this, which is sometimes even easier than reading an English sentence that describes that functionality, because English can be misleading, especially if I write it. So having a test, which is Python code, that you know what it does, it's very useful. And higher speed. It
09:44
takes less to write tests and code than to write code and then having to debug it. I can tell you this by my personal experience. I was working for a company called TBG. We were competing with other companies to get the
10:01
preview access to Twitter's advertisement API, and we had to deliver a proof of concept in about six weeks. We succeeded. It was a monolithic Django application, and the order from above was no tests. We didn't have the time to do them. So no tests. We do the coding, we do
10:25
overtime, we go on Saturdays and stuff, and the last two weeks were spent just to fix two bugs that they drove us crazy. And it was just one small Django website. But it
10:40
grows so complicated, touched in such a short amount of time by six, seven people, that basically we were going crazy in debugging, because we were changing something here, it was breaking something there, you go fix it, you break something there, and then you've got a ripple effect that flows through your code. On the other hand, had
11:01
we done tests first, that time that we spent debugging, we wouldn't have needed that. The main shortcomings of this technique is that the whole company needs to believe in it, otherwise you're going to fight all the time with your boss, which is something that me and some of
11:23
my colleagues know very well. We need to write the test. There is no time to do the test. But then we'll have to debug. It will be a problem that we will have later, okay? So we're going to write just the code. And then, oh, it doesn't work, it's your fault! So you don't want to, you have to really, really
11:40
convince everybody that TDD is the way to go. And because it's really hard to see what happens in the long term, all we, myself included, most of the time we just see what happens tomorrow, we need to deliver tomorrow, we need to make the client happy tomorrow, and we tend to forget about the long term. Blind spots, if you mislead, sorry, misread, or if you
12:04
don't understand, or if they are incomplete, the business requirements, it means this will reflect in the type of tests that you write, and this will also reflect in the code. And without the business requirement part, take a look at the tests, perfect.
12:21
Take a look at the code, makes the test pass, we're done. So the code and the tests, they would share the same blind spot, the same thing that you missed from the business requirement. So in this case, it can be harder to spot something. Pairing, for example, helps on this, because you
12:42
have to discuss what you're doing, which means you bring up a discussion about the business requirement, and then you realize, oh, I understand it this way, my colleague understands it that way, you go ask whoever it is that you have to ask for clarification, and then this does not happen. And also badly written tests are hard to maintain.
13:03
For example, tests with a lot of mocks, they're very hard to maintain, because when you change a mock, you're changing an object that basically is just a puppy, you can do whatever you want with it, and you're not really sure if you're breaking something, because you just change a mock, and you make it do something else, and then you make
13:21
your code pass, but if that mock is representing a real object, as it should, and that object is no longer in sync with the mock, then you have bugs. So when you have mocks, you have to take extra care in order to refactor your tests and your code. So these are the shortcomings.
13:43
I'm gonna give you a few real life examples, because one of the things that I'm told many times is like, yeah, okay, it's all good and nice when it's from a theoretical point of view, but then in real life, what happens? So for example, you are hired by a company,
14:03
you tell them, I really wanna do tests, they say, good, they don't say, you are the first, and they have a legacy code that is not tested, so you have to cope with that, you have to change something, so what do you do? You read the code, you understand how it works,
14:21
and you write a test for it, and this is wrong, because if you read the code, understand how it works, and write a test for it, what happens? You're inverting the cycle, basically, you're going from the code to the tests. What we wanna do is to go from the business requirement to the tests to the code, so a better way
14:43
of approaching this would be, read the code, try and reverse engineer what are the business requirements that were behind that code, and then write the tests for it, concentrating on the what part, not on the how. If you do this, it's very likely that your tests
15:02
will concentrate on the how part. Changing a horribly long view, let's say we have a Django view, and we need to insert pagination, filtering and sorting in it, we do a bunch of things, we get the data, the data is from a search,
15:24
so it can be empty, could be 10 things, could be a million things, then we do another bunch of things, and then we render a template with some context, so we need to add pagination, filtering and sorting, and it's not possible to do it at the API level, because it's too complicated and it's not tested, so something that I did
15:45
after discussing it with my colleagues was, let's write filter data, sort data, and paginate data, insert that into the function, into the view, without changing anything that was happening before we get the data or after we got the data,
16:03
but those three functions, let's write them TDD, so at least we're changing the code, yes, but that bit of code will be rock solid, and this is a very good way to go about it, because in the end, the function was not caring before about what data was coming from the search, so it's not gonna care now if you have paginated,
16:22
sorted it, or filtered it in any way. Introducing a new functionality into an existing code, which is not test, so that's a nasty piece of code that does a lot of things, like this function, Uncle Bob would cry if he saw it, so how do we change, because of course,
16:40
you will not have the test, the time to go through every if clause to check if that for loop is correct or not, so what do you do? Well, one possible solution would be to come up with one test for the new functionality that you're trying to insert, and then you change the function, which was not tested before, it's not tested now,
17:02
but at least that new functionality is done. The next time you go back to this function to refactor, what happens? You have one test, you come up with another test, and then you go back again, and at some point, you will have a bit of tests behind this function, which means either the function was well written,
17:22
and therefore you just keep adding tests, or at some point you realize the function has a bug, because you start having tests for it. So the frog was in zen mode after all this, he went back to the princess and passed the exam, they married, and when the minister said,
17:41
you can kiss the bride, nothing changed. It was just a talking frog after all. What's the moral of this story? It means I should have tested first. And so should you, thank you very much.
18:13
Great, we have a few minutes for questions, do we have any?
18:22
So first of all, thanks for the talk, it was really enjoyable, and my question is, how do you put your test in directories structure, because I have some tests of all kinds, but I have trouble to checking if the code
18:43
I intend to make already have some. So how do you put them? So for example, when you start the Django project, you've got tests in the application folder, first thing that I do is to remove that, and I reproduce the code structure in a test folder, preventing everything with test underscore.
19:02
This is two main advantages. The tests basically are easier to find, because if you know the tree of your files, you just go to the same tree with the tests, and also when you deploy, you just delete that folder. Because sometimes when you deploy, and occasionally someone run those tests on production,
19:21
you get your production database that goes, and that's not nice. So I just reproduced basically the tree of my code. Probably there's also other approaches, but this works, it's good, they're out of the way. And then for functional tests or integration tests, you may want to have a separate folder as well,
19:42
or maybe even a separate repository. For example, if you are doing integration tests, it's likely that they won't be testing what's just in one repository, maybe it's more repository, more services or more application, so you want to have a whole project dedicated to your tests.
20:03
We have a question here somewhere. Hi, and a great talk. As humans, we are, I believe sometimes there can be mistakes on the test data, either by typos or by hand calculations.
20:23
In your experience, how often do those mistakes happen, and how do you cope with them? They happen more when you have to deliver by yesterday. When you have the luxury of delivering by tomorrow, they happen less.
20:42
What you want to do is to take good care of your features and especially when you change the tests or you have migrations, take good care of those, because basically those are the things that you test against. So they need some love. Me, for example, I tend to write unit tests,
21:01
more like an interface style, which means I have some inputs and I check on the outputs, rather than mocking everything out. I tend to mock the least possible, because it's dangerous to mock. And what was the second part of your questions? I mean, in your experience, how often, for example, in the examples
21:20
you printed before, one of the numbers could be wrong by a typo and you don't notice that while you are writing tests or the assertion. How often does that happen and how do you detect it? The way we detect it is to have pull requests. So we use a branching system. We have story branches and then we have a staging branch
21:40
and before master branch, there's at least two pull requests for other people other than the guy who wrote the code to take a look at that code and we read it. So most of the times the stuff is detected on either of these two passes. That's very helpful. Thank you. My pleasure.
22:07
I was recently involved in discussions about TDD and some guys were defending that if you focus too much on tests using TDD and writing the tests first and et cetera, you may leave for later,
22:22
thinking about the architecture of the code, the design of the code, how different parts of the code is coupled between them, the performance, et cetera. What would you say to them? I would say that TDD is not a type of methodology that fix everything and solves every problem.
22:44
You still have to take care of the architecture, take care of the design and you can do it in the refactor phase. What people who do this and find themselves neglecting the rest of the code is because probably they believe TDD can do too much. TDD gives you a very good way of writing your code
23:01
and a very good solid test code base that basically shields you a bit more when you refactor. But it's not that you can go refactoring like a monk because you've got tests. You still have to give it your best shot and take good care of the code you write. But with TDD, you've got this extra thing,
23:22
extra like a guardrail that basically keeps you on the right path. It's just something that you have more but will not solve all the other things that you have to do. Great, do we have one last question? No, all right, now I already won.
23:42
Thank you.