Learning Test-Driven Development A Polyglot Guide to Writing Uncluttered Code (Saleem Siddiqui) (Z-Library) (1)

Author: Saleem Siddiqui

科学

Your code is a testament to your skills as a developer. No matter what language you use, code should be clean, elegant, and uncluttered. By using test-driven development (TDD), you'll write code that's easy to understand, retains its elegance, and works for months, even years, to come. With this indispensable guide, you'll learn how to use TDD with three different languages: Go, JavaScript, and Python. Author Saleem Siddiqui shows you how to tackle domain complexity using a unit test-driven approach. TDD partitions requirements into small, implementable features, enabling you to solve problems irrespective of the languages and frameworks you use. With Learning Test-Driven Development at your side, you'll learn how to incorporate TDD into your regular coding practice. This book helps you: • Use TDD's divide-and-conquer approach to tame domain complexity • Understand how TDD works across languages, testing frameworks, and domain concepts • Learn how TDD enables continuous integration • Support refactoring and redesign with TDD • Learn how to write a simple and effective unit test harness in JavaScript • Set up a continuous integration environment with the unit tests produced during TDD • Write clean, uncluttered code using TDD in Go, JavaScript, and Python

📄 File Format: PDF
💾 File Size: 9.7 MB
21
Views
0
Downloads
0.00
Total Donations

📄 Text Preview (First 20 pages)

ℹ️

Registered users can read the full content for free

Register as a Gaohf Library member to read the complete e-book online for free and enjoy a better reading experience.

📄 Page 1
Learning Test-Driven Development A Polyglot Guide to Writing Uncluttered Code Saleem Siddiqui Foreword by Dr. Konstantin Läufer
📄 Page 2
(This page has no text content)
📄 Page 3
Saleem Siddiqui Learning Test-Driven Development A Polyglot Guide to Writing Uncluttered Code Boston Farnham Sebastopol TokyoBeijing
📄 Page 4
978-1-098-10647-8 [LSI] Learning Test-Driven Development by Saleem Siddiqui Copyright © 2022 Saleem Siddiqui. All rights reserved. Printed in the United States of America. Published by O’Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA 95472. O’Reilly books may be purchased for educational, business, or sales promotional use. Online editions are also available for most titles (http://oreilly.com). For more information, contact our corporate/institutional sales department: 800-998-9938 or corporate@oreilly.com. Acquisitions Editor: Melissa Duffield Development Editor: Michele Cronin Production Editor: Kristen Brown Copyeditor: Piper Editorial Consultants, LLC Proofreader: Paula L. Fleming Indexer: Ellen Troutman-Zaig Interior Designer: David Futato Cover Designer: Karen Montgomery Illustrator: Kate Dullea October 2021: First Edition Revision History for the Early Release 2021-10-12: First Release See http://oreilly.com/catalog/errata.csp?isbn=9781098106478 for release details. The O’Reilly logo is a registered trademark of O’Reilly Media, Inc. Learning Test-Driven Development, the cover image, and related trade dress are trademarks of O’Reilly Media, Inc. The views expressed in this work are those of the author, and do not represent the publisher’s views. While the publisher and the author have used good faith efforts to ensure that the information and instructions contained in this work are accurate, the publisher and the author disclaim all responsibility for errors or omissions, including without limitation responsibility for damages resulting from the use of or reliance on this work. Use of the information and instructions contained in this work is at your own risk. If any code samples or other technology this work contains or describes is subject to open source licenses or the intellectual property rights of others, it is your responsibility to ensure that your use thereof complies with such licenses and/or rights.
📄 Page 5
For Ammi, Apa, Janelle, and Safa. Without your love and support, neither this book nor its author would be complete.
📄 Page 6
(This page has no text content)
📄 Page 7
Table of Contents Foreword. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi Preface. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xiii Chapter 0: Introduction and Setup. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 Part I. Getting Started 1. The Money Problem. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Red-Green-Refactor: The Building Blocks of TDD 11 What’s the Problem? 12 Our First Failing Test 13 Go 14 JavaScript 15 Python 16 Going for Green 17 Go 18 JavaScript 19 Python 20 Cleaning Up 21 Go 22 JavaScript 23 Python 23 Committing Our Changes 23 Where We Are 25 Go 25 v
📄 Page 8
JavaScript 26 Python 26 2. Multicurrency Money. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Enter the Euro 29 Go 30 JavaScript 30 Python 31 Keeping Code DRY 31 Go 32 JavaScript 32 Python 33 Didn’t We Just Say “Don’t Repeat Yourself ”?! 33 Divide and Conquer 34 Go 34 JavaScript 36 Python 37 Cleaning Up 38 Go 38 JavaScript 39 Python 39 Committing Our Changes 40 Where We Are 41 3. Portfolio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Designing Our Next Test 43 Go 45 JavaScript 47 Python 49 Committing Our Changes 51 Where We Are 52 Part II. Modularization 4. Separation of Concerns. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Test and Production Code 55 Unidirectional Dependency 56 Dependency Injection 57 Packaging and Deployment 57 Modularization 58 vi | Table of Contents
📄 Page 9
Removing Redundancy 59 Where We Are 60 5. Packages and Modules in Go. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Separating Our Code into Packages 63 Go Modules 64 Creating a Package 66 Encapsulation 68 Removing Redundancy in Tests 69 Committing Our Changes 70 Where We Are 70 6. Modules in JavaScript. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 Separating Our Code into Modules 73 A Segue into JavaScript Modules 75 CommonJS 75 Asynchronous Module Definition (AMD) 76 Universal Module Definition (UMD) 77 ESModules 78 Improving Our Tests 79 Removing Redundancy in Tests 80 Adding a Test Class and Test Methods 80 Discovering and Running Tests Automatically 83 Produce Output When Tests Run Successfully 85 Run All Tests Even When an Earlier Test Assertion Fails 86 Committing Our Changes 87 Where We Are 87 7. Modules in Python. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Separating Our Code into Modules 89 Removing Redundancy in Tests 91 Committing Our Changes 91 Where We Are 92 Part III. Features and Redesign 8. Evaluating a Portfolio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Mixing Money 95 Go 96 JavaScript 98 Table of Contents | vii
📄 Page 10
Python 100 Committing Our Changes 103 Where We Are 103 9. Currencies, Currencies, Everywhere. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105 Making a Hash(map) of Things 105 Go 107 JavaScript 109 Python 111 Committing Our Changes 112 Where We Are 112 10. Error Handling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 115 Error Wish List 115 Go 116 JavaScript 121 Python 124 Committing Our Changes 127 Where We Are 128 11. Banking on Redesign. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129 Dependency Injection 130 Putting It All Together 131 Go 132 JavaScript 140 Python 147 Committing Our Changes 151 Where We Are 151 Part IV. Finishing Up 12. Test Order. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 Changing Exchange Rates 156 Go 156 JavaScript 159 Python 162 Committing Our Changes 164 Where We Are 164 viii | Table of Contents
📄 Page 11
13. Continuous Integration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 Core Concepts 168 Version Control 170 Build Server and Agent 171 Artifact Repository 171 Deployment Environment 172 Putting It All Together 173 Create Your GitHub Account 173 Verify Your GitHub Account 174 Push Code Repository to GitHub 174 Prepare for CI Build Scripts 178 Go 182 JavaScript 183 Python 184 Committing Our Changes 185 Where We Are 191 14. Retrospective. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Profile 194 Cyclomatic Complexity 195 Coupling 195 Succinctness 196 Purpose 198 Cohesion 198 Completeness 198 Process 199 Putting It All Together 200 Go 200 JavaScript 202 Python 208 Isn’t TDD Dead? 213 Where We Are 215 A. Development Environment Setup. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 B. A Brief History of the Three Languages. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227 C. Acknowledgments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 Index. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Table of Contents | ix
📄 Page 12
(This page has no text content)
📄 Page 13
Foreword Throughout my 30-year career as a computer science and software engineering edu‐ cator, especially since my brief stint in industry in 2001, few other techniques have shaped and pervaded my teaching (and research) as much as automated unit testing, the general approach that test-driven development (TDD) operationalizes into a spe‐ cific, yet widely applicable technique. I still remember catching on to TDD in a concrete sense, almost as a side effect, after adopting Martin Fowler’s 2003 text, UML Distilled (3rd edition), as a UML reference for my object-oriented development course. There, Martin discusses three key practi‐ ces that are usually found in successful iterative development processes: automated regression tests, refactoring, and continuous integration. This concise description strongly resonated with me, and I’ve always enjoyed convincing my students to have more fun by writing additional code to test the rest of their code and receiving instant feedback in the form of colorful test results. My other aha moment came almost a decade later, around 2012, when I started to listen to some Software Engineering Radio podcasts about software architecture. I was reading up on some of the references mentioned in the podcasts and came across a brief subsection entitled “Serendipitous Architecture” in “Uncle Bob” Robert C. Mar‐ tin’s book Agile Software Development: Principles, Patterns, and Practices, which dis‐ cussed how focusing on making one’s code testable almost automatically leads to good, maintainable architecture. Taken together, these two points highlight the way automated testing ties together process and architecture, as well as functional and nonfunctional requirements: by giving us more confidence in the extent to which our code satisfies the functional requirements, testability arguably becomes the most important nonfunctional requirement. This summer, almost another decade later, Saleem Siddiqui contacted me regarding his book. Incidentally, next year will be the 25th anniversary of Saleem having taken three graduate-level courses with me! It has been highly rewarding to see him become xi
📄 Page 14
a successful technology professional—a ThoughtWorker like Martin Fowler—and author. I felt honored that he asked me to write this foreword for his book and was eager to learn more about his perspective on TDD. What excites me most about Saleem’s book is that it engages the reader in the TDD process in a hands-on yet methodical way, using a highly familiar running example from everyday life. The red-green-refactor cycle sets the tone for the process regard‐ less of programming language. The successive features from the financial currency domain are concrete and easy to relate to but lead the reader through progressively more complex challenges, thereby building confidence, exposing nuanced tradeoffs, and awakening curiosity to explore further. The final code review along the three dimensions of profile, purpose, and process integrates the insights gathered along the way. By using three highly visible languages with rather complementary designs—of which JavaScript and Python already occupy top positions in the market and Go is quickly on the rise—Saleem makes a strong case for the broad applicability of the TDD approach. In addition, he provides the reader with additional touchpoints and aware‐ ness of the relationship between language design and the “three Ps” just mentioned. My great hope is for Saleem’s book to have a multiplier effect by resonating with new generations of software developers drawn to impactful languages, such as Go, Java‐ Script, and Python, and pulling them onto the virtuous path of test-driven develop‐ ment. To borrow the words of the great jazz saxophonist Cannonball Adderley when describing hipness to his live audience in New York, it’s not a state of mind, it’s a fact of life. — Konstantin Läufer Professor of Computer Science, Loyola University Chicago Chicago, Illinois, September 2021 xii | Foreword
📄 Page 15
Preface Test-driven development is a way of managing fear during programming. —Kent Beck We are so ineffably lucky! We’ve had test-driven development for years. Several decades have passed since the developers who wrote the code for the Mercury Space Program practiced Punch Card TDD (test-driven development). XUnit libra‐ ries that facilitate the adoption of test-driven development date back to the turn of the century. In fact, Kent Beck, who wrote Test-Driven Development: By Example (Addison-Wesley Professional, 2002) and developed the JUnit framework, refers to himself as having “rediscovered” (and not invented) the practice of TDD. That state‐ ment is evidence of his humility, yet it is also the truth. TDD is as old as software development itself. Then why is it that test-driven development is still far from the standard way to write code? Why is it often the first practice that gets sacrificed when there is schedule pressure, or when IT budgets need to be trimmed, or (my personal favorite) when there is a desire to “increase the velocity of the software-delivery team”? All these rea‐ sons are proffered despite the ready availability of empirical and experimental evi‐ dence that TDD reduces defect count, creates simpler design, and improves developers’ confidence in their own code. Why is TDD adopted grudgingly and abandoned readily? The following arguments, heard often from those who are reluctant to practice it, may explain why: I don’t know where and how to start. Perhaps the most common reason is lack of awareness and exposure. Like any other skill, writing code in a test-driven style is something that needs to be learned. Many developers either haven’t had the external inducement (time, resources, guidance, encouragement) or internal motivation (overcoming one’s own reluctance and fear) to learn this skill. xiii
📄 Page 16
TDD works in toy programs or during coding interviews, but not when writing “real- world” code. This is untrue yet understandable. Most test-driven development tutorials and books—including this one—are constrained to pick relatively simple examples from an obvious domain. It’s difficult to write a TDD article or book with actual code from a piece of software plucked from a commercially deployed application (say, from a financial institution, a healthcare management system, or a self- driving automobile). For one thing, much of such real-world code is proprietary and is not open source. For another, it’s the job of the author to show code from a domain that has the widest appeal to the largest audience. It would be illogical, bordering on obscurantism, to show TDD in the context of a highly specialized domain. Doing so would require, before anything else, a lengthy explanation of the arcane jargon and cant of that domain. That would defeat the very purpose of the author: making TDD understandable, approachable, even lovable. These obstacles to using real-world code in TDD literature notwithstanding, developers regularly write production software using test-driven development. Perhaps the best and most convincing example is the suite of unit tests for the JUnit framework itself. The Linux Kernel—possibly the most strenuously used piece of software in the world—is being improved with unit tests. Writing tests after the fact is sufficient; TDD is too restrictive and/or pedantic. This is more refreshing to hear than the occasional rant that “unit testing is over‐ rated”! Writing tests after writing production code is an improvement over writ‐ ing no tests at all. Anything that raises the developers’ confidence in their code, reduces accidental complexity, and provides authentic documentation is a good thing. However, writing unit tests before writing the production code provides a forcing function against creating arbitrary complexity. TDD guides us to simpler design because it provides these two practical rules as guardrails: 1. Only write production code to fix a failing test. 2. Refactor energetically when, and only when, tests are green. Does test-driven development guarantee that all the code we ever write will automati‐ cally and inevitably be the simplest code that works? No, it does not. No practice, rule, book, or manifesto can do that. It’s up to the people who bring these practices to life to ensure that simplicity is achieved and retained. xiv | Preface
📄 Page 17
1 This definition of simplicity is enshrined in one of the 12 principles of the Agile Manifesto. This book’s content explains and instructs how test-driven development works in three different programming languages. Its purpose is to instill in developers the habit and self-belief to use TDD as a regular practice. That purpose may be ambitious, but I’m hopeful it isn’t elusive. What Is Test-Driven Development? Test-driven development is a technique for designing and structuring code that encour‐ ages simplicity and increases one’s confidence in code, even as its size increases. Let’s take a look at the various parts of this definition. A Technique Test-driven development is a technique. It’s true that this technique is borne of a set of beliefs about code, namely: • That simplicity—the art of maximizing the amount of work not done, is essential1 • That obviousness and clarity are more virtuous than cleverness • That writing uncluttered code is a key component of being successful Despite being rooted in these beliefs, as a practical matter, TDD is a technique. Like riding a bike, kneading dough, or solving differential equations, it’s a skill that no one is born with and that everyone has to learn. Other than this section, this book does not dwell on the belief system behind test- driven development. It’s assumed that you either subscribe to it already or that you’re willing to give TDD a try as a new (or forgotten) skill. The mechanics of that technique—writing a failing unit test first, then briskly writing just enough code to make it pass, and then taking the time to clean up—occupy the bulk of this book. There will be ample opportunity to try this technique for yourself. In the final analysis, it is more satisfying to learn a skill and imbue oneself with the beliefs that support it—just like riding a bike is more enjoyable if you remind yourself that it’s good for your health and the environment! Preface | xv
📄 Page 18
Designing and Structuring Code Notice that TDD is not fundamentally about testing code. It is true that we use unit tests to drive the code, but the purpose of TDD is to improve the design and structure of the code. This focus is vital. If TDD were only about testing, we couldn’t really mount an effec‐ tive case for writing tests before rather than after the business code is written. It’s the goal of designing better software that spurs us on; the tests are simply a vehicle for this progress. The unit tests that we end up with via TDD are an added bonus; the primary benefit is the simplicity of design we get. How do we achieve this simplicity? It is through the mechanism of red-green-refactor, which is described in detail at the beginning of Chapter 1. A Bias Toward Simplicity Simplicity isn’t a mere esoteric notion. In software, we can measure it. Fewer lines of code per feature, lower cyclomatic complexity, fewer side effects, smaller runtime or memory requirements—any subset of these (or other) requirements can be taken as an objective measure of simplicity. Test-driven development, by forcing us to craft “the simplest thing that works” (i.e., that which gets all tests to pass), constantly nudges us toward these metrics of sim‐ plicity. We aren’t allowed to add superfluous code “in case we need it” or because “we can see it coming.” We must first write a failing test to justify writing such code. The act of writing the test first acts as a forcing function—compelling us to deal with arbi‐ trary complexity early. If the feature we’re about to develop is ill-defined, or our understanding of it flawed, we’ll find it hard to write a good test up front. This will force us to address these issues before we write a line of production code. This is the virtue of TDD: by exercising the discipline of driving our code through tests, we weed out arbitrary complexity at every juncture. This virtue isn’t mystical: using test-driven development won’t cut your development time, the lines of code, or defect count by half. What it will allow you to do is to arrest the temptation to introduce artificial and contrived complexity. The resultant code— driven by the discipline of writing failing tests first—will emerge as the most straight‐ forward way to gets the job done, i.e., the simplest code that meets the needs of the tests. Increased Confidence Code should inspire confidence, especially code we have authored ourselves. This confidence, while itself a nebulous feeling, is grounded in an expectation of predicta‐ bility. We are confident in things whose behavior we can presage. If the corner coffee xvi | Preface
📄 Page 19
shop undercharges me one day and overcharges me by the same amount the next day, I’m likely to lose confidence in the staff even though I break even over the two days. It’s human nature that we value regularity and predictability even more than net value. The world’s luckiest gambler, who may have just won 10 times in a row at a roulette table, wouldn’t say that they “trust” or have “confidence” in the wheel. Our affinity for predictability survives even dumb luck. Test-driven development increases our confidence in our code because each new test flexes the system in new and previously untested ways—literally! Over time, the suite of tests we create guards us against regression failures. This steadily increasing battery of tests is the reason that as the size of the code grows, so does its quality and our confidence in it. Who Is This Book For? This is a book for developers—people who write software. There are many professional titles that go with this vocation: “software engineer,” “application architect,” “devops engineer,” “test automation engineer,” “programmer,” “hacker,” “code whisperer,” and countless others. Titles may be impressive or humble, trendy or solemn, traditional or modern. However, the one thing that’s held in com‐ mon by the people professing these titles is this: they spend at least a part of their week—if not each day—in front of a computer, reading and/or writing source code. I have chosen the term developers to represent this community of which I’m both a humble and grateful member. Writing code is one of the most liberating and egalitarian activities one may imagine. In theory, all one needs by way of physical prowess is the possession of a brain. Age, gender, sex, nationality, national origin—none of these should be a barrier. Having physical disabilities shouldn’t be a barrier. However, it would be naive to assume that reality is as neat or fair as that. Access to computing resources isn’t equitable. A certain level of wealth, freedom from want, and security are necessary. Access is thwarted even further by badly written software, badly designed hardware, and myriad other usability limitations that prevent all peo‐ ple from learning to program based solely on their interest and effort. I have tried to make this book accessible to as many people as possible. In particular, I’ve tried to make it approachable to people with physical disabilities. The images have alt-text to facilitate e-reading. The code is available via GitHub. And the prose is straightforward. Preface | xvii
📄 Page 20
In terms of experience, this book is intended both for people who are still learning how to program and for those who already know how to program. If you are ramping up on one (or more) of the three languages in this book, you are well within the tar‐ get audience. However, this book does not teach the basics of programming in any language, including Go, JavaScript, or Python. The ability to read and write code in at least one of the programming languages is a requirement. If you are absolutely new to pro‐ gramming, it’d be wise to solidify the foundations of writing code in one of the three languages before you proceed with this book. The sweet spot for this book spans developers who are beyond their early forays into programming, all the way to seasoned architects, as shown in Figure P-1. (Kent Beck is an outlier.) Figure P-1. This is a book for software developers Writing code can be, by turns, exhilarating and exasperating. However, even at its most frustrating, there should always be more than a glimmer of optimism and a bushel of confidence that we can make code do our bidding. With perseverance, you’ll find that your journey through this book is fruitful and that the joy of writing code in a test-driven manner is one you want to savor long after you’re done reading Chapter 14. What Are the Prerequisites for Reading This Book? By way of equipment and technical prowess, you should: • Have access to a computer with internet connectivity. • Be able to install and delete software on that computer. That is, your access on that computer should not be restricted; in most cases this would require having “Administrator” or “Superuser” access on that computer. • Be able to launch and use a shell program, a web browser, a text editor, and optionally an integrated development environment (IDE) on that computer. xviii | Preface
The above is a preview of the first 20 pages. Register to read the complete e-book.

💝 Support Author

0.00
Total Amount (¥)
0
Donation Count

Login to support the author

Login Now
Back to List