Harry J.W. Percival目前就职于PythonAnywhere,他在各种演讲、研讨会和开发者大会上积极推广测试驱动开发(TDD)。他在利物浦大学获得计算机科学硕士学位,在剑桥大学获得哲学硕士学位。Harry Percival著有《Python Web开发:测试驱动方法》一书,该书手把手教你从头开始开发一个真正的Web应用,并且展示使用Python完成测试驱动开发(TDD)的优势。你将学到如何在开发应用的每一个部分之前先编写和运行测试,然后再编写最少量的代码让测试通过——也就是应用TDD理念,写出简洁可用、赏心悦目的代码。

enter image description here

iTuring: Many agile coaches claim that training newbies to do TDD can be torturous and time-consuming, while there are no garanteed results. What approaches have you adoped in your book to avoid such situation?

That's a tough question! It is impossible to guarantee results when teaching a methodology like TDD, but I think the fact that there are so many TDD books and tutorials out there is proof that, once people have understood it, it changes the way they program, and they feel a lot happier about it, so they want to share it.

There are two ways I think my book is a little different from most. The first is its "conversational" style. Instead of trying to write an academic, lecture-style book, I wrote the book, imagining the reader sitting next to me, working together on a project. That's how I learned TDD myself, by pair-programming with experts. So, rather than trying to deliver a series of lessons or rules, we take a real, practical example, and I demonstrate how to use TDD to address the problem, and I also talk about what's going on in my head, why I make the decisions I make, why use this kind of test or that kind of test.

That's the second aspect that's different, which is that I use a very practical example -- a real project, building a real website, with real functionality. It's simple, but it feels realistic, unlike many tutorials that, for example, ask you to build a "roman numeral translator", or some other abstract example, which seems obvious when you read it but then is hard to apply in the real world.

iTuring: According to you, what are the most effective ways of learning TDD? Is the learning curve of TDD steep?

One way to put it is that TDD is easy in theory, harder in practice. It's easy to get the basic idea TDD with a simple example, like you might find in any online tutorial. But applying that to real-world projects can be tough. It really takes time to get a feeling for what is a good test and what is a bad test, what is useful and what is not productive. Unfortunately the only way to gain that knowledge is with time and experience, but I try to get people started along that road. In the book I tried very hard to keep the learning curve as gentle as possible -- I only introduce one concept as a time, and I tried to keep the early chapters nice and short, with each one introducing just once concept even if it means we learn a simplistic, unrealistic technique at first, just to understand the basic idea, and later on we learn the "real" way of doing it, which is slightly more complex. I think that's a better way to learn than just trying to jump to the perfect solution all in one go. But by the end of the book, the reader will be following on with exactly the kind of TDD techniques that my colleagues and I use every day.

Why do you choose Python as the programming language to approach TDD? Does it have some unique advantages that other languages don't possess? Python tends to be adopted as a teaching language because it's so simple -- it "stays out of the way", by which I mean that Python code is very easy to read, and it doesn't have a lot of boilerplate getting in the way; no "public static void main bla bla" keywords like you might have in java for example. Those kinds of things might be useful for big enterprise applications (although that's debatable!), but they just get in the way when you're trying to teach a programming methodology. So Python is simple, which is great for teaching, but it is also very flexible, which makes lots of TDD techniques easy as well (things like mocking and patching, which I talk about later in the book).

iTuring: For web development, what are the differences between Python Web and ASP.NETWeb? What are their pros and cons?

I'm afraid I don't know anything about ASP.NET, so I'm not really qualified to answer this question. I know that a lot of businesses use ASP.NETbecause it's part of a Microsoft stack that's very familiar, but I also know that a lot of silicon valley startups have used Python to great success (Youtube, Instagram, Reddit, and many more...)

What do you think about Flask? How do you choose between Django and Flask? Flask is simpler but Django has more built-in functionality (or "batteries included", we sometimes say). I've used Flask for small "microservice" applications, but for a larger application Django's features like authentication, the admin UI, and the wide variety of third party plugins and apps make it an appealing choice. But, like any "one size fits all" solution, it can be great, but occasionally the built-in django functionality isn't always exactly what you want. There are trade-offs, but in the end, it's not that important of a decision. You can succeed with either!

iTuring: The more complex a project is, the more tests it requires, sometimes tests take even longer time than developing, which is something a startup cannot afford. So do you have any suggestions for people who have to make tradeoff between testing and developing?

I want to turn this on its head a little. First off, I don't think it's the case that a more complex project means proportionally more time is spent on testing. For the sake of argument, let's say writing a piece of code with tests takes twice as long as writing it without tests (although some people will tell you that, once you get good, you can write it faster with tests than without). But, for the sake of argument, let's say it takes twice the time. I think if it takes twice as long to write code in a very small, simple project, it also takes twice as long in a very large, complex project -- that ratio doesn't change.

What does change is the benefits that tests bring. In a simple project, tests aren't actually all that valuable. If you have a very simple project, it's very easy and quick to test it manually. And if you want to make a major change to the code, maybe upgrading a core module, or refactoring some central algorithm, then you can do it, without tests, check everything quickly, and be fairly confident that everything is OK.

But in a large project it's a very different story. Imagine the largest project you've worked on, and now imagine upgrading one of the core components -- a framework that's used absolutely everywhere in the code, and you want to upgrade to a new version, with a new API and new behaviours. Without tests, that becomes an almost impossible task. Recently, at work, we upgraded our core version of django, and, thanks to tests, it only took us a couple of weeks. But by the end, we could be sure that everything worked, and when we deploy it to our customers, we can have confidence that they won't see any regressions. But without tests, we couldn't have done it. And I mean that literally, without tests, we would simply not have attempted it, it would have been way too risky. And then we would have been stuck on an old version of django, perhaps one that eventually would get so old it would be unsupported, and have all kinds of security problems...

So actually it's in complex projects, after time, that the real benefits of TDD come in. Without tests, you tend to start to get scared to make big changes to your system. You shy away from refactoring, because it's too risky. Technical debt builds up, making new changes becomes more and more difficult, and the engineers working on the system get more and more unhappy.

I talk about this, and my own experience with my first project, in the introduction to the book.

This is one of the real difficulties with testing as a practice - it's an investment. It costs extra time and effort now, and although it can lead to some immediate improvements in the code, the real benefits come in in the future. So it needs some level of forward thinking from the team and from management. But, although speed is critical to a startup, it will die just as fast later if it finds it has too much technical debt to continue innovating and responding to market demands.

iTuring: In TDD, does one have to write test case for each class and method? How to be thorough without spending too much time?

I talk about this a little in chapter 4 in the book. You have to find your own balance. Sometimes you can justify to yourself that, if a function is so small and trivial, maybe it doesn't need a test. But then the danger is: what happens over time? Maybe the simple function grows a tiny bit bigger, it has one more if statement. But it doesn't have tests, and adding new tests is extra effort, so maybe it's ok to just add new code without tests. And then the function grows a for loop, and little by little, it gets more and more complex, and more and more in need of tests, but because of the psychological barrier at each stage, it might never get them. So perhaps it's safer to just have the tests from the very beginning, because adding one new tests when there is already a test there is so much easier. And, after all, if it's such a simple function, then surely it will be very simple to write the first test?

We know that TDD is very effective in projects of small scale, but is TDD able to handle middle-scale or even large-scale projects? Are there anything to look out when you use TDD in large proejcts? As I say, I think some of the real benefits of TDD actually only come in once a project reaches a certain size and a certain age. But, yes, there are things to look out for. One problem is the speed of your test suite. It's easy to take this too far, and some people get obsessed with test speed, but it is important to keep an eye out for it. If you have tests split out into low-level "unit" tests and higher level "functional" tests, then you want to start worrying when your unit tests start to take more than a few minutes to run, or when your functional tests take several hours. Tests are all about trying to get feedback as quickly as possible, within reason. Sometimes you can get into a situation where the speed of your tests stops increasing proportionally to the size of your application, and instead starts increasing geometrically, as complexity increases. But there are ways of dealing with this. I talk about these tradeoffs in the later chapters of the book, particularly in the last chapter (Chapter 22).

iTuring: Legacy code is not always test-friendly, do you have any suggestion about doing TDD on legacy code?

I cannot claim to be an expert in this area, but there is an excellent book on the subject by Michael Feathers called "Working Effectively With Legacy Code". The general advice is: do not despair. It may seem like an impossible task to add tests to a massive existing codebase, but it can be done, gradually and incrementally, over time. Once you've made the decision to start adding tests, then you can, for example, do so whenever a new addition needs to be made to the code, or whenever a bug needs to be fixed. Over time, test coverage will improve, and you start to be able to refactor your application to make it more testable, and the process accelerates.

iTuring: Can front-end TDD be extended to back-end TDD? Are there any models in software engineering worth recommended?

I'm not sure I quite understand this question, but I will try to answer. In the book I talk about using different layers of tests to be able to test both the front-end (the rendering of HTML and the behaviourof javascript, as it appears in a real browser) and the back-end (the controllers and views that connect to the database and process data). By using a layer of "high-level", "real" tests, which I call functional tests, using Selenium, we can test both the front-end and the integration of the whole application. Lower-level "unit" test can be used to test the individual components of front-end javascript and back-end Python code. In the book, in Chapters 18 and 19, I talk about this a little, and models called "Double-Loop TDD" and "Outside-In TDD".

The other thing I should say is that my book isn't just about TDD. It's really about a whole way of doing software engineering in a structured, rigorous, incremental fashion. When I started out coding I was just "hacking", just figuring it out on my own, coding "cowboy-style", and taking shortcuts to make things work. That's fine at first but you soon get into trouble.

What I learned over the next few years, and what I try to communicate in the book, are the tools and techniques to do things in a more structured way. That means TDD, yes, but also means many other things: using a version control tool like git, and making numerous small, incremental commits. It may seem like a separate topic, but in fact it's closely linked to the test-driven way of working, which is all about small, incremental changes. I demonstrate some of the "devops" philosophy, of not just working and testing on your own machine, but also building a staging environment, and using your tests alongside automated deployment scripts to make sure that our code works in production, on a real server, on the internet, as well as on a local machine. I talk about integration with third party systems, and using a continuous integration server to run builds overnight, I talk a little bit about how TDD fits in with agile development methodologies like Extreme Programming (XP)... I really tried to convey everything I learned about the difference between "hacking around" and "real software development" into a book.


更多精彩,加入图灵访谈微信!