├── .gitignore ├── Pipfile ├── double-loop-tdd.md ├── screenshots.py ├── pragprog-challenges.md ├── README.md ├── bbig.py ├── tdd-general.md ├── owasp-top10 └── top10.md └── docs └── double-loop-tdd.html /.gitignore: -------------------------------------------------------------------------------- 1 | Pipfile.lock 2 | .cache 3 | *.png 4 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [packages] 7 | mistune = "*" 8 | thttp = "*" 9 | 10 | [dev-packages] 11 | 12 | [requires] 13 | python_version = "3.11" 14 | -------------------------------------------------------------------------------- /double-loop-tdd.md: -------------------------------------------------------------------------------- 1 | Coding Practice 2 | 3 |
4 | (We'll kick off in a couple of minutes) 5 |
6 | 7 | --- 8 | 9 | Double Loop TDD 10 | 11 |
12 | via Samman Coaching 13 |
14 | 15 | --- 16 | 17 | What do you call tests that aren’t unit tests? 18 | 19 | --- 20 | 21 | - End to end tests 22 | - Integration tests 23 | - Acceptance tests 24 | - Snapshot tests 25 | - Fuzzing 26 | - API tests 27 | - Performance tests 28 | 29 | --- 30 | 31 | ![](https://media.brntn.me/postie/b3019fd5.png) 32 | 33 |
34 | via Samman Coaching recreated by Brenton (CC-BY-SA) 35 |
36 | 37 | --- 38 | 39 | Monty Hall Kata 40 | 41 | --- 42 | 43 | What does your outer loop test look like? 44 | 45 | --- 46 | 47 | Let's go write some code! 48 | 49 | --- 50 | 51 | 🙏🏻 Thanks 52 | 53 | 59 | -------------------------------------------------------------------------------- /screenshots.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import subprocess 3 | 4 | 5 | def parse_args(args): 6 | result = { 7 | a.split("=")[0]: int(a.split("=")[1]) 8 | if "=" in a and a.split("=")[1].isnumeric() 9 | else a.split("=")[1] 10 | if "=" in a 11 | else True 12 | for a in args 13 | if "--" in a 14 | } 15 | result["[]"] = [a for a in args if not a.startswith("--")] 16 | return result 17 | 18 | 19 | def usage(): 20 | print("Quickly take screenshots of a presentation using shot-scraper") 21 | print("") 22 | print("Usage:") 23 | print(" python screenshots.py --num-slide=10 ") 24 | 25 | 26 | def take_screenshots(url, num_slides): 27 | for i in range(num_slides + 1): 28 | subprocess.run(["shot-scraper", f"{url}#{i}", "--retina", "--width", "920", "--height", "560"]) 29 | 30 | 31 | if __name__ == "__main__": 32 | args = parse_args(sys.argv[1:]) 33 | base_url_list = args["[]"] 34 | num_slides = args.get("--num-slides") 35 | 36 | if len(base_url_list) != 1 or not num_slides: 37 | usage() 38 | sys.exit(1) 39 | 40 | try: 41 | num_slides = int(num_slides) 42 | url = base_url_list[0] 43 | except: 44 | usage() 45 | sys.exit(1) 46 | 47 | take_screenshots(url, num_slides) 48 | -------------------------------------------------------------------------------- /pragprog-challenges.md: -------------------------------------------------------------------------------- 1 | # Challenge! 2 | 3 | --- 4 | 5 | You are asked "which has the higher bandwidth: a 1 Gbps network connection, or a person walking between two computers with a full 1TB of storage device in their pocket?"


6 | 7 | What constraints will you put on your answer to ensure that the scope of your response is correct? 8 | 9 |
10 | Source: Page 71 of The Pragmatic Programmer (20th Anniversary Edition) 11 |
12 | 13 | --- 14 | 15 | So, which has the higher bandwidth? 16 | 17 | --- 18 | 19 | The storage device contains the information we need to be transferred 20 | 21 | --- 22 | 23 | We know the speed at which the person walks 24 | 25 | --- 26 | 27 | We know the distance between the machines 28 | 29 | --- 30 | 31 | We are not accounting for the time it takes to transfer information to and from the storage device 32 | 33 | --- 34 | 35 | The overhead of storing data is roughly equal to the overhead of sending it over a communication line 36 | 37 | --- 38 | 39 | > Never underestimate the bandwidth of a station wagon full of tapes hurtling down the highway. 40 | 41 | — Andrew S. Tanenbaum 42 | 43 |
44 | https://archive.org/details/computernetworks02tane/page/56/mode/2up 45 |
46 | 47 | 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Build quick presentations from markdown files. 2 | 3 | --- 4 | 5 | Usage: 6 | 7 | ``` 8 | pipenv run python bbig.py 9 | ``` 10 | 11 | Build all markdown files with: 12 | 13 | ``` 14 | pipenv run python bbig.py *.md 15 | ``` 16 | 17 | --- 18 | 19 | Under the hood this really just combines a bunch of bigger utilities: 20 | 21 | - [big](https://github.com/tmcw/big/) to power the presentation 22 | - [mistune](https://mistune.lepture.com/en/latest/) for Markdown parsing 23 | - [highlight.js](https://highlightjs.org/) for syntax highlighting 24 | - [Twemoji](https://twemoji.twitter.com/) to ensure emoji are consistent and scalable 25 | - [thttp](https://github.com/sesh/thttp) as a lightweight HTTP client 26 | 27 | --- 28 | 29 | The Markdown files in this repository are presentations. 30 | 31 | Processed examples: 32 | 33 | - [This README](https://sesh.github.io/quick-presentations/README.html) 34 | - [Lighting TDD Introduction](https://sesh.github.io/quick-presentations/tdd-general.html) 35 | - [Estimation Challenge](https://sesh.github.io/quick-presentations/pragprog-challenges.html) 36 | - [OWASP Top 10](https://sesh.github.io/quick-presentations/owasp-top10/top10.html) 37 | 38 | The `docs` folder is generated locally (i.e. not with Github Actions) and deployed with Github Pages. 39 | 40 | --- 41 | 42 | The `screenshots.py` script is a simple utility to generate screenshots from a hosted presentation: 43 | 44 | ``` 45 | python3 screenshots.py http://localhost:8000/top10.html --num-slides=62 46 | ``` 47 | 48 | --- 49 | 50 | Licence 51 | 52 | - Code: MIT 53 | - Presentations: CC-BY-SA 54 | -------------------------------------------------------------------------------- /bbig.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import mistune 3 | import thttp 4 | import os 5 | from pathlib import Path 6 | import hashlib 7 | 8 | # TODO: 9 | # Single image of a slide gets wrapped with

and breaks layout 10 | # Image slide with footnote 11 | 12 | 13 | SCRIPTS = [ 14 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js", 15 | "https://unpkg.com/twemoji@latest/dist/twemoji.min.js", 16 | "https://raw.githubusercontent.com/sesh/big/master/big.js", 17 | ] 18 | 19 | STYLES = [ 20 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/default.min.css", 21 | "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/tokyo-night-dark.min.css", 22 | "https://raw.githubusercontent.com/sesh/big/master/big.css", 23 | "https://raw.githubusercontent.com/sesh/big/master/themes/light.css", 24 | ] 25 | 26 | 27 | def cached_get(url, enable_cache=True): 28 | m = hashlib.sha256() 29 | m.update(url.encode()) 30 | fn = m.hexdigest() 31 | 32 | cache_dir = Path('.cache') 33 | cache_dir.mkdir(exist_ok=True) 34 | cached_file = cache_dir / fn 35 | 36 | if enable_cache and cached_file.exists(): 37 | with open(cached_file, 'r') as f: 38 | return f.read() 39 | else: 40 | content = thttp.request(url).content.decode() 41 | 42 | with open(cached_file, 'w') as f: 43 | f.write(content) 44 | 45 | return content 46 | 47 | 48 | def parse_args(args): 49 | result = { 50 | a.split("=")[0]: int(a.split("=")[1]) 51 | if "=" in a and a.split("=")[1].isnumeric() 52 | else a.split("=")[1] 53 | if "=" in a 54 | else True 55 | for a in args 56 | if "--" in a 57 | } 58 | result["[]"] = [a for a in args if not a.startswith("--")] 59 | return result 60 | 61 | 62 | def parse_slides(slides_fn): 63 | with open(slides_fn) as f: 64 | text = f.read() 65 | slides = text.split("\n---\n") 66 | return slides 67 | 68 | 69 | def process_slide(slide): 70 | # If the slide is wrapped in

then just return the slide 71 | # the author knows better than us :) 72 | if slide.strip().startswith('')[0].strip() 79 | 80 | body_style = '' 81 | if "')[0].strip() 83 | 84 | slide_html = f"
" + mistune.html(slide).strip() + "
\n" 85 | return slide_html 86 | 87 | 88 | if __name__ == "__main__": 89 | args = parse_args(sys.argv[1:]) 90 | 91 | slides_fns = args.get("[]") 92 | 93 | if not slides_fns: 94 | print("Slide filenames must be provided as arguments") 95 | sys.exit(1) 96 | 97 | for slides_fn in slides_fns: 98 | print(f"Processing {slides_fn}...") 99 | slides = parse_slides(slides_fn) 100 | 101 | html = """ 102 | 103 | 104 | 105 | 106 | 107 | 108 | """ 109 | 110 | for style_url in STYLES: 111 | content = cached_get(style_url) 112 | html += f"\n" 113 | 114 | for script_url in SCRIPTS: 115 | content = cached_get(script_url) 116 | html += f"\n" 117 | 118 | html += """ 119 | 133 | 134 | 135 | """ 136 | 137 | for slide in slides: 138 | html += process_slide(slide) 139 | 140 | html += """""" 144 | html += "\n" 145 | html += "" 146 | 147 | docs = Path("docs") 148 | out_fn = docs / slides_fn.replace(".md", ".html") 149 | Path(os.path.dirname(out_fn)).mkdir(parents=True, exist_ok=True) 150 | 151 | with open(out_fn, "w") as f: 152 | print(f"Saving to {out_fn}...") 153 | f.write(html) 154 | -------------------------------------------------------------------------------- /tdd-general.md: -------------------------------------------------------------------------------- 1 | # Test Driven Development 2 | 3 | --- 4 | 5 | - I'll introduce TDD and some of its guidelines 6 | - We will run through a quick example together 7 | - Complete a TDD Kata in pairs 8 | - We'll have a quick chat about TDD 9 | 10 | --- 11 | 12 | So, what is TDD? 13 | 14 | --- 15 | 16 | > Test-Driven Development (TDD) is a technique for building software that guides software development by writing tests 17 | 18 | _- Martin Fowler_ 19 | 20 |
21 | https://martinfowler.com/bliki/TestDrivenDevelopment.html 22 |
23 | 24 | --- 25 | 26 | 1. Write a failing test 27 | 2. Write code to make it pass 28 | 3. Refactor to ensure clean code 29 | 30 | --- 31 | 32 | 🔴💚🔁 33 | 34 | --- 35 | 36 | Refactor both your code and tests 37 | 38 | --- 39 | 40 | Rules, you say? 41 | 42 | --- 43 | 44 | > ## The Three Laws of TDD 45 | 46 | _- Robert Martin_ 47 | 48 |
49 | http://www.butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd 50 |
51 | 52 | --- 53 | 54 | 1. You are not allowed to write any production code unless it is to make a failing unit test pass. 55 | 56 | --- 57 | 58 | 2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures. 59 | 60 | --- 61 | 62 | 3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test. 63 | 64 | --- 65 | 66 | Generalisation 67 | 68 | --- 69 | 70 | Avoid writing code for the "general" case when you don't have to 71 | 72 | --- 73 | 74 | ```javascript 75 | describe('isPrime', () => { 76 | it('should be prime', () => { 77 | expect(isPrime(3)).toBe(true); 78 | }); 79 | }); 80 | ``` 81 | 82 | --- 83 | 84 | ```javascript 85 | const isPrime = (n) => { 86 | return n == 3; 87 | }; 88 | ``` 89 | 90 | --- 91 | 92 | Triangulation 93 | 94 | --- 95 | 96 | Use a second assertion to drive a safer creation of the generic version 97 | 98 |
99 | https://dmitripavlutin.com/triangulation-test-driven-development/ 100 |
101 | 102 | --- 103 | 104 | ![](https://media.brntn.me/postie/e3dbd8f4.png) 105 | 106 |
107 | Image: CC-BY (Brenton Cleeland) 108 |
109 | 110 | --- 111 | 112 | ```javascript 113 | describe('isPrime', () => { 114 | it('should return true when prime', () => { 115 | expect(isPrime(3)).toBe(true); 116 | expect(isPrime(7)).toBe(true); 117 | }); 118 | }); 119 | ``` 120 | 121 | --- 122 | 123 | ```javascript 124 | const isPrime = (n) => { 125 | for (let i = 2; i <= n/2; i++) { 126 | if (n % i === 0) { 127 | return; 128 | } 129 | } 130 | return true; 131 | }; 132 | ``` 133 | 134 | --- 135 | 136 | Refactor 137 | 138 | --- 139 | 140 | ```javascript 141 | const isPrime = (n) => { 142 | let max = Math.sqrt(n); 143 | for (let i = 2; i <= max; i++) { 144 | if (n % i === 0) { 145 | return; 146 | } 147 | } 148 | return true; 149 | }; 150 | ``` 151 | 152 | --- 153 | 154 | Once you're happy with the code, think about and test negative cases 155 | 156 | --- 157 | 158 | ```javascript 159 | describe('isPrime', () => { 160 | it('should return false when not prime', () => { 161 | expect(isPrime(22)).toBe(false); 162 | }); 163 | }); 164 | ``` 165 | 166 | --- 167 | 168 | ```javascript 169 | const isPrime = (n) => { 170 | let max = Math.sqrt(n); 171 | for (let i = 2; i <= max; i++) { 172 | if (n % i === 0) { 173 | return false; 174 | } 175 | } 176 | return true; 177 | }; 178 | ``` 179 | 180 | --- 181 | 182 | Think about the edge cases 183 | 184 | --- 185 | 186 | ```javascript 187 | describe('isPrime', () => { 188 | it('should return false when passed one', () => { 189 | expect(isPrime(1)).toBe(false); 190 | }); 191 | }); 192 | ``` 193 | 194 | --- 195 | 196 | ```javascript 197 | const isPrime = (n) => { 198 | let max = Math.sqrt(n); 199 | for (let i = 2; i <= max; i++) { 200 | if (n % i === 0) { 201 | return false; 202 | } 203 | } 204 | return n > 1; 205 | }; 206 | ``` 207 | 208 |
209 | https://stackoverflow.com/a/40200710 210 |
211 | 212 | --- 213 | 214 | Awesome-sauce 🍅 215 | 216 | --- 217 | 218 | Let's recap 219 | 220 | --- 221 | 222 | - Write a test 223 | - Write code 224 | - Refactor to make things 👍🏻 225 | 226 | --- 227 | 228 | Only write the code required to make your test pass 229 | 230 | --- 231 | 232 | Think about edge cases 233 | 234 | --- 235 | 236 | TDD is a discipline and takes practice 237 | 238 | --- 239 | 240 | Try using TDD on your next side project 241 | 242 | --- 243 | 244 | Thank you 🙏🏻 245 | 246 | 252 | -------------------------------------------------------------------------------- /owasp-top10/top10.md: -------------------------------------------------------------------------------- 1 | OWASP Top 10 2 | 3 | --- 4 | 5 | A regularly updated list of the *most critical* security risks to web applications 6 | 7 |
8 | https://owasp.org/www-project-top-ten/ 9 |
10 | 11 | --- 12 | 13 | Open Worldwide Application Security Project (OWASP) is a community-led security project. 14 | 15 | The team is made up of security experts from around the world. 16 | 17 |
18 | https://owasp.org/ 19 |
20 | 21 | --- 22 | 23 | # The 2021 Top 10 24 | 25 | 1. Broken Access Control 26 | 2. Cryptographic Failures 27 | 3. Injection 28 | 4. Insecure Design 29 | 5. Security Misconfiguration 30 | 6. Vulnerable and Outdated Components 31 | 7. Identification and Authentication Failures 32 | 8. Software and Data Integrity Failures 33 | 9. Security Logging and Monitoring Failures 34 | 10. Server-Side Request Forgery 35 | 36 | --- 37 | 38 | 39 | 40 | Broken Access Control 41 | 42 | --- 43 | 44 | Any situation where a user can access something that is outside their intended permissions 45 | 46 | --- 47 | 48 | Manipulating URLs, cookies or tokens 49 | 50 | --- 51 | 52 | Missing access controls on unexpected HTTP methods or routes 53 | 54 | --- 55 | 56 | ``` 57 | /api/getCustomerInformation?id=123129112 58 | ``` 59 | 60 | --- 61 | 62 |
63 | Optus logo 64 |
65 | 66 | --- 67 | 68 | 69 | 70 | Cryptographic Failures 71 | 72 | --- 73 | 74 | Bad cryptography that can lead to unexpected data exposure 75 | 76 | --- 77 | 78 | Weak SSL / TLS configurations that allow person in the middle attacks 79 | 80 |
81 | Use Mozilla's SSL Config Generator and Qualys' SSL Labs Server Test. 82 |
83 | 84 | --- 85 | 86 | Missing HTTP Strict Transport Security headers that allow HTTP downgrade attacks 87 | 88 |
89 | SecurityHeaders.io has tests for this and a bunch of others. 90 |
91 | 92 | --- 93 | 94 | The use of default passwords or secrets in applications 95 | 96 |
97 | Default credentials are really easy to come by... 98 |
99 | 100 | --- 101 | 102 | Keys using weak algorithms or low entropy 103 | 104 | Accidental key exposure 105 | 106 | --- 107 | 108 |
109 | 110 |
111 | 112 | 113 | --- 114 | 115 | 116 | 117 | Injection 118 | 119 | --- 120 | 121 | Situations where user supplied data is directly injected into your code 122 | 123 | --- 124 | 125 | Not just SQL! 126 | 127 | LDAP, NoSQL, system commands, and ORMs are all targets. 128 | 129 | --- 130 | 131 |
132 | 133 |
134 | 135 | --- 136 | 137 | 138 | 139 | Insecure Design 140 | 141 | --- 142 | 143 | Design and architectural flaws in your system 144 | 145 | --- 146 | 147 | Promotes secure by design and ensures teams understand the security implications of changes 148 | 149 | --- 150 | 151 |
152 | 153 |
154 | 155 | --- 156 | 157 | 158 | 159 | Security Misconfiguration 160 | 161 | --- 162 | 163 | Applications that are improperly secured because of a misconfiguration 164 | 165 | --- 166 | 167 | Missed steps in go live or hardening documentation 168 | 169 | --- 170 | 171 | Applications deployed to production with development configurations 172 | 173 | --- 174 | 175 | Features enabled that aren't being used increasing the attack surface area 176 | 177 | --- 178 | 179 |
180 | 181 |
182 | 183 | --- 184 | 185 | 186 | 187 | Vulnerable and Outdated Components 188 | 189 | --- 190 | 191 | Applications using components with known security vulnerabilities 192 | 193 | --- 194 | 195 | Both accidental vulnerabilities and malicious changes apply here 196 | 197 | --- 198 | 199 | Supply chain attacks 200 | 201 | --- 202 | 203 |
204 | 205 |
206 | 207 | --- 208 | 209 | 210 | 211 | Identification and Authentication Failures 212 | 213 | --- 214 | 215 | Allowing the wrong user (malicious or not) to authenticate to a system 216 | 217 | --- 218 | 219 | Account enumeration, especially when weak and previously leaked passwords are allowed 220 | 221 | --- 222 | 223 | Misconfiguration of authentication that allows MFA bypass, OAuth scope creep, unverified user registrations, etc.. 224 | 225 | --- 226 | 227 |
228 | 229 |
230 | 231 | --- 232 | 233 | 234 | 235 | Software and Data Integrity Failures 236 | 237 | --- 238 | 239 | Assuming that the build you just tested is the one that will be deployed 240 | 241 | --- 242 | 243 | Using dependencies from third parties without verifying them 244 | 245 | --- 246 | 247 | Allowing untrusted code into your ecosystem 248 | 249 | --- 250 | 251 |
252 | 253 |
254 | 255 | --- 256 | 257 | 258 | 259 | Security Logging and Monitoring Failures 260 | 261 | --- 262 | 263 | Not having enough information to investigate an issue 264 | 265 | --- 266 | 267 | Logging so much information that it becomes toxic waste 268 | 269 |
270 | Haunted By Data at Idle Words, Data is a Toxic Asset by Bruce Schneier. 271 |
272 | 273 | --- 274 | 275 |
276 | 277 |
278 | 279 | --- 280 | 281 | 282 | 283 | 284 | Server-Side Request Forgery 285 | 286 | --- 287 | 288 | Allowing your server to make requests to a resource provided by a user 289 | 290 | --- 291 | 292 | Making requests that reveal metadata about the service 293 | 294 | --- 295 | 296 | Tricking servers into mining cryptocurrencies 297 | 298 | --- 299 | 300 | ```bash 301 | http://google.com:80+&@127.88.23.245:22/#+@google.com:80/ 302 | ``` 303 | 304 |
305 | Source: SSRF-Testing on Github 306 |
307 | --- 308 | 309 | ```bash 310 | http://0/ 311 | ``` 312 | 313 | --- 314 | 315 |
316 | 317 |
318 | 319 | --- 320 | 321 | Wrap Up 322 | 323 | --- 324 | 325 | The OWASP Top 10 is a great guide, but is really only the first 10 of 100s of potential issues 326 | 327 | --- 328 | 329 | Think about security early, during the planning and design phase of new work 330 | 331 | --- 332 | 333 | Resources if you want to learn more 334 | 335 | - [OWASP](https://owasp.org/) ([Top 10](https://owasp.org/www-project-top-ten/), [Cheat Sheet Series](https://cheatsheetseries.owasp.org/), [Testing Guide](https://owasp.org/www-project-web-security-testing-guide/stable/)) 336 | - [Pentester Labs Bootcamp](https://www.pentesterlab.com/bootcamp) 337 | - [PicoCTF](https://www.picoctf.org/) 338 | - [Risky Business](https://risky.biz/) 339 | 340 | --- 341 | 342 | Exposure links 343 | 344 | - [Optus breach details](https://verse.systems/blog/post/2022-09-25-optus-breach/) 345 | - [Github RSA key leak](https://github.blog/2023-03-23-we-updated-our-rsa-ssh-host-key/) 346 | - [Exploits of a Mom](https://xkcd.com/327/) 347 | - [Lastpass Security Incidents](https://en.wikipedia.org/wiki/LastPass#Security_incidents) 348 | - [Salesforce public sites](https://krebsonsecurity.com/2023/04/many-public-salesforce-sites-are-leaking-private-data/) 349 | - [node-ipc updated to support Ukraine](https://www.theregister.com/2022/03/18/protestware_javascript_node_ipc/) 350 | - [Have I Been Pwned?](https://haveibeenpwned.com) 351 | - [Solarwinds](https://www.wired.com/story/russia-solarwinds-hack-roundup/) ([non-paywalled link](https://archive.md/3RizK)) 352 | - [Latitude breach just got much worse](https://ia.acs.org.au/article/2023/latitude-breach-just-got-much-worse.html) 353 | - [Github SSRF Exploit Chain](http://blog.orange.tw/2017/07/how-i-chained-4-vulnerabilities-on.html) 354 | 355 | --- 356 | 357 | 🙏🏻 Thanks! 358 | 359 | 374 | -------------------------------------------------------------------------------- /docs/double-loop-tdd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 18 | 27 | 150 | 161 | 1364 | 1367 | 1588 | 1589 | 1603 | 1604 | 1605 |

Coding Practice

1606 |
1607 | (We'll kick off in a couple of minutes) 1608 |
1609 |

Double Loop TDD

1610 |
1611 | via Samman Coaching 1612 |
1613 |

What do you call tests that aren’t unit tests?

1614 |
    1615 |
  • End to end tests
  • 1616 |
  • Integration tests
  • 1617 |
  • Acceptance tests
  • 1618 |
  • Snapshot tests
  • 1619 |
  • Fuzzing
  • 1620 |
  • API tests
  • 1621 |
  • Performance tests
  • 1622 |
1623 |

1624 |
1625 | via Samman Coaching recreated by Brenton (CC-BY-SA) 1626 |
1627 |

Monty Hall Kata

1628 |

What does your outer loop test look like?

1629 |

Let's go write some code!

1630 |

🙏🏻 Thanks

1631 |
1637 | 1641 | --------------------------------------------------------------------------------