├── .gitignore ├── LICENSE ├── README.md ├── assets └── favicon.ico ├── credits.html ├── css ├── main.css ├── materialize.css └── materialize.min.css ├── font ├── material-design-icons │ ├── LICENSE.txt │ ├── Material-Design-Icons.eot │ ├── Material-Design-Icons.svg │ ├── Material-Design-Icons.ttf │ ├── Material-Design-Icons.woff │ └── Material-Design-Icons.woff2 └── roboto │ ├── Roboto-Bold.eot │ ├── Roboto-Bold.ttf │ ├── Roboto-Bold.woff │ ├── Roboto-Bold.woff2 │ ├── Roboto-Light.eot │ ├── Roboto-Light.ttf │ ├── Roboto-Light.woff │ ├── Roboto-Light.woff2 │ ├── Roboto-Medium.eot │ ├── Roboto-Medium.ttf │ ├── Roboto-Medium.woff │ ├── Roboto-Medium.woff2 │ ├── Roboto-Regular.eot │ ├── Roboto-Regular.ttf │ ├── Roboto-Regular.woff │ ├── Roboto-Regular.woff2 │ ├── Roboto-Thin.eot │ ├── Roboto-Thin.ttf │ ├── Roboto-Thin.woff │ └── Roboto-Thin.woff2 ├── fonts └── roboto │ ├── Roboto-Bold.eot │ ├── Roboto-Bold.ttf │ ├── Roboto-Bold.woff │ ├── Roboto-Bold.woff2 │ ├── Roboto-Light.eot │ ├── Roboto-Light.ttf │ ├── Roboto-Light.woff │ ├── Roboto-Light.woff2 │ ├── Roboto-Medium.eot │ ├── Roboto-Medium.ttf │ ├── Roboto-Medium.woff │ ├── Roboto-Medium.woff2 │ ├── Roboto-Regular.eot │ ├── Roboto-Regular.ttf │ ├── Roboto-Regular.woff │ ├── Roboto-Regular.woff2 │ ├── Roboto-Thin.eot │ ├── Roboto-Thin.ttf │ ├── Roboto-Thin.woff │ └── Roboto-Thin.woff2 ├── index.html ├── js ├── main.js ├── materialize.js └── materialize.min.js ├── lessons ├── __init__.py ├── lesson_0_hello_world │ ├── __init__.py │ └── index.html ├── lesson_1_greetings │ ├── __init__.py │ └── index.html ├── lesson_2_fizzbuzz │ ├── __init__.py │ └── index.html ├── lesson_3_calculator │ ├── __init__.py │ └── index.html ├── lesson_4_temperature │ ├── __init__.py │ └── index.html ├── lesson_5_pig_latin │ ├── __init__.py │ └── index.html ├── lesson_6_books │ ├── __init__.py │ └── index.html └── lesson_7_anagrams │ ├── __init__.py │ └── index.html ├── requirements.txt ├── tests ├── __init__.py ├── test_lesson_0_hello_world.py ├── test_lesson_1_greetings.py ├── test_lesson_2_fizzbuzz.py ├── test_lesson_3_calculator.py ├── test_lesson_4_temperature.py ├── test_lesson_5_pig_latin.py ├── test_lesson_6_books.py └── test_lesson_7_anagrams.py └── venv.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | *.pyc 3 | .idea 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jess Unrein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Test Driven Python 2 | 3 | ## Learn Python using Test Driven Development! 4 | 5 | This is a project designed for Python beginners who want to learn the ins and outs of Python. The project contains two main sections: `lessons` and `tests`. `lessons` contains a number of directories, inside each of which is a challenge that's described in an `index.html` file. Unit tests for the chanllege live in the `tests` directory. 6 | 7 | This project was inspired by the Learn Ruby course at [TestFirst.org](http://testfirst.org/). 8 | 9 | ## How to use this project 10 | 11 | ### I want to learn Python 12 | 13 | If you've never used Python before, open the `index.html` file in the top level folder of this project to get started. You can always open any file that ends in `.html` in your web browser. These will be a little bit easier to read than just looking at the raw files themselves. 14 | 15 | Each of the directories in `lessons` contains a single challenge. Create the appropriately named file inside that directory, (for lesson 0 your filename should be `hello_world.py`) to run the tests. The file **must** be in the right directory for the unit tests to evaluate whether it works or not. 16 | 17 | If you already have python, pip, and virtualenv installed on your computer, and you know how to set up your virtual environment then install `requirements.txt`. Then hop into `lessons/lesson_0_hello_world` and check out the `index.html` file in there for more instructions. 18 | 19 | ### I want to use this project to teach Python 20 | 21 | Awesome! Please let me know by starring the repo or dropping me a line on [twitter](https://twitter.com/JLUnrein). I would love to know how it works out for you. 22 | 23 | Have each of the students in your class or workshop clone the repo locally. I recommend walking through the first two lessons as a group or with a live code demo. This will get students used to the directory structure and to how to run unit tests. Have them start solo or paired work with one another on Lesson 2: FizzBuzz. 24 | 25 | ## How to contribute to this project 26 | 27 | To contribute to this project, fork the repo and open a pull request. 28 | 29 | ### Improve existing lesson content, language, or styling 30 | 31 | If you've used this project to learn Python or if you've used it in a class I would love your feedback! 32 | 33 | I'm sure there are sections of the written material for the challenges that could be clearer or more explicit. If you have ideas for how to improve unit tests for an existing lesson or on how lessons can be written more lclearly, please open an issue detailing your proposed improvements. 34 | 35 | If you would like to fix the issue yourself, please comment on the issue, fork the repo, and open a pull request. 36 | 37 | I'm also no prize when it comes to visual design, so if you think you can improve on it, **please do**. 38 | 39 | ### Adding new lessons 40 | 41 | If you would like to add a new lesson that you think would meaningfully contribute to the cirriculum, please fork the repo and open a pull request. Each new lesson will need the following: 42 | 43 | - A Python package in `lessons` that follows this convention: "lesson_{}_{}".format(lesson_number, challenge_name) 44 | - An `index.html` file detailing the lesson specs and a brief introduction to any new concepts introduced in the lesson (ex: new data structures, etc.) 45 | - A test file with specs that match the outline in your `index.html` file. 46 | -- Please include comments at the top with an abbreviated version of your instructions and notes from `/lessons/your_lesson/index.html` 47 | 48 | Note: The first few lessons are designed to build in complexity. If you're including a lesson that belongs in the more basic lessons of this project, please choose an appropriate lesson number. We'll figure out the details of how it should fit in during the pull request discussion. If it's a more complex challenge that includes several concepts covered in previous challenges, please choose the next available lesson number. 49 | -------------------------------------------------------------------------------- /assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/assets/favicon.ico -------------------------------------------------------------------------------- /credits.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 | 42 |
43 | 53 | 54 | 64 | 65 | 66 |

Credits

67 |

68 | Thank you to Alex Chaffee and TestFirst.org 69 | for inspiring this project. 70 |

71 | 72 |

73 | This project was created and is maintained by Jess Unrein. 74 |

75 | 76 |

77 | Design courtesy of the wonderful Stepahnie Marx. 78 |

79 | 80 |

81 | To contribute to this project or to identify an issue, please visit the github repo to file the issue or fork the repo 83 | and create a pull request. 84 |

85 |
86 | 87 | 88 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | nav .brand-logo { 2 | padding-left: 1em; 3 | } 4 | 5 | #dropdown1 a { 6 | color: #303f9f !important; 7 | } 8 | 9 | #dropdown1 { 10 | min-width: 300px; 11 | } 12 | 13 | .main { 14 | padding-left: 2em; 15 | } 16 | 17 | code { 18 | /*background-color: grey;*/ 19 | margin: 4em 2em; 20 | padding: .5em 1em; 21 | } 22 | 23 | code.inline { 24 | margin: 0; 25 | padding: 0 .33em 0 .25em; 26 | } 27 | 28 | ul.browser-default li { 29 | list-style-type: disc !important; 30 | display: list-item; 31 | margin-left: 1em; 32 | margin-bottom: .3em; 33 | } 34 | 35 | p { 36 | padding: 1em; 37 | } 38 | -------------------------------------------------------------------------------- /font/material-design-icons/LICENSE.txt: -------------------------------------------------------------------------------- 1 | https://github.com/google/material-design-icons/blob/master/LICENSE 2 | https://github.com/FezVrasta/bootstrap-material-design/blob/master/fonts/LICENSE.txt 3 | 4 | Attribution-ShareAlike 4.0 International 5 | 6 | ======================================================================= 7 | 8 | Creative Commons Corporation ("Creative Commons") is not a law firm and 9 | does not provide legal services or legal advice. Distribution of 10 | Creative Commons public licenses does not create a lawyer-client or 11 | other relationship. Creative Commons makes its licenses and related 12 | information available on an "as-is" basis. Creative Commons gives no 13 | warranties regarding its licenses, any material licensed under their 14 | terms and conditions, or any related information. Creative Commons 15 | disclaims all liability for damages resulting from their use to the 16 | fullest extent possible. 17 | 18 | Using Creative Commons Public Licenses 19 | 20 | Creative Commons public licenses provide a standard set of terms and 21 | conditions that creators and other rights holders may use to share 22 | original works of authorship and other material subject to copyright 23 | and certain other rights specified in the public license below. The 24 | following considerations are for informational purposes only, are not 25 | exhaustive, and do not form part of our licenses. 26 | 27 | Considerations for licensors: Our public licenses are 28 | intended for use by those authorized to give the public 29 | permission to use material in ways otherwise restricted by 30 | copyright and certain other rights. Our licenses are 31 | irrevocable. Licensors should read and understand the terms 32 | and conditions of the license they choose before applying it. 33 | Licensors should also secure all rights necessary before 34 | applying our licenses so that the public can reuse the 35 | material as expected. Licensors should clearly mark any 36 | material not subject to the license. This includes other CC- 37 | licensed material, or material used under an exception or 38 | limitation to copyright. More considerations for licensors: 39 | wiki.creativecommons.org/Considerations_for_licensors 40 | 41 | Considerations for the public: By using one of our public 42 | licenses, a licensor grants the public permission to use the 43 | licensed material under specified terms and conditions. If 44 | the licensor's permission is not necessary for any reason--for 45 | example, because of any applicable exception or limitation to 46 | copyright--then that use is not regulated by the license. Our 47 | licenses grant only permissions under copyright and certain 48 | other rights that a licensor has authority to grant. Use of 49 | the licensed material may still be restricted for other 50 | reasons, including because others have copyright or other 51 | rights in the material. A licensor may make special requests, 52 | such as asking that all changes be marked or described. 53 | Although not required by our licenses, you are encouraged to 54 | respect those requests where reasonable. More_considerations 55 | for the public: 56 | wiki.creativecommons.org/Considerations_for_licensees 57 | 58 | ======================================================================= 59 | 60 | Creative Commons Attribution-ShareAlike 4.0 International Public 61 | License 62 | 63 | By exercising the Licensed Rights (defined below), You accept and agree 64 | to be bound by the terms and conditions of this Creative Commons 65 | Attribution-ShareAlike 4.0 International Public License ("Public 66 | License"). To the extent this Public License may be interpreted as a 67 | contract, You are granted the Licensed Rights in consideration of Your 68 | acceptance of these terms and conditions, and the Licensor grants You 69 | such rights in consideration of benefits the Licensor receives from 70 | making the Licensed Material available under these terms and 71 | conditions. 72 | 73 | 74 | Section 1 -- Definitions. 75 | 76 | a. Adapted Material means material subject to Copyright and Similar 77 | Rights that is derived from or based upon the Licensed Material 78 | and in which the Licensed Material is translated, altered, 79 | arranged, transformed, or otherwise modified in a manner requiring 80 | permission under the Copyright and Similar Rights held by the 81 | Licensor. For purposes of this Public License, where the Licensed 82 | Material is a musical work, performance, or sound recording, 83 | Adapted Material is always produced where the Licensed Material is 84 | synched in timed relation with a moving image. 85 | 86 | b. Adapter's License means the license You apply to Your Copyright 87 | and Similar Rights in Your contributions to Adapted Material in 88 | accordance with the terms and conditions of this Public License. 89 | 90 | c. BY-SA Compatible License means a license listed at 91 | creativecommons.org/compatiblelicenses, approved by Creative 92 | Commons as essentially the equivalent of this Public License. 93 | 94 | d. Copyright and Similar Rights means copyright and/or similar rights 95 | closely related to copyright including, without limitation, 96 | performance, broadcast, sound recording, and Sui Generis Database 97 | Rights, without regard to how the rights are labeled or 98 | categorized. For purposes of this Public License, the rights 99 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 100 | Rights. 101 | 102 | e. Effective Technological Measures means those measures that, in the 103 | absence of proper authority, may not be circumvented under laws 104 | fulfilling obligations under Article 11 of the WIPO Copyright 105 | Treaty adopted on December 20, 1996, and/or similar international 106 | agreements. 107 | 108 | f. Exceptions and Limitations means fair use, fair dealing, and/or 109 | any other exception or limitation to Copyright and Similar Rights 110 | that applies to Your use of the Licensed Material. 111 | 112 | g. License Elements means the license attributes listed in the name 113 | of a Creative Commons Public License. The License Elements of this 114 | Public License are Attribution and ShareAlike. 115 | 116 | h. Licensed Material means the artistic or literary work, database, 117 | or other material to which the Licensor applied this Public 118 | License. 119 | 120 | i. Licensed Rights means the rights granted to You subject to the 121 | terms and conditions of this Public License, which are limited to 122 | all Copyright and Similar Rights that apply to Your use of the 123 | Licensed Material and that the Licensor has authority to license. 124 | 125 | j. Licensor means the individual(s) or entity(ies) granting rights 126 | under this Public License. 127 | 128 | k. Share means to provide material to the public by any means or 129 | process that requires permission under the Licensed Rights, such 130 | as reproduction, public display, public performance, distribution, 131 | dissemination, communication, or importation, and to make material 132 | available to the public including in ways that members of the 133 | public may access the material from a place and at a time 134 | individually chosen by them. 135 | 136 | l. Sui Generis Database Rights means rights other than copyright 137 | resulting from Directive 96/9/EC of the European Parliament and of 138 | the Council of 11 March 1996 on the legal protection of databases, 139 | as amended and/or succeeded, as well as other essentially 140 | equivalent rights anywhere in the world. 141 | 142 | m. You means the individual or entity exercising the Licensed Rights 143 | under this Public License. Your has a corresponding meaning. 144 | 145 | 146 | Section 2 -- Scope. 147 | 148 | a. License grant. 149 | 150 | 1. Subject to the terms and conditions of this Public License, 151 | the Licensor hereby grants You a worldwide, royalty-free, 152 | non-sublicensable, non-exclusive, irrevocable license to 153 | exercise the Licensed Rights in the Licensed Material to: 154 | 155 | a. reproduce and Share the Licensed Material, in whole or 156 | in part; and 157 | 158 | b. produce, reproduce, and Share Adapted Material. 159 | 160 | 2. Exceptions and Limitations. For the avoidance of doubt, where 161 | Exceptions and Limitations apply to Your use, this Public 162 | License does not apply, and You do not need to comply with 163 | its terms and conditions. 164 | 165 | 3. Term. The term of this Public License is specified in Section 166 | 6(a). 167 | 168 | 4. Media and formats; technical modifications allowed. The 169 | Licensor authorizes You to exercise the Licensed Rights in 170 | all media and formats whether now known or hereafter created, 171 | and to make technical modifications necessary to do so. The 172 | Licensor waives and/or agrees not to assert any right or 173 | authority to forbid You from making technical modifications 174 | necessary to exercise the Licensed Rights, including 175 | technical modifications necessary to circumvent Effective 176 | Technological Measures. For purposes of this Public License, 177 | simply making modifications authorized by this Section 2(a) 178 | (4) never produces Adapted Material. 179 | 180 | 5. Downstream recipients. 181 | 182 | a. Offer from the Licensor -- Licensed Material. Every 183 | recipient of the Licensed Material automatically 184 | receives an offer from the Licensor to exercise the 185 | Licensed Rights under the terms and conditions of this 186 | Public License. 187 | 188 | b. Additional offer from the Licensor -- Adapted Material. 189 | Every recipient of Adapted Material from You 190 | automatically receives an offer from the Licensor to 191 | exercise the Licensed Rights in the Adapted Material 192 | under the conditions of the Adapter's License You apply. 193 | 194 | c. No downstream restrictions. You may not offer or impose 195 | any additional or different terms or conditions on, or 196 | apply any Effective Technological Measures to, the 197 | Licensed Material if doing so restricts exercise of the 198 | Licensed Rights by any recipient of the Licensed 199 | Material. 200 | 201 | 6. No endorsement. Nothing in this Public License constitutes or 202 | may be construed as permission to assert or imply that You 203 | are, or that Your use of the Licensed Material is, connected 204 | with, or sponsored, endorsed, or granted official status by, 205 | the Licensor or others designated to receive attribution as 206 | provided in Section 3(a)(1)(A)(i). 207 | 208 | b. Other rights. 209 | 210 | 1. Moral rights, such as the right of integrity, are not 211 | licensed under this Public License, nor are publicity, 212 | privacy, and/or other similar personality rights; however, to 213 | the extent possible, the Licensor waives and/or agrees not to 214 | assert any such rights held by the Licensor to the limited 215 | extent necessary to allow You to exercise the Licensed 216 | Rights, but not otherwise. 217 | 218 | 2. Patent and trademark rights are not licensed under this 219 | Public License. 220 | 221 | 3. To the extent possible, the Licensor waives any right to 222 | collect royalties from You for the exercise of the Licensed 223 | Rights, whether directly or through a collecting society 224 | under any voluntary or waivable statutory or compulsory 225 | licensing scheme. In all other cases the Licensor expressly 226 | reserves any right to collect such royalties. 227 | 228 | 229 | Section 3 -- License Conditions. 230 | 231 | Your exercise of the Licensed Rights is expressly made subject to the 232 | following conditions. 233 | 234 | a. Attribution. 235 | 236 | 1. If You Share the Licensed Material (including in modified 237 | form), You must: 238 | 239 | a. retain the following if it is supplied by the Licensor 240 | with the Licensed Material: 241 | 242 | i. identification of the creator(s) of the Licensed 243 | Material and any others designated to receive 244 | attribution, in any reasonable manner requested by 245 | the Licensor (including by pseudonym if 246 | designated); 247 | 248 | ii. a copyright notice; 249 | 250 | iii. a notice that refers to this Public License; 251 | 252 | iv. a notice that refers to the disclaimer of 253 | warranties; 254 | 255 | v. a URI or hyperlink to the Licensed Material to the 256 | extent reasonably practicable; 257 | 258 | b. indicate if You modified the Licensed Material and 259 | retain an indication of any previous modifications; and 260 | 261 | c. indicate the Licensed Material is licensed under this 262 | Public License, and include the text of, or the URI or 263 | hyperlink to, this Public License. 264 | 265 | 2. You may satisfy the conditions in Section 3(a)(1) in any 266 | reasonable manner based on the medium, means, and context in 267 | which You Share the Licensed Material. For example, it may be 268 | reasonable to satisfy the conditions by providing a URI or 269 | hyperlink to a resource that includes the required 270 | information. 271 | 272 | 3. If requested by the Licensor, You must remove any of the 273 | information required by Section 3(a)(1)(A) to the extent 274 | reasonably practicable. 275 | 276 | b. ShareAlike. 277 | 278 | In addition to the conditions in Section 3(a), if You Share 279 | Adapted Material You produce, the following conditions also apply. 280 | 281 | 1. The Adapter's License You apply must be a Creative Commons 282 | license with the same License Elements, this version or 283 | later, or a BY-SA Compatible License. 284 | 285 | 2. You must include the text of, or the URI or hyperlink to, the 286 | Adapter's License You apply. You may satisfy this condition 287 | in any reasonable manner based on the medium, means, and 288 | context in which You Share Adapted Material. 289 | 290 | 3. You may not offer or impose any additional or different terms 291 | or conditions on, or apply any Effective Technological 292 | Measures to, Adapted Material that restrict exercise of the 293 | rights granted under the Adapter's License You apply. 294 | 295 | 296 | Section 4 -- Sui Generis Database Rights. 297 | 298 | Where the Licensed Rights include Sui Generis Database Rights that 299 | apply to Your use of the Licensed Material: 300 | 301 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 302 | to extract, reuse, reproduce, and Share all or a substantial 303 | portion of the contents of the database; 304 | 305 | b. if You include all or a substantial portion of the database 306 | contents in a database in which You have Sui Generis Database 307 | Rights, then the database in which You have Sui Generis Database 308 | Rights (but not its individual contents) is Adapted Material, 309 | 310 | including for purposes of Section 3(b); and 311 | c. You must comply with the conditions in Section 3(a) if You Share 312 | all or a substantial portion of the contents of the database. 313 | 314 | For the avoidance of doubt, this Section 4 supplements and does not 315 | replace Your obligations under this Public License where the Licensed 316 | Rights include other Copyright and Similar Rights. 317 | 318 | 319 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 320 | 321 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 322 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 323 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 324 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 325 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 326 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 327 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 328 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 329 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 330 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 331 | 332 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 333 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 334 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 335 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 336 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 337 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 338 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 339 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 340 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 341 | 342 | c. The disclaimer of warranties and limitation of liability provided 343 | above shall be interpreted in a manner that, to the extent 344 | possible, most closely approximates an absolute disclaimer and 345 | waiver of all liability. 346 | 347 | 348 | Section 6 -- Term and Termination. 349 | 350 | a. This Public License applies for the term of the Copyright and 351 | Similar Rights licensed here. However, if You fail to comply with 352 | this Public License, then Your rights under this Public License 353 | terminate automatically. 354 | 355 | b. Where Your right to use the Licensed Material has terminated under 356 | Section 6(a), it reinstates: 357 | 358 | 1. automatically as of the date the violation is cured, provided 359 | it is cured within 30 days of Your discovery of the 360 | violation; or 361 | 362 | 2. upon express reinstatement by the Licensor. 363 | 364 | For the avoidance of doubt, this Section 6(b) does not affect any 365 | right the Licensor may have to seek remedies for Your violations 366 | of this Public License. 367 | 368 | c. For the avoidance of doubt, the Licensor may also offer the 369 | Licensed Material under separate terms or conditions or stop 370 | distributing the Licensed Material at any time; however, doing so 371 | will not terminate this Public License. 372 | 373 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 374 | License. 375 | 376 | 377 | Section 7 -- Other Terms and Conditions. 378 | 379 | a. The Licensor shall not be bound by any additional or different 380 | terms or conditions communicated by You unless expressly agreed. 381 | 382 | b. Any arrangements, understandings, or agreements regarding the 383 | Licensed Material not stated herein are separate from and 384 | independent of the terms and conditions of this Public License. 385 | 386 | 387 | Section 8 -- Interpretation. 388 | 389 | a. For the avoidance of doubt, this Public License does not, and 390 | shall not be interpreted to, reduce, limit, restrict, or impose 391 | conditions on any use of the Licensed Material that could lawfully 392 | be made without permission under this Public License. 393 | 394 | b. To the extent possible, if any provision of this Public License is 395 | deemed unenforceable, it shall be automatically reformed to the 396 | minimum extent necessary to make it enforceable. If the provision 397 | cannot be reformed, it shall be severed from this Public License 398 | without affecting the enforceability of the remaining terms and 399 | conditions. 400 | 401 | c. No term or condition of this Public License will be waived and no 402 | failure to comply consented to unless expressly agreed to by the 403 | Licensor. 404 | 405 | d. Nothing in this Public License constitutes or may be interpreted 406 | as a limitation upon, or waiver of, any privileges and immunities 407 | that apply to the Licensor or You, including from the legal 408 | processes of any jurisdiction or authority. 409 | 410 | 411 | ======================================================================= 412 | 413 | Creative Commons is not a party to its public licenses. 414 | Notwithstanding, Creative Commons may elect to apply one of its public 415 | licenses to material it publishes and in those instances will be 416 | considered the "Licensor." Except for the limited purpose of indicating 417 | that material is shared under a Creative Commons public license or as 418 | otherwise permitted by the Creative Commons policies published at 419 | creativecommons.org/policies, Creative Commons does not authorize the 420 | use of the trademark "Creative Commons" or any other trademark or logo 421 | of Creative Commons without its prior written consent including, 422 | without limitation, in connection with any unauthorized modifications 423 | to any of its public licenses or any other arrangements, 424 | understandings, or agreements concerning use of licensed material. For 425 | the avoidance of doubt, this paragraph does not form part of the public 426 | licenses. 427 | 428 | Creative Commons may be contacted at creativecommons.org. 429 | -------------------------------------------------------------------------------- /font/material-design-icons/Material-Design-Icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/material-design-icons/Material-Design-Icons.eot -------------------------------------------------------------------------------- /font/material-design-icons/Material-Design-Icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/material-design-icons/Material-Design-Icons.ttf -------------------------------------------------------------------------------- /font/material-design-icons/Material-Design-Icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/material-design-icons/Material-Design-Icons.woff -------------------------------------------------------------------------------- /font/material-design-icons/Material-Design-Icons.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/material-design-icons/Material-Design-Icons.woff2 -------------------------------------------------------------------------------- /font/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /font/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /font/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /font/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /font/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /font/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /font/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /font/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /font/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /font/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /font/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /font/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /font/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /font/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /font/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /font/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /font/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /font/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /font/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /font/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/font/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Bold.eot -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Bold.woff -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Bold.woff2 -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Light.eot -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Light.ttf -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Light.woff -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Light.woff2 -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Medium.eot -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Medium.ttf -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Medium.woff -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Medium.woff2 -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Regular.eot -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Regular.woff -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Regular.woff2 -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Thin.eot -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Thin.ttf -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Thin.woff -------------------------------------------------------------------------------- /fonts/roboto/Roboto-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/fonts/roboto/Roboto-Thin.woff2 -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 39 | 40 |
41 | 42 | 52 | 53 | 63 | 64 |

Introduction

65 |
66 |
67 |
68 |
69 |

70 | This project was inspired by Test First and their approach to teaching beginners to code with a test driven mindeset. After not seeing any comparable Python projects, I decided to start my own. Many thansk to Sarah Allen and Alex Chaffee for providing excellent beginner resources for learning Ruby and Javascript. 71 |

72 |
73 |
74 |
75 |
76 | 77 |

Setup

78 |
79 |
80 |
81 |
82 | Install Python 83 |
84 | You will need Python, pip, and virtualenv for this Python tutorial. 85 |
86 | If you're using a Windows environment, go here for excellent instructions on setting up your environment. 87 |
88 | If you're using Mac OS X, go here for similarly excellent instructions. 89 |
90 | For Linux, here are your instructions 91 |
92 |
93 |
94 |
95 | 96 |
97 |
98 |
99 |
100 | Create your Virtualenv 101 |

Note: For more information on virtual environments, check this out.

102 |

Once you've gotten Python, pip, and virtualenv set up, you'll need to create your virtualenv.

103 |

First, enter your course directory.

104 | cd test_driven_python 105 |

Then, create your venv

106 | virtualenv venv 107 |

Next, activate your virtualenv

108 | source venv/bin/activate 109 |

Install the project requirements

110 | pip install -r requirements.txt 111 |

Now enter your first lesson

112 | cd lessons 113 |
114 | cd lesson_0_hello_world 115 |

Open that lesson's index.html file in a web browser and follow the instructions there to get started!

116 |
117 |
118 |
119 |
120 | 121 | 122 | 123 |

Next Steps

124 |
125 |
126 |
127 |
128 |

You should start with the first lesson (lesson_0_hello_world) to get a feel for how this course works. If you've finished that, fantastic! You'll now proceed through the lessons by opening the index.html file in each directory and following the instructions there.

129 |
130 |
131 |
132 |
133 | 134 |

Need Help?

135 |
136 |
137 |
138 |
139 | Where do you go if something doesn't seem to be working? 140 |
141 |
    142 |
  • Read the error message. Error messages are built to be informative, not scary. There's probably some infomration there that will lead you to your next step.
  • 143 |
  • Google! Googling your error message is one of the most valuable skills you can develop in the early stages of learning to program
  • 144 |
  • Stack Overflow is a great resource for debugging your code. 145 |
  • 146 |

    iPython. iPython is an interactive shell where you can test out your code in real time. I've included it in the requirements file, so if you've set up your venv and installed requirements.txt, all you have to do is type:

    147 | ipython 148 |

    in your command line with your venv activated, and you can try your code out. Never underestimate the power of debugging using well placed print statements!

    149 |
  • 150 |
  • Still can't find anything? Is something wrong with this Python course? If you think that's the case, hop over to the github repo and create an issue. Please include which version of Python you're using, and let me know how to reproduce the issue you're seeing.
  • 151 |
152 |
153 |
154 |
155 |
156 | 157 |
158 | 159 | 160 | -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | $( document ).ready(function(){ 2 | $(".button-collapse").sideNav(); 3 | $(".dropdown-button").dropdown(); 4 | }); 5 | -------------------------------------------------------------------------------- /lessons/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/lessons/__init__.py -------------------------------------------------------------------------------- /lessons/lesson_0_hello_world/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/lessons/lesson_0_hello_world/__init__.py -------------------------------------------------------------------------------- /lessons/lesson_0_hello_world/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 |
42 | 43 | 53 | 54 | 64 | 65 |

Hello World!

66 |
67 |
68 |
69 |
70 |

71 | This lesson is meant for absolute beginners in Python. We'll learn how to craft a 72 | function that says "Hello, World!". 73 |

74 |
75 |
76 |
77 |
78 | 79 | 80 |

Lesson objectives:

81 |
82 |
83 |
84 |
85 |

86 |

    87 |
  • 88 | Create a .py file in the correct directory. 89 |
  • 90 |
  • 91 | Understand how to define a function in Python. 92 |
  • 93 |
  • 94 | Learn how to make a function return a value. 95 |
  • 96 |
97 | 98 |

99 | This lesson teaches you how to define a Python function. Try running the tests for this lesson 100 | before doing 101 | anything else! 102 |

103 | 104 |

105 |
106 |
107 |
108 |
109 | 110 |

Running Python Unit Tests

111 | 112 | 113 |
114 |
115 |
116 |
117 |

118 | Here's a command that will allow you to run your tests from the top level directory of this 119 | project (the test_driven_python 120 | folder): 121 |

122 | python -m unittest -vf tests.test_lesson_0_hello_world 123 | 124 |

125 | That can look a little overwhelming at first, so let's break it down. 126 |

127 |
    128 |
  • python indicates that we want to run something in Python
  • 129 |
  • -m tells Python to run the module we want (test.test_lesson_x) 130 | as 131 | a script 132 |
  • 133 |
  • unittest tells Python that we're running unit tests
  • 134 |
  • -vf is a combination of two separate options: 135 |
      136 | 137 |
    • -v encourages Python to be as verbose as possible with 138 | its output 139 |
    • 140 |
    • -f tells Python to stop running the tests once it hits a 141 | failure. This 142 | is entirely 143 | optional, but you should try doing this to fix one failure at a time while you get 144 | the hang of how 145 | Python works 146 |
    • 147 |
    148 |
  • 149 |
  • 150 | Finally, we tell Python which file it should run. In this case, that's 151 | tests.test_lesson_0_hello_world 152 |
  • 153 |
154 |
155 |
156 |
157 |
158 | 159 |
160 |
161 |
162 |
163 | 164 | What error did you get when you first ran that command? 165 | 166 | 167 |

168 | Remember, errors are built to be helpful, not scary! 169 |

170 | 171 |

172 | You should have seen an ImportError. This is Python telling you that 173 | it's unable to 174 | import the 175 | module hello_world because there is no hello_world.py 176 | file in the 177 | lesson 0 directory. 178 |

179 | So your first step should be to create hello_world.py in lessons/lesson_0_hello_world. 181 |

182 | 183 |

184 | Run the tests again. What's your next failure? 185 |

186 | 187 |
188 |
189 |
190 |
191 | 192 |

193 | Defining a function in Python 194 |

195 | 196 |
197 |
198 |
199 |
200 |

201 | Functions in Python are defined by the def keyword, followed by your 202 | function name. After 203 | your function name there are two parenthases followed by a colon, (): . If you're 204 | defining a 205 | function that accepts arguments this is where they'll go. Don't worry about arguments 206 | for now, though. 207 | We'll cover those in the next lesson. 208 |

209 | 210 |

211 | Python operates using significant whitespace. This means that each time you enter a new 212 | function or 213 | logical block, you need to indent your code by 4 spaces. Python knows when you're done with your 214 | block when you 215 | unindent by 4 spaces. 216 |

217 | 218 | 219 | def does_something(): 220 | 221 |
222 | 223 |     print "something" 224 | 225 | 226 |
227 |
228 |
229 |
230 | 231 | 232 |

Print vs Return

233 | 234 |
235 |
236 |
237 |
238 |

239 | Two core concepts to think about when using Python are print and 240 | return. 241 |

242 | 243 |

244 | print takes something in your function and prints it out into your 245 | terminal for 246 | you to read. This is a great way to see what's going on inide your function while it's running. 247 | print is incredibly useful for debugging, but does not provide your program with 248 | any useful 249 | information. print is for human use only. 250 |

251 | 252 |

253 | return is the keyword that lets Python know that you're ready to 254 | exit the function 255 | with the value on that line. return provides useful information to the computer so 256 | that computed 257 | values can be passed from one function to another. Whenever you want to set something as the 258 | result of 259 | your function, use return. Remember that this will exit the function. You should 260 | never have any 261 | logic in your function that is unreachable after the return statement. 262 |

263 | 264 |

265 | Note: if you've used other programming lanugages before, you might have come across the idea of 266 | implicit 267 | returns, meaning that the last variable in the top level scope of the function will always be 268 | returned. This is 269 | not the case in Python. If you want your function to return something, you must 270 | always 271 | explicitly state it, otherwise your function will return None. 272 |

273 |
274 |
275 |
276 |
277 | 278 | 279 |

Finishing the challenge

280 |
281 |
282 |
283 |
284 |

285 | Now that you have your hello_world.py file, edit it to include a function that 286 | returns 287 | the 288 | string "Hello World!" Once you've edited your file, keep running your tests and amending 289 | your code until all 290 | of 291 | your tests pass. You'll know you're done when the output on your terminal reads: 292 |

293 | Ran 2 tests in 0.000s OK 294 |

295 | That means that you've passed all the tests and completed the challenge!
You're ready to 296 | move on to Lesson 1: Greetings! 297 |

298 |
299 |
300 |
301 |
302 | 303 |
304 | 305 | 306 | -------------------------------------------------------------------------------- /lessons/lesson_1_greetings/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/lessons/lesson_1_greetings/__init__.py -------------------------------------------------------------------------------- /lessons/lesson_1_greetings/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 |
42 | 43 | 53 | 54 | 64 | 65 |

66 | Personalized Greetings 67 |

68 |
69 |
70 |
71 |
72 |

73 | Instead of printing a generic output, like we did in Lesson 0, this time we'll create a function 75 | that takes an input and creates a customized greeting for whatever name you feed into the 76 | function. 77 |

78 |
79 |
80 |
81 |
82 | 83 |

Lesson objectives:

84 |
85 |
86 |
87 |
88 |
    89 |
  • 90 | Understand how to define a function that accepts inputs (arguments) 91 |
  • 92 |
  • 93 | Learn how to use a function's arguments inside your function 94 |
  • 95 |
  • Understand basic string interpolation
  • 96 |
97 |
98 |
99 |
100 |
101 | 102 |

103 | Defining a function that accepts arguments 104 |

105 | 106 |
107 |
108 |
109 |
110 |

111 | In Lesson 0 we learned how to define a basic 112 | function in 113 | Python. This time, we'll create a function that's slightly more complicated. 114 |

115 | 116 |

117 | Instead of using empty parenthases when defining the name of the funtion, we can fill those 118 | parenthases with 119 | argument names. These are variables that we pass into the function that we can use 120 | later. 121 |

122 | 123 | def repeat_my_argument(argument): 124 | 125 |
126 | 127 |     print argument + "!" 128 | 129 | 130 |

131 | That simple function above takes a variable and prints it out with an extra "!" on th end. It's 132 | a simplistic 133 | example, but it illustrates how we can use arguments inside a function to alter our output. 134 |

135 |
136 |
137 |
138 |
139 | 140 |

String Interpolation

141 |
142 |
143 |
144 |
145 |

146 | String interpolation is the name for inserting a variable into a string so that we can 147 | alter the end 148 | state of that string. For example, if we had a family with the last name "Smith," and we wanted 149 | to create a 150 | function that takes in a first name and outputs their whole name, it might look something like 151 | this: 152 |

153 | 154 | 155 | def smith_full_name(first_name): 156 | 157 |
158 | 159 |     return "{} Smith".format(first_name) 160 | 161 |

162 | 163 | smith_full_name("Ellen") #=> "Ellen Smith" 164 | 165 | 166 |

167 | In that example, we provided the name "Ellen" to the function, and it inserted it into the 168 | string using the 169 | .format method. 170 |

171 |
172 |
173 |
174 |
175 | 176 |

Challenge Rules:

177 | 178 |
179 |
180 |
181 |
182 |
    183 |
  • 184 | Create a Python file called greetings.py in the lessons/lesson_1_greetings 186 | directory 187 |
  • 188 |
  • 189 | Create a function called greetings in the greetings.py 190 | file 191 |
  • 192 |
  • 193 | The function should accept a single string as an argument 194 |
  • 195 |
  • 196 | The function should return "Hi, " + argument + "!" 197 |
  • 198 |
199 | 200 | Example 201 |
202 | greetings("Jill") #=> "Hi, Jill!" 203 |
204 |
205 |
206 |
207 | 208 |

Running the exercise

209 | 210 |
211 |
212 |
213 |
214 |

215 | Let's run our unit test command in the terminal again, but with the correct module name for our 216 | lesson 1 tests. 217 | 218 |

219 | 220 | python -m unittest -vf tests.test_lesson_1_greetings 221 | 222 | 223 |

224 | Again, you should have seen an ImportError that indicates we need a certain file 225 | that doesn't exist 226 | yet. So let's create greetings.py in lessons/lesson_1_greetings. 227 |

228 | 229 |

230 | Run your tests again and fix each failure as it comes up. You'll know you're done when your 231 | output says: 232 |

233 | Ran 3 tests in 0.000s OK 234 |

235 | Congratulations, you're ready for Lesson 2! 236 |

237 |
238 |
239 |
240 |
241 |
242 | 243 | 244 | -------------------------------------------------------------------------------- /lessons/lesson_2_fizzbuzz/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/lessons/lesson_2_fizzbuzz/__init__.py -------------------------------------------------------------------------------- /lessons/lesson_2_fizzbuzz/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 |
42 | 43 | 53 | 54 | 64 | 65 |

FizzBuzz

66 |
67 |
68 |
69 |
70 |

71 | Fizzbuzz is a common coding challenge that teaches control flow and use of the modulus operator. 72 | 73 |

74 |

75 | Some FizzBuzz challenges you can find online will have you print out the result for each number 76 | from 1 to x. 77 | In this challenge, however, we'll save all of the 'Fizzbuzzed' results to a list and return the 78 | list at the end 79 | of the function. 80 |

81 |
82 |
83 |
84 |
85 | 86 |

Lesson objectives

87 | 88 |
89 |
90 |
91 |
92 |
    93 |
  • Understand how to assign variables within a function
  • 94 |
  • Understand how to use the mouduls operator
  • 95 |
  • Get comfortable with basic control flow
  • 96 |
  • Understand how to initialize and add items to a list
  • 97 |
98 |
99 |
100 |
101 |
102 | 103 |

Modulus operator

104 | 105 |
106 |
107 |
108 |
109 |

110 | Modulus is a useful mathematical operation available in Python using the 111 | % 112 | symbol. It returns 113 | the remainder of two numbers when you divide x by y. For example: 114 |

115 | 116 | 117 | 10 % 2 # == 0 because 10 is divisible by 2 118 | 119 |
120 | 121 | 8 % 3 # == 2 because 2 is the remainder of 8 / 3 122 | 123 |
124 |
125 |
126 |
127 | 128 |

Control flow

129 | 130 |
131 |
132 |
133 |
134 |

135 | Control flow is the process by which we tell a program how to make decisions based on certain 136 | criteria. If one 137 | condition is True, you instruct the program to do one thing. If it's 138 | not, you tell 139 | the program to 140 | do something else. 141 |

142 |

143 | Control flow in Python is fairly readable. For example: 144 |

145 | 146 | 147 | x = 2 148 | 149 |
150 | 151 | if x > 2: 152 | 153 |
154 | 155 |     print "x is greater than 2" 156 | 157 |
158 | 159 | else: 160 | 161 | 162 |
163 |     print "x is less than or equal to 2" 164 |
165 | 166 |

167 | However, Python doesn't limit us to just two options. We can check for multiple conditions in 168 | our control flow 169 | with the elif keyword. 170 |

171 | 172 | 173 | x = 2 174 | 175 |
176 | 177 | if x > 2: 178 | 179 |
180 | 181 |     print "x is greater than 2" 182 | 183 |
184 | 185 | elif x == 2: 186 | 187 |
188 | 189 |     print "x equals 2" 190 | 191 |
192 | 193 | elif x == 0: 194 | 195 |
196 | 197 |     print "x is zero" 198 | 199 |
200 | 201 | else: 202 | 203 |
204 | 205 |     print "x is less than 2" 206 | 207 | 208 |

209 | The else keyword lets us know that if we've passed through 210 | all of our 211 | specifically listed 212 | conditions and didn't find anything, it should do this. It acts as the "base case" for what to 213 | do when no other 214 | condition is satisfied. 215 |

216 | 217 |
218 |
219 |
220 |
221 | 222 |

Features of a list

223 |
224 |
225 |
226 |
227 | 228 |

229 | If you've worked in other programming langauges before, or if you're familiar with different 230 | structures in math, 231 | a list is Python's name for an array. 232 |

233 | 234 |
    235 |
  • 236 | Python initializes an empty list with two empty square brackets: [] 238 |
  • 239 |
  • 240 | A list can hold any number of items. The items do not need to be the same type. 241 |
  • 242 |
  • 243 | Lists are ordered. That means that to access a value inside the list, you need to know what 244 | position it 245 | occupies in the list. 246 |
  • 247 |
  • 248 | List positions, or "indexes" (or "indicies" - both are correct and people will use them 249 | interchangeably), 250 | start counting at 0, not 1. 251 |
  • 252 |
253 |
254 |
255 |
256 |
257 | 258 |

Challenge Rules:

259 | 260 |
261 |
262 |
263 |
264 |
    265 |
  • 266 | The function should accept an integer 267 |
  • 268 |
  • 269 | For every integer between 1 and the input the function should save one of four options to an 270 | list 271 |
  • 272 |
      273 |
    1. 274 | If the integer is divisible by 3, save the word "Fizz" to the list 275 |
    2. 276 |
    3. 277 | If the integer is divisible by 5, save the word "Buzz" to the list 278 |
    4. 279 |
    5. 280 | If the integer is divisible by both 3 and 5, save the word "FizzBuzz" to the list 281 |
    6. 282 |
    7. 283 | If the integer is divisible by neither 3 nor 5, save that integer to the list 284 |
    8. 285 |
    286 |
  • 287 | The function should return a list of integers and strings 288 |
  • 289 |
  • 290 | Edge cases 291 |
      292 |
    • 293 | If the integer input is <= 0, return an emtpy list 294 |
    • 295 |
    296 |
  • 297 |
298 | 299 |
300 |
301 |
302 |
303 | 304 | 305 |
306 |
307 |
308 |
309 | Example 310 |
311 | 312 | fizzbuzz(15) #=> [1, 2, "Fizz", 4, "Buzz", "Fizz", 7, 8, "Fizz", "Buzz", 11, "Fizz", 13, 14, 313 | "FizzBuzz"] 314 | 315 |
316 |
317 |
318 |
319 |
320 | 321 | 322 | -------------------------------------------------------------------------------- /lessons/lesson_3_calculator/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/lessons/lesson_3_calculator/__init__.py -------------------------------------------------------------------------------- /lessons/lesson_3_calculator/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 |
42 | 43 | 53 | 54 | 64 |

Basic Calculator

65 |
66 |
67 |
68 |
69 |

In this lesson you'll build a basic calculator script that has the following functions:

70 |
    71 |
  • add accepts two integers and returns the sum of those integers 72 |
  • 73 |
  • subtract accepts two integers and returns the first minus the 74 | second 75 |
  • 76 |
  • total_sum accepts a 77 | list of integers and 78 | returns the sum of all integers in the list. 79 |
  • 80 |
81 |
82 |
83 |
84 |
85 | 86 |

Lesson objectives

87 | 88 |
89 |
90 |
91 |
92 |
    93 |
  • Perform basic mathematical operations
  • 94 |
  • Learn to interact with list elements
  • 95 |
  • Create a python file containing multiple functions
  • 96 |
  • Read some Python documentation!
  • 97 |
98 |
99 |
100 |
101 |
102 | 103 |

Challenge Rules:

104 | 105 |
106 |
107 |
108 |
109 | add 110 |
    111 |
  1. The function should accept two integers
  2. 112 |
  3. The function should return one integer equal to the sum of the two inputs
  4. 113 |
  5. The function should work for positive and negative integers
  6. 114 |
115 | 116 | subtract 117 |
    118 |
  1. The function should accept two integers
  2. 119 |
  3. The function should return one integer equal to the first integer minus the second
  4. 120 |
  5. The function should work for positive and negative integers
  6. 121 |
122 | 123 | 124 | total_sum 125 |
    126 |
  1. The function should accept a list of integers
  2. 127 |
  3. The function should return one integer equal to the sum of all the list elements
  4. 128 |
  5. The function should work for a list of any size
  6. 129 |
  7. The function should work for positive and negative integers
  8. 130 |
  9. Edge case: 131 |
      132 |
    • If the function receives an empty list, return 0
    • 133 |
    134 |
  10. 135 |
136 |
137 |
138 |
139 |
140 | 141 |

Working with iterables!

142 | 143 |
144 |
145 |
146 |
147 |

148 | The total_sum function probably requires some additional Python 149 | concepts you might 150 | not be familiar with yet. You'll need to learn to move through each element of a list, or iterate 151 | over the contents of the list. 152 |

153 |

154 | In previous lessons, the lesson's index.html file has walked you 155 | through everything 156 | you need 157 | to know in order to accomplish the challenge goals. This time, however, you'll need to take a 158 | peek at 159 | the Python documentation on for 160 | loops. 161 |

162 |

163 | See if you can find the information you need in the linked documentation. If you still need 164 | help, I 165 | recommend googling your question (or even googling your error message!). Learning to ask the 166 | right 167 | questions and hunt for answers is one of the most important skills you can learn as a developer. 168 |

169 |
170 |
171 |
172 |
173 | 174 |

Example

175 | 176 |
177 |
178 |
179 |
180 | add 181 |
182 | add(2, 3) #=> 5 183 |
184 | add(-2, 3) #=> 1 185 |
186 | add(-10, -3) #=> -7 187 |
188 |
189 |
190 |
191 | 192 |
193 |
194 |
195 |
196 | subtract 197 |
198 | subtract(5, 3) #=> 2 199 |
200 | subtract(10, -3) #=> 13 201 |
202 | subtract(-12, -2) #=> -10 203 |
204 |
205 |
206 |
207 | 208 |
209 |
210 |
211 |
212 | total_sum 213 |
214 | total_sum([1, 2, 3]) #=> 6 215 |
216 | total_sum([2, 3]) #=> 5 217 |
218 | total_sum([8, 4, -2]) #=> 10 219 |
220 | total_sum([]) #=> 0 221 |
222 | total_sum([0]) #=> 0 223 |
224 |
225 |
226 |
227 | 228 | 229 |
230 | 231 | 232 | 233 | -------------------------------------------------------------------------------- /lessons/lesson_4_temperature/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/lessons/lesson_4_temperature/__init__.py -------------------------------------------------------------------------------- /lessons/lesson_4_temperature/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 | 42 |
43 | 53 | 54 | 64 | 65 |

Temperature

66 |
67 |
68 |
69 |
70 |

71 | In this lesson you'll build two functions, f_to_c and c_to_f that will 73 | convert 74 | readings from one scale into another. For this particular example, we don't want to have to deal 75 | with 76 | decimal precision, so each function should return an integer reading after the 77 | conversion. 78 |

79 | 80 |
81 |
82 |
83 |
84 | 85 |

Fahrenheit and Celsius

86 | 87 |
88 |
89 |
90 |
91 |

92 | One of the key differences between Fahrenheit and Celsius is that the freezing point of water is 93 | 0 degrees 94 | on the Celsius scale, and 32 degrees on the Fahrenheit scale. 95 | 96 |

97 | 98 |

99 | Beyond that, each degree of Fahrenheit is 5/9 of one degree Celsius. 100 |

101 | 102 | 103 |
104 |
105 |
106 |
107 | 108 |

How is a float different than an integer?

109 | 110 | 111 |
112 |
113 |
114 |
115 |

116 | floats are a data type in Python that represent numbers. They're different from integers 117 | because floats can represent a larger subset of numbers. floats have decimal 118 | points. 119 | integers will always represent "whole" numbers. 120 |

121 | 122 |

123 | When doing integer math in Python, it will always round down. 124 |

125 | 126 |

127 | Python2 uses integer math for math operations when you input integers. 128 |

129 | 130 | 5 / 3 = 1 131 | 132 |

However, Python3 uses float math when dividing, so instead you'll get

133 | 134 | 5 / 3 = 1.6666666666666667 135 |

136 | Your code implementation for this challenge will be different based on which version of python 137 | you're 138 | using. If you aren't sure whether you're using Python2 or Python3 type python 139 | --version 140 | in your terminal with your virtualenv activated. It will print out your current version. 141 |

142 | 143 |
144 |
145 |
146 |
147 | 148 |

Lesson objectives

149 | 150 |
151 |
152 |
153 |
154 |
    155 |
  • Floating point math
  • 156 |
157 |
158 |
159 |
160 |
161 | 162 | 163 |

Challenge Rules:

164 | 165 |
166 |
167 |
168 |
169 | f_to_c
170 |
    171 |
  • Accepts a float
  • 172 |
  • Converts a Fahrenheit reading to Celsius
  • 173 |
  • Returns an integer
  • 174 |
175 |
176 |
177 |
178 |
179 | 180 |
181 |
182 |
183 |
184 | c_to_f
185 |
    186 |
  • Accepts a float
  • 187 |
  • Converts a Celsius reading to Fahrenheit
  • 188 |
  • Returns an integer
  • 189 |
190 |
191 |
192 |
193 |
194 | 195 | 196 |

Example

197 | 198 |
199 |
200 |
201 |
202 | freezing point
203 | f_to_c(32) #=> 0 204 |
205 | c_to_f(0) #=> 32 206 |
207 |
208 |
209 |
210 | 211 |
212 |
213 |
214 |
215 | boiling point
216 | f_to_c(212) #=> 100 217 |
218 | c_to_f(100) #=> 212 219 |
220 |
221 |
222 |
223 | 224 |
225 | 226 | 227 | -------------------------------------------------------------------------------- /lessons/lesson_5_pig_latin/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/lessons/lesson_5_pig_latin/__init__.py -------------------------------------------------------------------------------- /lessons/lesson_5_pig_latin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 | 42 |
43 | 53 | 54 | 64 |

65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /lessons/lesson_6_books/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/lessons/lesson_6_books/__init__.py -------------------------------------------------------------------------------- /lessons/lesson_6_books/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 | 42 |
43 | 53 | 54 | 64 |

65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /lessons/lesson_7_anagrams/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/lessons/lesson_7_anagrams/__init__.py -------------------------------------------------------------------------------- /lessons/lesson_7_anagrams/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 | 42 |
43 | 53 | 54 | 64 |

65 |
66 |
67 |
68 |
69 | 70 |
71 |
72 |
73 |
74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ipython 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_lesson_0_hello_world.py: -------------------------------------------------------------------------------- 1 | # This lesson teaches you how to define a Python method. Try running the tests for this file before doing anything! 2 | 3 | # Here's a python script you can use to run your tests from the top level directory of this project 4 | # `python -m unittest -vf tests.test_lesson_0_hello_world` 5 | # That command looks like a lot at first. Let's break it down. 6 | 7 | 8 | # `python` indicates that you want to run a python file 9 | # -m tells python to run the module (tests.test_lesson_x) as a script 10 | # unittest tells python that we're running unit tests on something 11 | # -vf is the combination of two options that I'll outline separately below: 12 | # -v encourages python to be as verbose as possible with its output 13 | # -f tells python to stop running the tests once it hits a failure. This is entirely optional, but I 14 | # want you to try running the tests one at a time, and fix each failure as it comes, rather than trying to 15 | # make all the tests pass at once. That way you get a feel for how to build some python functions one idea at a time 16 | # `tests.test_lesson_0_hello_world` is the specific module (composed of unit tests) that we're executing 17 | 18 | 19 | # What error did you get when you first ran it? 20 | # Remember, errors are built to be **helpful**, not scary! 21 | 22 | # It should be an ImportError. This is Python telling you that it's unable to import the file `hello_world` 23 | 24 | # So your first step should be to create `hello_world.py` in the `lessons/lesson_0_hello_world` directory 25 | 26 | # Run the tests again. 27 | 28 | import unittest 29 | 30 | from lessons.lesson_0_hello_world import hello_world 31 | 32 | 33 | class HelloWorldTestClass(unittest.TestCase): 34 | def test_hello_function_exists(self): 35 | func = hello_world.hello_world 36 | self.assertIsNotNone(func) 37 | 38 | def test_hello_function_output(self): 39 | greeting = hello_world.hello_world() 40 | self.assertEqual(greeting, "Hello World!") 41 | -------------------------------------------------------------------------------- /tests/test_lesson_1_greetings.py: -------------------------------------------------------------------------------- 1 | # This lesson teaches you how to define a function that takes an input, and then uses 2 | # it to alter the output of that function. 3 | 4 | # Much like last time, run your python command in your terminal to see your first failure. 5 | 6 | # `python -m unittest -vf tests.test_lesson_1_greetings` 7 | 8 | # Again, like last time, you should see an import error indicating that you need a file titled 9 | # `greetings` in your lesson folder. 10 | 11 | # This challenge is very similar to the last one, but instead of always outputting the same string - "Hello World!", 12 | # the output will change based on the input you give to the function. 13 | 14 | 15 | import unittest 16 | 17 | from lessons.lesson_1_greetings import greetings 18 | 19 | 20 | class GreetingsTestCase(unittest.TestCase): 21 | def test_greetings_function_exists(self): 22 | func = greetings.greetings 23 | self.assertIsNotNone(func) 24 | 25 | def test_greetings_function_with_input(self): 26 | greet = greetings.greetings("Amy") 27 | self.assertEqual(greet, "Hi, Amy!") 28 | 29 | def test_grettings_function_with_another_input(self): 30 | greet = greetings.greetings("Belle") 31 | self.assertEqual(greet, "Hi, Belle!") 32 | -------------------------------------------------------------------------------- /tests/test_lesson_2_fizzbuzz.py: -------------------------------------------------------------------------------- 1 | # Fizzbuzz is a common coding challenge that teaches control flow and use of the modulus operator. 2 | 3 | # Some FizzBuzz challenges will have you print out the result for each number from 1 to x. 4 | # In this challenge however, we'll save all of the 'Fizzbuzzed' results to an list 5 | # and return the list at the end of the function. 6 | 7 | # If you've worked in other programming langauges before, or if you're familiar with different 8 | # structures in math, a list is Python's name for an array. Two square brackets ("[]") are the way 9 | # Python represents an empty list. The list can hold any number of items. The items do not need to be the same type. 10 | # Lists are ordered. That means that to access a value inside the list, you need to know what position it occupies in the list. 11 | # List positions, or "indexes" (or "indicies" - both are correct and people will use them interchangeably), start counting at 0, not 1. 12 | 13 | # Challenge Rules: 14 | # - The function should accept an integer 15 | # - For every integer between 1 and the input the function should save one of four options to an list 16 | # - If the integer is divisible by 3, save the word "Fizz" to the list 17 | # - If the integer is divisible by 5, save the word "Buzz" to the list 18 | # - If the integer is divisible by both 3 and 5, save the word "FizzBuzz" to the list 19 | # - If the integer is divisible by neither 3 nor 5, save that integer to the list 20 | # - Return the list of integers and strings 21 | # - Edge cases: 22 | # - If the integer input is <= 0, return an emtpy list 23 | 24 | import unittest 25 | 26 | from lessons.lesson_2_fizzbuzz import fizzbuzz 27 | 28 | 29 | class FizzBuzzTestCase(unittest.TestCase): 30 | def test_fizzbuzz_returns_list(self): 31 | twelve = fizzbuzz.fizzbuzz(12) 32 | self.assertIsInstance(twelve, list) 33 | 34 | def test_fizzbuzz_list_is_the_correct_length(self): 35 | twelve = fizzbuzz.fizzbuzz(12) 36 | self.assertEqual(len(twelve), 12) 37 | eighty = fizzbuzz.fizzbuzz(80) 38 | self.assertEqual(len(eighty), 80) 39 | 40 | def test_fizzbuzz_handles_multiples_of_3(self): 41 | up_to_twelve = fizzbuzz.fizzbuzz(12) 42 | self.assertEqual(up_to_twelve[2], "Fizz") 43 | self.assertEqual(up_to_twelve[5], "Fizz") 44 | self.assertEqual(up_to_twelve[8], "Fizz") 45 | self.assertEqual(up_to_twelve[11], "Fizz") 46 | 47 | def test_fizzbuzz_handles_multiples_of_5(self): 48 | up_to_twenty_five = fizzbuzz.fizzbuzz(25) 49 | self.assertEqual(up_to_twenty_five[4], "Buzz") 50 | self.assertEqual(up_to_twenty_five[9], "Buzz") 51 | self.assertEqual(up_to_twenty_five[19], "Buzz") 52 | 53 | def test_fizzbuzz_handles_multiples_of_3_and_5(self): 54 | up_to_eighty = fizzbuzz.fizzbuzz(60) 55 | self.assertEqual(up_to_eighty[14], "FizzBuzz") 56 | self.assertEqual(up_to_eighty[29], "FizzBuzz") 57 | self.assertEqual(up_to_eighty[44], "FizzBuzz") 58 | self.assertEqual(up_to_eighty[59], "FizzBuzz") 59 | 60 | def test_fizzbuzz_saves_non_fizzbuzzable_integers_to_list(self): 61 | up_to_seventy_three = fizzbuzz.fizzbuzz(73) 62 | self.assertIsInstance(up_to_seventy_three[0], int) 63 | self.assertEqual(up_to_seventy_three[0], 1) 64 | self.assertIsInstance(up_to_seventy_three[12], int) 65 | self.assertEqual(up_to_seventy_three[12], 13) 66 | self.assertIsInstance(up_to_seventy_three[-1], int) 67 | self.assertEqual(up_to_seventy_three[-1], 73) 68 | 69 | def test_fizzbuzz_handles_zero(self): 70 | zero = fizzbuzz.fizzbuzz(0) 71 | self.assertIsInstance(zero, list) 72 | self.assertListEqual(zero, []) 73 | 74 | def test_fizzbuzz_handles_negative_integers(self): 75 | negative = fizzbuzz.fizzbuzz(-15) 76 | self.assertIsInstance(negative, list) 77 | self.assertListEqual(negative, []) 78 | -------------------------------------------------------------------------------- /tests/test_lesson_3_calculator.py: -------------------------------------------------------------------------------- 1 | # In this lesson you'll create several functions to perform basic calculations 2 | 3 | # Run your tests to get your first failure. Try reading the tests first to anticipate which failures 4 | # you'll run into. 5 | 6 | # `python -m unittest -vf tests.test_lesson_3_calculator` 7 | 8 | # You should create 3 functions: 9 | 10 | # `add` should accept two integers and return the sum of those two integers 11 | # `subtract` should accept two integers and return the first integer minus the second 12 | # `total_sum` should accept a list of integers and return the total sum of all the array elements 13 | # note: if total_sum receives an empty list, it should return 0 14 | 15 | # All three functions should be able to handle both positive and negative integers 16 | 17 | 18 | import unittest 19 | 20 | from lessons.lesson_3_calculator import calculator 21 | 22 | 23 | class AddTestCase(unittest.TestCase): 24 | def test_add_returns_sum_of_two_numbers(self): 25 | five = calculator.add(2, 3) 26 | self.assertEqual(five, 5) 27 | ten = calculator.add(7, 3) 28 | self.assertEqual(ten, 10) 29 | 30 | def test_position_of_arguments_does_not_matter(self): 31 | a = calculator.add(2, 3) 32 | b = calculator.add(3, 2) 33 | self.assertEqual(a, b) 34 | 35 | def test_add_handles_negative_numbers(self): 36 | a = calculator.add(-3, 5) 37 | self.assertEqual(a, 2) 38 | 39 | 40 | class SubtractTestCase(unittest.TestCase): 41 | def test_subtract_returns_second_argument_minus_first(self): 42 | three = calculator.subtract(10, 7) 43 | self.assertEqual(three, 3) 44 | eleven = calculator.subtract(15, 4) 45 | self.assertEqual(eleven, 11) 46 | 47 | def test_position_matters_for_subtraction(self): 48 | a = calculator.subtract(10, 7) 49 | b = calculator.subtract(7, 10) 50 | self.assertNotEqual(a, b) 51 | 52 | def test_subtract_handles_negative_numbers(self): 53 | a = calculator.subtract(-7 - 8) 54 | self.assertEqual(a, 1) 55 | 56 | 57 | class TotalSumTestCase(unittest.TestCase): 58 | def test_total_sum_accepts_list_and_returns_sum_of_list(self): 59 | result = calculator.total_sum([1, 2, 3]) 60 | self.assertEqual(result, 6) 61 | 62 | def test_total_sum_handles_negative_integers(self): 63 | result = calculator.total_sum([-1, -2, -3]) 64 | self.assertEqual(result, -6) 65 | 66 | def test_total_sum_handles_mix_of_positive_and_negative_integers(self): 67 | result = calculator.total_sum([1, 2, -3, -5]) 68 | self.assertEqual(result, -5) 69 | 70 | def test_empty_array_input_returns_0(self): 71 | result = calculator.total_sum([]) 72 | self.assertEqual(result, 0) 73 | -------------------------------------------------------------------------------- /tests/test_lesson_4_temperature.py: -------------------------------------------------------------------------------- 1 | # This lesson introduces the concept of floating point math in Python 2 | 3 | # Note: It's important for this lesson to know what version of python you're running. 4 | 5 | # type `python --version` in your terminal to see what version of python you're using. 6 | 7 | # As usual, run your python command in your terminal to see your first failure. 8 | 9 | # `python -m unittest -vf tests.test_lesson_4_temperature` 10 | 11 | # This challenge asks you to create two functions: `f_to_c` and `c_to_f` 12 | # Your functions should be able to receive a float and return an integer. 13 | # Try adding you own tests to make sure your functions are returning the correct values! 14 | 15 | import unittest 16 | 17 | from lessons.lesson_4_temperature import temperature 18 | 19 | 20 | class CtoFTestCase(unittest.TestCase): 21 | def test_handles_freezing_point(self): 22 | freezing = temperature.c_to_f(0) 23 | self.assertEqual(freezing, 32) 24 | 25 | def test_handles_boiling_point(self): 26 | boiling = temperature.c_to_f(100) 27 | self.assertEqual(boiling, 212) 28 | 29 | def test_handle_room_temp(self): 30 | arbitrary = temperature.c_to_f(20) 31 | self.assertEqual(arbitrary, 68) 32 | 33 | def test_handles_body_temp(self): 34 | body_temp = temperature.c_to_f(37) 35 | self.assertEqual(body_temp, 98) 36 | 37 | 38 | class FtoCTestCase(unittest.TestCase): 39 | def test_handles_freezing_point(self): 40 | freezing = temperature.f_to_c(32) 41 | self.assertEqual(freezing, 0) 42 | 43 | def test_handles_boiling_point(self): 44 | boiling = temperature.f_to_c(212) 45 | self.assertEqual(boiling, 100) 46 | 47 | def test_handles_arbitrary_temp(self): 48 | arbitrary = temperature.f_to_c(68) 49 | self.assertEqual(arbitrary, 20) 50 | 51 | def test_handles_body_temp(self): 52 | body_temp = temperature.f_to_c(98.6) 53 | self.assertEqual(body_temp, 37) 54 | -------------------------------------------------------------------------------- /tests/test_lesson_5_pig_latin.py: -------------------------------------------------------------------------------- 1 | # In this lesson you'll be working with regular expressions for the first time. For an introduction to 2 | # regular expressions, refer to `lesson_5_pig_latin/index.html`. If you're still unsure how to proceed, 3 | # check out the additional resources linked in the lesson. 4 | 5 | # Regular expressions are rules you construct to figure out patterns inside strings. 6 | 7 | # You'll need to write a function called `translate` in your lesson_5_pig_latin.pig_latin file. However 8 | # you might want to construct one or more helper methods that do the heavy lifting of your translation 9 | # operation. This lesson's `index.html` file has more information about helper methods and separation 10 | # of concerns. 11 | 12 | # Run your tests! 13 | # `python -m unittest -vf tests.test_lesson_5_pig_latin` 14 | 15 | # Rules: 16 | # - Single letter words are appended with "ay" 17 | # - Ex: "i" -> "iay" 18 | # - Words that start with vowels are appended with "ay" 19 | # - Ex: "apple" -> "appleay" 20 | # - Words that start with consonants have the beginning consonant group shifted to the end of the word. 21 | # The new "shifted" word is appended with "ay" 22 | # - Ex: "whiskers" -> "iskerswhay" 23 | # - "qu" is treated as a single consonant 24 | # - Ex: "quit" -> "itquay" 25 | # - If a string contains multiple words, it should transform each word individually. 26 | # - Ex: "hello world" -> "ellohay orldway" 27 | 28 | # BONUS 29 | # There is an additional bonus test class with a `skip` decorator on top of it. For more information 30 | # on decorators, refer to `lesson_5_pig_latin/bonus.html` 31 | # To run the bonus tests, comment out or remove the skip decorator 32 | # Bonus Rules: 33 | # - Words that are capitalized should have the first letter of the translated word capitalized. 34 | # If the initial letter of the word has been shifted, it should no longer be capitalized. 35 | # - Ex: "Mary" -> "Arymay", "Eliose" -> "Eloiseay" 36 | # - Punctionation that occurs at the end of a word or phrase, like periods or question marks, should 37 | # stay at the end of that word or phrase. 38 | # - Ex: "Wow!" -> "Owway!", "When did that happen?" -> "Ehenway idday atthay appenhay?" 39 | # - Punctuation that occurs in the middle of a word, like an apostrophe, should stay where it is. 40 | # - Ex: "isn't" -> "isn'tay", "wasn't" -> "asn'tway" 41 | 42 | 43 | import unittest 44 | 45 | from lessons.lesson_5_pig_latin import pig_latin 46 | 47 | 48 | class PigLatinTestCase(unittest.TestCase): 49 | def test_word_starting_with_vowel(self): 50 | word = pig_latin.translate("apple") 51 | self.assertEqual(word, "appleay") 52 | word = pig_latin.translate("am") 53 | self.assertEqual(word, "amay") 54 | 55 | def test_word_starting_with_consonant(self): 56 | word = pig_latin.translate("word") 57 | self.assertEqual(word, "ordway") 58 | word = pig_latin.translate("whiskers") 59 | self.assertEqual(word, "iskerswhay") 60 | 61 | def test_translates_phrases(self): 62 | phrase = pig_latin.translate("number games") 63 | self.assertEqual(phrase, "umbernay amesgay") 64 | phrase = pig_latin.translate("monster offspring") 65 | self.assertEqual(phrase, "onstermay offspringay") 66 | 67 | def test_translates_word_beginning_with_three_consonants(self): 68 | word = pig_latin.translate("three") 69 | self.assertEqual(word, "eethray") 70 | word = pig_latin.translate("shrug") 71 | self.assertEqual(word, "ugshray") 72 | 73 | def test_handles_qu_at_the_beginning_of_a_word(self): 74 | word = pig_latin.translate("quit") 75 | self.assertEqual(word, "itquay") 76 | word = pig_latin.translate("quiet") 77 | self.assertEqual(word, "ietquay") 78 | 79 | def test_handles_qu_after_initial_consonant(self): 80 | word = pig_latin.translate("square") 81 | self.assertEqual(word, "aresquay") 82 | word = pig_latin.translate("squish") 83 | self.assertEqual(word, "ishsquay") 84 | 85 | def test_one_letter_words(self): 86 | word = pig_latin.trainslate("a") 87 | self.assertEqual(word, "aay") 88 | word = pig_latin.translate("i") 89 | self.assertEqual(word, "iay") 90 | 91 | 92 | ################### 93 | ###STRETCH GOALS### 94 | ################### 95 | # To attempt these, remove the "skip" line from the test case 96 | 97 | @unittest.skip 98 | class PigLatinStretchGoalsTestCase(unittest.TestCase): 99 | def test_handles_punctuation(self): 100 | sentence = pig_latin.translate("we had fun!") 101 | self.assertEqual(sentence, "eway adhay unfay!") 102 | sentence = pig_latin.translate("the quick brown fox went home.") 103 | self.assertEqual(sentence, "ethay ickquay ownbray oxfay entway omehay.") 104 | 105 | def test_handles_capitalization(self): 106 | name = pig_latin.translate("Emily") 107 | self.assertEqual(name, "Emilyay") 108 | name = pig_latin.translate("Zelda Fitzgerald") 109 | self.assertEqual(name, "Eldazay Itzgeraldfay") 110 | 111 | def test_handles_apostrophes(self): 112 | sentence = pig_latin.translate("She doesn't understand French.") 113 | self.assertEqual(sentence, "Eshay oesn'tday understanday Enchfray.") 114 | title = pig_latin.translate("It's a Wonderful Life") 115 | self.assertEqual(title, "It'say aay Onderfulway Ifelay") 116 | -------------------------------------------------------------------------------- /tests/test_lesson_6_books.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/tests/test_lesson_6_books.py -------------------------------------------------------------------------------- /tests/test_lesson_7_anagrams.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thejessleigh/test_driven_python/db6f99dfac11b2fc049aaf1d73a1361b6206626c/tests/test_lesson_7_anagrams.py -------------------------------------------------------------------------------- /venv.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Test Driven Python 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 41 | 42 |
43 | 53 | 54 | 64 | 65 |

66 |
67 |
68 |
69 |
70 | 71 |
72 |
73 |
74 |
75 |
76 | 77 | 78 | --------------------------------------------------------------------------------