---
title: "Where Do You Start with Legacy Code?"
url: "https://courses.osherove.com/courses/legacy-code-hero/where-do-you-start-with-legacy-code.md"
canonical: "https://courses.osherove.com/courses/legacy-code-hero/where-do-you-start-with-legacy-code/"
tags: []
author: "Roy Osherove"
description: "Lesson 1 of 5 in Legacy Code Hero (Starting Out), 50:03. Free video by Roy Osherove."
usage: "Licensed under CC BY 4.0 — reuse, quote and adapt with attribution (Roy Osherove, link to the original page)."
license: "https://creativecommons.org/licenses/by/4.0/"
---

# Where Do You Start with Legacy Code?

1. [Courses](https://courses.osherove.com/#courses)
2. [Legacy Code Hero](https://courses.osherove.com/courses/legacy-code-hero/)
3. Starting Out

Lesson 1 of 5

Duration: 50:03 [Watch on YouTube](https://www.youtube.com/watch?v=XCS-9_kC6Kw)

In this first part, Roy discusses:

- Where to start with [legacy code](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12 "Working with legacy code (The Art of Unit Testing, 3rd ed., ch. 12)")
- What types of [acceptance tests](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_225 "Writing integration tests before refactoring (The Art of Unit Testing, 3rd ed., ch. 12)") would you run on it if it was a web application
- What technologies and platforms you can use
- The Anatomy of a Continuous Acceptance Test

## In this lesson

Roy Osherove discusses [definitions of legacy code](https://osherove.com/blog/2005/6/5/whats-legacy-code.html "What's legacy code? (osherove.com)") and why dependencies make it hard to change, then sorts classes into [four quadrants](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_223 "Pros and cons of the easy-first strategy (The Art of Unit Testing, 3rd ed., ch. 12)") by their number of dependencies and amount of logic to decide [where to start](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_221 "Where do you start adding tests? (The Art of Unit Testing, 3rd ed., ch. 12)") testing. For most teams he recommends starting with the easy classes. He then describes wrapping a web project in smoke, security, load speed and page weight tests, built with TeamCity and FinalBuilder scripts that run Cucumber with Capybara or PhantomJS.

## Transcript

7,783 words

[\[00:03\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=3s) So, my name is Roy Osherove. Thank you for inviting me. You can find me on Twitter where I post links to interesting things and there's a lot more of what we're going to talk about at arttoofunitesting.com. Now, the next two hours are going to be based on a two-day course that I do called refactoring skills for testdriven development. And we're going to maybe make 50% of the materials. We have two hours for that and I hope that we can make them in a understandable

[\[00:35\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=35s) And maintain maintainable way something that you will you will actually be able to use. So that's why I'm not going over all the material because it has to be pragmatic. The agenda we're going to have is at the beginning we're going to talk about which [legacy code](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-10 "Working with legacy code (The Art of Unit Testing, 2nd ed., ch. 10)") do we want to test at all. How many people here work with legacy code? Okay. What is legacy code? Can anyone tell me exactly what? Nobody. Well, I'm sure you all have an idea of what legacy code is.

[\[01:06\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=66s) So, I would love to hear it. This is a conversation, not a lecture. Yes. >> Code that my colleagues g >> code that you didn't write. >> What about code that you wrote six months ago and you have to maintain? Is that legacy code? >> Is that legacy code too? Okay. Anything else? So, why do you think you're working on legacy code? Okay. What's not legacy code? Maybe this will help. What's not legacy code? >> Fresh code. >> Sorry. >> Fresh code. So like two minutes ago. >> Yeah.

[\[01:36\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=96s) >> So tomorrow it will be legacy code >> somehow. >> Interesting. What if what if it has tests tomorrow like you write it today with tests and you have to come back to it tomorrow. Is that still legacy code? So there are several [definitions of legacy code](https://osherove.com/blog/2005/6/5/whats-legacy-code.html "What's legacy code? (osherove.com)"). Too late sir.

[\[02:08\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=128s) >> Oh interesting code that you learn something since you wrote it. Interesting. I didn't think about it like that. Maybe. Maybe. What about code that you hate? Maybe that's the definition you're looking for. So there are several definitions that are accepted. [Michael Feathers](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_226 "Read Michael Feathers’ book on legacy code (The Art of Unit Testing, 3rd ed., ch. 12)") started with the definition of legacy code as code that does not have tests. I kind of agree with this definition, but now we have he actually recanted some of this definition later and said that what he thinks legacy code is is a code that is

[\[02:45\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=165s) That you are afraid to maintain. So you might have test and you're still afraid to maintain it because the tests are very brittle etc. So if I'm not mistaken that was his definition but I I'll have to look it up. But a colleague of mine said code legacy code is code that works don't touch it. And that's a lot of people view it this way. Here's some code don't touch it. It works. Therefore we call it legacy code. It's something that we are afraid to touch. We're not supposed to touch.

[\[03:16\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=196s) This whole talk is what happens when you have legacy code and you have to deal with it and how do we test it without touching it and how do we approach touching it at all. So we're going to start by talking about which legacy code do we want to test at all? What do we what do we look for? Because obviously we have a lot of code. We have to start somewhere. We even test all of it maybe not. Then we'll talk about the fact that we wrap up the code with [acceptance tests](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-10#sigil_toc_id_178 "E2E/UI isolated tests (The Art of Unit Testing, 3rd ed., ch. 10)") before we can actually change it because if we don't have any tests and we start refactoring the code,

[\[03:53\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=233s) Life begins to suck even more. Okay, so we cannot change code that is not tested in some way and integration and acceptance testing is easier than [unit testing](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-1#sigil_toc_id_17 "Defining unit testing, step by step (The Art of Unit Testing, 3rd ed., ch. 1)") on existing legacy code. And then we'll talk about refactoring the hell out of that code. Once you have [integration tests](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-1#sigil_toc_id_25 "Integration tests (The Art of Unit Testing, 3rd ed., ch. 1)"), acceptance tests, then you have some confidence and you can actually change it. So we'll talk about patterns for refactoring the code and we we should have a break in the middle at some point.

[\[04:25\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=265s) So let's talk about legacy code problems. Legacy code, the biggest problem legacy code is dependencies. Everything you try to do in legacy code, you want to change it and something breaks. A lot of times you can't even create instances of classes either because they are derived from other classes or because they use other classes that have a lot of dependencies. For example, you might need a lot of configuration to run a test because it uses a database or because a test uses an email

[\[04:56\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=296s) Let's say an exchange server and you want to test the system. Now you need an Exchange server running or some sort of something like that. And if you want to have a [fake](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-4#ch04lev1sec2 "The difference between mocks and stubs (The Art of Unit Testing, 2nd ed., ch. 4)") exchange store somewhere, that's also very difficult. So it's not that it's impossible to fake things in that system, it's just very very very difficult and it takes a long time. And so the biggest problem with legacy code is that everything takes very very long to do. And when code is testable and code is welld designigned, usually it's it lends itself to testing and to changing very easily because of the way it's designed.

[\[05:37\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=337s) So that means that when we look at code, we're afraid that what we're going to do, we're going to come across, we're afraid of even touching a single line of code of whatever monster is going to jump at us when we actually compile. And that's the biggest worry is that we are afraid of legacy code. We are afraid and we look at it just like we look at this spider. Can I change this method? No. I'll just copy this method, change the copy and call the copy of the method. Problem solved. And this is how code begets new code

[\[06:09\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=369s) And code actually has children. So we're going to try to answer some of the following questions. Should I be testing this specific piece of code? How much value do I get by testing a specific piece of code? How much cost is there to test a specific piece of code? And to understand that I want you to think about code or your classes in two value

[\[06:41\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=401s) In two value types. Here we can see dependencies on the left and logic at at the bottom. Dependencies is the amount of different classes that your class uses. For example, if your class uses a logger and an email sender, it has two dependencies. Okay? If another class uses those two plus two more classes, then it has four dependencies. So, classes with more dependencies are going to be easier or harder or harder to test.

[\[07:13\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=433s) What do you think? >> Harder. obviously harder to test because you need to either fake more dependencies to test those classes or you need to change the design or maybe those dependencies are hardcoded. So it's going to be harder. So we measure the amount of dependencies per class. We also measure the amount of logic or what do you call that? The complexity there's like this weird name that I keep forgetting. Cyclomatic complexity.

[\[07:43\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=463s) Cyclomatic. I always think about the I like a cyclop cyclomatic complexity for a code. So the more cyclomatic complexity or the more logic your code has, the more value you can gain from testing that code. So we can actually have [four quadrants](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_222 "Choosing a selection strategy (The Art of Unit Testing, 3rd ed., ch. 12)") where we can put our classes in. If we look at a specific assembly, we say there are classes that might have very little dependencies and very little logic. Do you have classes like that in your code?

[\[08:15\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=495s) >> What what are what for example would those classes do? >> Database layer. >> Database layer forwarding mechanisms. They just do something and move it on for example facads or those things. What about classes with a lot of logic but very little dependencies? Do you have code like that? validations, algorithms, utility methods. A lot of times you'll have them in some I'm going to say manager classes

[\[08:48\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=528s) Where you just have a lot of static methods. I'm not saying that's a good design. I'm just saying that's usually what happens. By the way, when you when you talk about design, don't feel bad. A lot of the companies that I go to see and visit claim that their code really sucks. their code is really disgusting no matter what I've seen before. And the truth is that most of the companies that used to be successful have really really disgusting code.

[\[09:19\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=559s) Okay. So it could be that the fact that you have disgusting code is because at the beginning the product was successful or some other product in the company was successful. You didn't have time to work on it. You just had to run with it and now you're paying all the technical debt. So this ugly code is not necessarily a bad thing. It's just a necess necessity of reality sometimes. But technical debt has to be paid which is why we're doing this. So we have code with a lot of logic but no dependencies.

[\[09:50\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=590s) Code with no logic and no dependencies. What about code with a lot of dependencies and no logic? >> You have such code. H >> dispatchers. dispatchers, composable objects. Would it make sense to test code like that? What do you think? We definitely don't want to test code that doesn't have logic. That will bring the least value. It's also the code least problematic. Usually, the code with with logic is the code with problems.

[\[10:23\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=623s) So, the code with a lot of logic and a lot of dependencies. Do you have code like that? Yes. These are called in a lot of places God classes because nobody knows why they do what they do. They do a lot and if you do something you get punished. So it would make sense that you have only two clear paths of [where to start](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-10#ch10lev1sec1 "Where do you start adding tests? (The Art of Unit Testing, 2nd ed., ch. 10)") testing. You can either test things that have a lot of dependencies and a lot of logic in which case we can say that the time to write the first few tests for those components is going to be very very high.

[\[11:04\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=664s) So it's going to take you a few good days to write the first test for one of those classes. But then after you write the first few tests, maybe two or three or four, the next few tests are going to be significant significantly easier because your design will have changed to the point where it's more testable and easier to test. But there is a big initial upfront investment in trying to test these things. So I call this dependencybound strategy is because you're looking for the things with a lot of dependencies because you're trying to

[\[11:40\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=700s) You know start with the big stuff and kill the big stuff first so that you're only left with the easy stuff later. And logic bound is when you try to tackle the simple stuff first and leave the difficult stuff for later. So you only try to test classes that have a lot of logic but not necessarily a lot of dependencies. maybe one or two. And this the time curve to write those tests is much much simpler. It's much easier to write a test at the beginning, but as time goes by, it's going to be harder and harder to write your tests as you achieve as you go through to new components.

[\[12:18\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=738s) Okay, so these are the two basic strategies. We haven't talked about priority yet, but obviously out of all those components based on what you chose. You will only test those things that are in high priority that you are actively working on, those things that are actually part of active development today or those things that create a lot of problems for you today. If something works amazingly well and it has been living there for three years, but you have something just as complex that works really badly,

[\[12:53\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=773s) You probably want to test the one that works badly first. So now the question is, what do you think is what do you think is right for you guys? Would you want to start with the difficult stuff first or the easy stuff first? I can I can play more gifts until you stop thinking. >> Easy stuff first. >> The easy stuff first.

[\[13:23\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=803s) Why? >> Yeah, that's a good reason. So, you you start with the easy stuff and you count on the fact that you might learn something during the way so that when you get to the hard stuff, it's going to be easier to test. So that's actually a very good strategy if you don't have a lot of experience with testing and unit testing. So now the big question is how much experience do you guys have with unit testing and testing in particular? How many people here have been writing unit tests in the past year?

[\[13:58\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=838s) Okay, that's pretty good. How many projects do we have here by the way? >> Many different >> more than 10. Definitely >> more than 50. >> Okay. So now if we have a lot of different projects the it really is important to know how many tests each product each project actually has and the reason is is based on the amount of tests that you have. Maybe you wrote a single test for each one of your 50 50 projects

[\[14:32\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=872s) Or you have hundreds of tests for projects. Now based on that, based on how much experience and how much confidence you feel in writing unit tests, you might want to start with actually the difficult stuff first because the difficult stuff first is is something that a mature team, someone that has a lot of experience might tackle to change the design of the system. to start with the difficult things first will mean that the easy things will come later as the project progresses. As time constraints become thinner and thinner, you're going to have less stress and less stress.

[\[15:08\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=908s) If you start with the easy stuff first, as the project progresses and time constraints become more problematic, you're going to be more stressed because you're going to get to the tough parts. However, for most teams, what I recommend is to start with the easy stuff. And there are several reasons. One of them is political. If the organization is just trying out their feet in testing and unit testing or [TDD](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-1#sigil_toc_id_27 "Test-driven development (The Art of Unit Testing, 3rd ed., ch. 1)"), they're not sure whether it's a good investment.

[\[15:39\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=939s) You start with the easy stuff and you show results quickly and that gives your managers the backing that they need that they're doing the right thing. If you start with the hard stuff, it's going to take a few good months to show good progress. And that only works for a company that already knows that the value of unit test and and automated testing is a big one. And most companies are not aware of that. So as a risk factor, I would say start with easy stuff and you always have developers that have less knowledge than you or have less experience.

[\[16:16\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=976s) Let them pair with you as you write the new tests on the easy stuff so that they're ready when the hard stuff comes in. And then you can choose not to do it if time pressure is looming. But at least you have tests for some of those things. Now there are several tools that I used to use to to check how much [testability](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-11#ch11lev1sec1 "Why should I care about testability in my design? (The Art of Unit Testing, 2nd ed., ch. 11)") or dependencies classes have. I wrote a cool a tool called depender.

[\[16:46\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1006s) This was a long time ago that would look at a class or all the classes in in an assembly and look for testability problems like static methods etc. So you can look for that. It's open source. There's also a commercial tool called Ndepend. Do you guys use it? Have you heard of it? Some people have. Ndepend is a really powerful tool. What I like about it is that it allows you to do a visual dissection of your assembly and you can actually do queries on it like SQL.

[\[17:20\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1040s) It has a language called CQL component query language. So for example, you can select all the components that have methods that use other components and then count them and aggregate them by the amount and then sort them. And that will give you something that will fit this chart. So you can actually write a query that will create something close to this although not visually. But then you can put it in Excel and create this chart from that.

[\[17:52\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1072s) So although this tool costs money, I believe that it actually might do a lot of good. So I would definitely look at this tool to try to understand just how many dependency problems and how much cyclomatic complexity problems we have because it also knows how to measure cyclomatic complexity and a lot of other different things. So now that we have chosen maybe a few components, now we talk about myth

[\[18:25\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1105s) Myths and and the myth is that your test should not change production code. A lot of people are afraid of changing production code and legacy code because of unit tests. But I've noticed that when I let the te the tests change the design of the production code, I get a better design. Not necessarily good design, but better than what I had. I get better readability into the code. And that's important also in terms of the maintainability later on.

[\[18:59\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1139s) If you can't read the code, you cannot maintain it. So I think it's okay for tests to change the production code. For example, you want to test something, but it uses a static method, so you turn it into an interface or an instance method. For me, that doesn't actually create a problem. You want to turn a private method into a public method. I have no problem with that. Now, a lot of people do have a problem because in terms of design or component or encapsulation, this breaks what a lot of people consider object-oriented design.

[\[19:32\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1172s) But testable object-oriented design is what you would call almost differing and sometimes clashing method of design. And if you think about it, there are schools of thought in terms of the in terms of at least in the software universe where there are people who believe that there are principles of solid design that are not necessarily the same as what you've been taught in terms of object-oriented design. And some of those principles were articulated by Uncle Bob Martin

[\[20:07\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1207s) Who wrote them and he called them the [solid principles](https://osherove.com/blog/2013/3/6/refactoring-string-calculator-with-solid-principles-in-mind.html "Refactoring String Calculator with SOLID Principles in Mind - Examples Needed (osherove.com)") and I teach those fully there's a whole day just about this in the class but we're going to talk about them in the context of the [refactorings](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-3#ch03lev1sec5 "Variations on refactoring techniques (The Art of Unit Testing, 2nd ed., ch. 3)") I'm not going to cover them just as a systematic because this is not a design class this is more of a refactoring talk. So if we want to start testing our code, the first thing we have to do is to write some integration tests.

[\[20:39\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1239s) Once we have integration tests, we can refactor our code into more testability and then we can write unit tests. But one of the things that I'll tell you is you might not need to write unit tests. That might sound weird from someone who wrote a book about unit testing. But if I can avoid writing unit tests and only have integration or acceptance tests and it all runs fast, I don't have a problem with that. And that's important is that you have to be pragmatic. There is no one way to do something.

[\[21:10\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1270s) You don't have to be religious about the techniques that you learn. What you do is that you find what works for you and you take and you pick and choose from the things that you do. And if it doesn't work, you change it. And one of the things I'll talk about is that in the last few projects that I did which were web related projects, how many of the projects that you've worked on are web related? How many people here have worked on web related projects? About 52 and a half%.

[\[21:42\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1302s) So I'm going to talk about the web rellated stuff and if you have questions about non-web related stuff, feel free to ask. But in the last two projects that I did which were web related I had less than 50 unit tests together in those two projects and all the rest of the tests were acceptance and integration tests. So now I'm going to talk about how I wrap a system with acceptance tests so that I can actually test it in or refactor it if I want to.

[\[22:17\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1337s) So the first thing you have to understand about acceptance testing especially on legacy code is that it's not going to be necessarily easy because you have to try to automate the system from the outside. Now for web testing there are good tools for non-web testing there are no good tools for automating for example a desktop application and that's where the alcohol comes in. Here are the platforms I used on my last project which is an ASP.NET MVC project running on EPiServer.

[\[22:52\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1372s) So we used Java to run TeamCity for [continuous integration](https://pipelinedriven.org/article/2015/5/15/time-to-main-measuring-continuous-integration "Time to Main - Measuring Continuous Integration (pipelinedriven.org)"). We use tools from CIS internals to test and deploy things. We used a tool called FinalBuilder which was written in Deli that is basically a replacement for things like MS build or ant or team system build. We used Ruby to write the acceptance tests in a tool called Cucumber. And I'll show some examples.

[\[23:24\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1404s) And Cucumber is a tool that allows us to write the acceptance tests in a way that's a lot of people call behavior driven. So that we can write them almost in clear text and then we can automate the steps behind. And I and we automated the steps using a tool called Capybara which is like selenium if you know selenium only in ruby and better. And we used nodejs which is yet another technology to run a lot of tests with no browser.

[\[23:57\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1437s) So we had r browser tests running where you could actually see the browser running and things being clicked on and things like that. That was using Ruby and Firefox and we used Node.js JS to run u browserless tests or zombie tests. I don't know what people call them. But the fact is that we could run tests including running JavaScript on the browsers with no browser running. And that means you can run it at a command line in a server that doesn't have a UI.

[\[24:27\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1467s) And that's important. So we use a tool called Phantom.js JS to run which is which is the tool that can automate browsers with no UI on node.js and we used two other tools underneath called wislow and load speed. So when I explained this platform to the people on my team this is basically the look that I got and I'm seeing that look here right now as well. >> Yeah sure

[\[24:59\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1499s) This is how we test. Okay, but not to worry, it actually is ending up pretty simple. You just have to know there are a lot of pieces that go together. That does make things a bit more complex. But I f I find that the opposite of having a single tool, let's say team system. It does everything but marginally okay is actually worse because you miss out on a lot of a lot of things that you want to test and actually the maintainability over time is worse.

[\[25:36\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1536s) So you actually lose more money by not using best of breed tools like we were we did in these projects. So what types of tests did we actually have? We wanted to automate at the very least. We wanted to know that things were working, right? You have an existing system. You want to know you didn't break it before you start refactoring. So at the very least, we did smoke tests. Okay? Because if we didn't do smoke tests, we could get into a lot of crap when we change the code. Now, you could Yeah.

[\[26:08\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1568s) At the beginning when I was learning how to use all these tools, this is what what it felt like. But at the beginning, we we we started with smoke tests. Okay? And smoke test a lot of the way I define smoke test is that you run something and you make sure you don't get smoke out of the machine. Okay? And for us, it meant that you run the website and you see that the website actually loads. The main page loads and you click this link, it goes to that page. and you look at the page and you see at least the header tag something loads so it didn't break you see no license errors if you're using episerver for example

[\[26:43\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1603s) So those were basic smoke tests that we used just to make sure that we didn't break any configuration stuff because for example we did a lot of work with configuration and caching and all these things so we wanted to start changing all these things without being afraid because testing all that stuff manually was really really bad So there's a saying, if you hate it, automate it. Okay? And we hated doing the manual testing of those web pages and it took a long time. So we automated it. We did simple security tests.

[\[27:15\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1635s) For example, we wanted to make sure that when you logged into something, it actually logs in. When you log in with the wrong password, you get a you get a failed page. And this is important because we had a continuous integration and deployment going on. So we wanted to make sure that when we changed all the configuration and we changed security based code, we didn't break our pre-production servers for example, you would still not be able to log in with the wrong user and password. You would still be able to log in with the right user and password.

[\[27:45\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1665s) So we just did these automated tests that logged in through the login forms and made sure you got to see the editing page in Episerver. And when you made the wrong password, you got to see a this password was wrong page, etc. One of the requirements we had is that as we change the code, we don't make it slower because a lot of times that's what a lot of people fear is that when you change the code, what you get is more code that runs slowly.

[\[28:20\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1700s) And usually this is not a non- problem a non-issue because really what happens is that you change the code and all the issues are in database related stuff and IO related stuff and has nothing to do with the virtual methods calling each other but it was a governmental project. So what we did is we had to make sure that we live up to at least no more than 1 second per page load. So we had these load speed tests making sure all the pages loaded in less than one second

[\[28:57\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1737s) And of course we had weight tests. This is another requirement is that when you make you when you refactor etc you want to make sure that the pages don't go over a specific size and in this project we had for example no more than 250k. So we automated using PhantomJS and node.js JS to to measure how much a page weighs. So we had the same tests basically the same set of tests for all the pages in the system. And all we had to do was run the same set of scripts with a different URL as a parameter.

[\[29:32\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1772s) So each type of tests has a simple requirement and was written with a different tool. As you can see each one gets its own face as well. I feel that one of the things that happened is that we were so busy writing all these integration tests at some point you know two months into the project someone said we don't have any unit tests and everyone was okay with that I was okay with that nobody understood why I was okay with that because you know as a consultant that teaches unit testing telling people not to do unit testing is kind of counterproductive you know it's a bit of a

[\[30:10\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1810s) But there is no point in telling people to do something that they don't need to do. So if you if we got all these tests and after two months the test were fast enough where everything was just okay and we could change the code in a way that we weren't afraid to change it and it took us maybe five minutes to deploy to pre-production and to run all the tests. Maybe that's not a bad thing. So now this was a governmental project. So of course the project manager said, "Nope, you have to have at least

[\[30:43\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1843s) 50% [code coverage](https://osherove.com/blog/2005/4/3/test-coverage-and-testing-angles.html "Test Coverage and testing angles (osherove.com)") on the unit tests." Okay, but that was like a policy thing. It has it had nothing to do with the quality. So when I talked to the to the test director there and I showed the test that we had, this policy was actually changed. So the lessons that I learned in the last few projects were that not necessarily you want to start with unit tests.

[\[31:15\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1875s) Unit tests should be part of filling in a bigger solution. You start with integration tests and acceptance tests and then you have a framework. You have a framework that matches the current expectation from the product that you have. Now you can start adding new tests, but these tests will actually fail. And this is test-driven development just as much as unit test is. So if you have a test that expects a page that doesn't yet load and then you write the page with everything that's needed until the test passes,

[\[31:52\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1912s) That's test-driven development just as much. The cycles are bigger. So what we would do is that we would write these acceptance tests waiting for pages that don't exist yet. And then we would make the page load and we would write maybe one or two unit tests if there was a lot of logic in the page extracted into a different class. But initially we would measure ourselves based on how many acceptance tests were passing. So we would only deliver to production when the acceptance test for that new page would actually pass.

[\[32:28\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1948s) And it this this actually worked really really well. in terms of integration in terms of the system that we have. This is important. All these tests are really really nice but they don't mean anything if you are the only one running them. So the first step in doing something with legacy code is to set up an [automated build](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-7#ch07lev1sec1 "Automated builds running automated tests (The Art of Unit Testing, 2nd ed., ch. 7)") system that continuously runs the tests.

[\[32:59\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=1979s) I don't know do you guys have such a system? Yeah. Who has such a system? Okay. What what CI servers are you using? Can I >> team system? Anything else? >> Using FinalBuilder behind >> team team calling FinalBuilder. Anything else? Anyone tried anything else than team system for continuous integration ever? Okay, try it.

[\[33:31\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2011s) Seriously, your job as developers is to try new things and learn new things. If you just get fed up, you know, if you get a silver spoon in your mouth by any corporation telling you to use only this tool, suspect something. Okay? So, experiment and learn that there are different tools on the market with different abilities. Now, I've successfully used Team System calling tools like FinalBuilder, but I will never ever go back to editing XML code ever again.

[\[34:06\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2046s) So, if you find yourself having a continuous integration system and you have to edit XML files to make it work, like unloading your project file and editing the MS build stuff, you're doing it wrong. Okay? Don't edit XML. Look for a tool that will prevent you from editing XML. Tools like finer builder, etc. will do the work. I'm not getting paid by the guys at FinalBuilder. I'm just a happy customer. There are other tools like that.

[\[34:36\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2076s) So, at the top, you can't read it, but it says TeamCity. And I've been using TeamCity for the past maybe five years. And we've been using the TeamCity as what you would use team system for which is basically to run the automated build system. But for source control we would use maybe team system itself or GitHub or or subversion but we could pick and choose. The TeamCity is really good at managing the projects and it has a free edition for up to 20 projects that you can run and it's really really good really maintainable and readable.

[\[35:12\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2112s) And what we do is that we we separate the requirements of what each component does in this diagram. So the top is TeamCity and TeamCity has a specific role to play. That role is the knowledge of the system. So TeamCity knows about passwords to FTPs. It knows about the names of servers. It knows about the location of where the servers are. but it has no idea

[\[35:43\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2143s) About the structure of the source code. Okay, the source code builds itself. So the the the continuous the build scripts themselves are actually part of source code. They're FinalBuilder scripts. And if you've never seen FinalBuilder, I'll try to show an image next. But imagine it's just an IDE where you can hierarchically let me see if I actually have a FinalBuilder image here.

[\[36:15\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2175s) Let me see if I have that. Okay. So this is an example of a a FinalBuilder project. But let me see if I can open something a bit more interesting. >> You're all using it. >> Oh, forget I said anything. But for the video,

[\[36:46\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2206s) For the people in the video who might not be using it, this is for you guys. Remember that I fought for you. Okay. Anyway, so there are hundreds of built-in tasks and, it's a highly recommended tool there. Okay, that's all I needed. Shoot me. Okay, so we're using Teamct and Teamct gets the latest version of the source control. In the source control, we have FinalBuilder scripts.

[\[37:18\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2238s) Those FinalBuilder scripts have the knowledge about the structure of the source code. And because they're in version control, they have history and versioning built in. So if I wanted to build the project from three months ago, I would just get the latest version from three months ago and invoke the build script which three months ago fit exactly the structure of the code from three months ago because it is from three months ago. So this allows me to go back and forth in terms of versions

[\[37:53\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2273s) And I pass in variables from TeamCity from here so that the the code doesn't care about the structure of the environment and this is very important. So I don't hardcode anything in FinalBuilder except the the relative path in the source for example the location of where to upload things the passwords and all that stuff. But then FinalBuilder would invoke Ruby or NodeJS and then run cucumber with Capybara or PhantomJS to test load and speed and weight of pages.

[\[38:26\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2306s) So we had this really really big chain of responsibility but once you have that chain in place it's very easy to just change the entry points. So you want to add a new action and a new build configuration. You add one in TeamCity. You add a new FinalBuilder script at the root of the source code. You make it invoke it and then everything happens in the FinalBuilder script so that all the complicated stuff is very maintainable and very readable.

[\[39:01\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2341s) Now we we used cucumber for for customer acceptance tests. Who's used cucumber? Who knows what who heard of cucumber? Okay, cucumber is a really really funny name and in the in the non-Microsoft culture, it's really customary to give really cool cool and funny names to tools. So if you have cucumber and you have capiara which by the way is a type of animal. Cucumber is a type of fruit and vegetable

[\[39:33\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2373s) Veget and the point is that regardless of the names of these things they're really really helpful. Okay? So disregard the names. Don't worry about it. Now this is really the text of a cucumber feature file. So this is something dot feature and it really looks like this. This is opened in any text editor. This case you see it in Vim but it can be in notepad or sublime text or anything that you want or visual studio of course.

[\[40:09\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2409s) And we have these things called features. We can write backgrounds. We can write scenarios. The scenarios are always written in a specific way. given when then and we write them in a simple language and then we translate them. We make them work by writing in the background Ruby files. So you need to know a bit of Ruby to make these things work. However, very very little and Ruby is very easy to learn.

[\[40:42\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2442s) Here's another example of a feature file. And this is important because the feature names are readable and it's very easy to add more features and more scenarios and actually show this to a customer. So in my government project, I had a product owner who wanted for example for us to automate automatically test that when you check some boxes then some fields are disabled when you check this box some fields are enabled etc.

[\[41:15\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2475s) So there were a lot of these requirements. So we ended up I end up saying look if I could give you this file that looks like this. When I check this then this is enabled. When I check this then that is enabled. Could you write more of these lines for me? He said awesome. Yes. That way I don't have to just come and translate to you what's in my head. And so just this one part our product owner did and he ended up with a really big feature file.

[\[41:47\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2507s) But because we automated the steps, he basically just kept changing the parameters. The structure was the same. This is an example of what it's like. So if you see given the file blah blah blah, you can see given and then there is these slashes here. This in Ruby means that this is a regular expression. So match something. So given no configuration file means do the following that's in here. For example, remove

[\[42:18\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2538s) Recursively all the files in a specific location or given a YAML configuration file or given a Ruby configuration file then I want you to open this config file or open this config file. So basically we're saying when the customer expects something like a number or a string that looks like this under the hood we actually accomplish a a physical task. Now it doesn't have to be creating files. We used copy which is basically saying

[\[42:49\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2569s) Given main page on the website then in the given we would have visit and the URL for the main page. This is what it takes to use copy bar to visit the web page just the word visit and the URL and then we would have click to click specific things. So you can write whatever you want inside those given when then implementations but once you do you can reuse them and just change the parameters

[\[43:20\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2600s) And that's the important part is that we can use this thing to create language or domain specific test language or domain specific requirement language that we can then use and parameterize and then automate. And for us it worked really nicely. But we didn't use it for all the tests. Some tests we wanted to do with PhantomJS. And I my next step would be to use these implementations to also invoke NodeJS and PhantomJS

[\[43:55\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2635s) So that all our tests would be in cucumber. So that I wouldn't I always have start with Ruby. Maybe Ruby will invoke Node.js JS later, but I don't care because then I can have all the tests in the same config in the same way. Right now I have tests for Noode.js directly in FinalBuilder and all the test with Ruby in cucumber. But that was a mistake. Today I understand that I can just write the whole thing in cucumber and then automate Node.js inside the implementations instead of automating the the browser.

[\[44:26\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2666s) If there are any questions just let me know. This is what TeamCity looks like with our actual projects. And look at all these build types. Okay. And all these build configurations we actually gave them in the name. We gave them a number so that the ordering would be correct because the ordering is the exact ordering what you would expect in in the life cycle of the code.

[\[44:56\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2696s) So code starts out with a compilation step and then it gets deployed locally on the [build agent](https://osherove.com/blog/2010/1/14/the-difference-between-continuous-integration-tools-and-auto.html "The difference between continuous Integration tools and automated build tools (osherove.com)"). Then we have cucumber and HTML tests running on the build agent. Then it automatically also deploys to dev test and running the same tests. Then we have metrics running speed and weight and all these things happen without clicking anything. Okay. So once this happens, this happens automatically when someone compiles something and the rest automatically gets triggered when these projects finish.

[\[45:31\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2731s) So so I have a trigger here that says when this build is finished successfully, get all the artifacts from this build. So don't even use source control. Use the exact binaries that were compiled by this build. copy them locally to this build configuration and then deploy those binaries that literally the same binaries that passed the test on CI or that get compiled and run tests and deploy them and then this will run the tests.

[\[46:03\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2763s) This will get deployed with the exact same binaries from the artifacts here. So we can say that the tests here on this environment are in the code that has been tested here and has been deployed here. And so all of these things from the top to the bottom all use the same artifacts. So when something is in pre-production, I can say it is exactly the same binaries that we started with. It is not a different binary that got compiled.

[\[46:33\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2793s) It is the exact binary that has been tested and went through all the stages. That's a very important thing because a lot of times in our in in builds an antiattern that I've also done is that for each build step I would recompile again and run all the tests from the previous builds because I wanted to know that this step is more important than the others. So I wanted to make sure it also passes all the others. So each build became longer and longer and longer. But there are better ways.

[\[47:03\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2823s) And one way is to use artifacts, which by the way, it's really hard to do, if not impossible to do in a team system build. Maybe it is in 2013, but I'm not sure. This is really easy to do to have these artifacts shared by all the builds here so that I know that specific binary has passed everything before getting deployed to pre-production. But all of them do only one thing because it is already been done on the same binary.

[\[47:33\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2853s) So I don't need to compile here. I don't need to run the test here. I just need to deploy. So it's maybe 30 seconds. So this whole thing can take maybe 10 minutes to go through the entire line. 10 minutes instead of five minutes. And then the next one is 6 minutes. So together that's 11. The next one does everything the rest does. So that's more and more and more.

[\[48:04\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2884s) So it becomes hours. This is what the office looks like. Basically we have these panels. We have these two panels because we have developers sitting on both sides of this of the office. And we have we are using a an open source project called build screen that connects to TeamCity and shows the statuses. So here you can see there are two builds running. These are two screens on the same machine because developers on both sides needs to see what's going on at all times.

[\[48:36\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2916s) This simple thing gave us such a nice confidence boost and made everyone more conscious of the fact that they are actually part of something that keeps happening. Instead of a developer that just sits there at the computer not knowing when their code will be used and what it's going to be used for, they can check in and they can see the effect that it has on the chain on in the build system and they feel like they're actually changing something. And that's is important because they are

[\[49:07\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2947s) This is an example what happens when something fails. So you see deployment to pre-production failed because some guy from a different company because we have an SLA with that company. So he messed around with manually deploying things and suddenly the pre-production servers were not running. And what else? So, so, so there are there are a lot more things that I could just talk about in on those builds. I'm actually writing a book called [beautiful builds](https://osherove.com/videos/2013/5/17/video-beautiful-builds.html "Video: Beautiful Builds (osherove.com)") on this which is just about the patterns that we used in the last 10 years that I've been working on on on build related patterns.

[\[49:51\]](https://www.youtube.com/watch?v=XCS-9_kC6Kw&t=2991s) So we wrap the project with integration tests and then we write test first and we write test first accept this test first and then we fill in with unit test as I said before.

## Related

- [Where do you start adding tests?](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-10#ch10lev1sec1) · The Art of Unit Testing, 2nd ed., ch. 10

    The 2nd edition's section on where to start adding tests, ranking components by logic and dependencies as the quadrants do.

- [Pros and cons of the easy-first strategy](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_223) · The Art of Unit Testing, 3rd ed., ch. 12

    The 3rd edition's pros and cons of the easy-first strategy, the starting point Roy recommends for most teams.

- [What's legacy code?](https://osherove.com/blog/2005/6/5/whats-legacy-code.html) · osherove.com

    Roy's post on what 'legacy code' really means, matching the definitions this lesson opens with.

- [E2E/UI system tests](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-10) · The Art of Unit Testing, 3rd ed., ch. 10

    The 3rd edition's section on end-to-end system tests, like the smoke and acceptance tests wrapped around the web project.

### Also linked in this lesson

#### The Art of Unit Testing, 3rd ed.

- [Where do you start adding tests?](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_221), ch. 12
- [Working with legacy code](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12), ch. 12
- [Writing integration tests before refactoring](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_225), ch. 12
- [Read Michael Feathers’ book on legacy code](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_226), ch. 12
- [E2E/UI isolated tests](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-10#sigil_toc_id_178), ch. 10
- [Defining unit testing, step by step](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-1#sigil_toc_id_17), ch. 1
- [Integration tests](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-1#sigil_toc_id_25), ch. 1
- [Choosing a selection strategy](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-12#sigil_toc_id_222), ch. 12
- [Test-driven development](https://livebook.manning.com/book/the-art-of-unit-testing-third-edition/chapter-1#sigil_toc_id_27), ch. 1

#### The Art of Unit Testing, 2nd ed.

- [Working with legacy code](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-10), ch. 10
- [The difference between mocks and stubs](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-4#ch04lev1sec2), ch. 4
- [Why should I care about testability in my design?](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-11#ch11lev1sec1), ch. 11
- [Variations on refactoring techniques](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-3#ch03lev1sec5), ch. 3
- [Automated builds running automated tests](https://livebook.manning.com/book/the-art-of-unit-testing-second-edition/chapter-7#ch07lev1sec1), ch. 7

#### osherove.com

- [Refactoring String Calculator with SOLID Principles in Mind - Examples Needed](https://osherove.com/blog/2013/3/6/refactoring-string-calculator-with-solid-principles-in-mind.html)
- [Test Coverage and testing angles](https://osherove.com/blog/2005/4/3/test-coverage-and-testing-angles.html)
- [The difference between continuous Integration tools and automated build tools](https://osherove.com/blog/2010/1/14/the-difference-between-continuous-integration-tools-and-auto.html)
- [Video: Beautiful Builds](https://osherove.com/videos/2013/5/17/video-beautiful-builds.html)

#### pipelinedriven.org

- [Time to Main - Measuring Continuous Integration](https://pipelinedriven.org/article/2015/5/15/time-to-main-measuring-continuous-integration)

[Next lesson Refactoring Hardcore Skills](https://courses.osherove.com/courses/legacy-code-hero/refactoring-hardcore-skills/)


---

## About the author

**Roy Osherove**: Senior Solutions Architect at AWS working on AI for production and AI-driven development; author of The Art of Unit Testing and Elastic Leadership, with about 20 years of consulting and training on unit testing, TDD, team leadership and continuous delivery.

### Sites

- [RobotPaper](https://robotpaper.ai/): This blog: AI patterns and anti-patterns, AI and coding agents, and engineering leadership.
- [osherove.com](https://osherove.com): Roy's personal site. Author of The Art of Unit Testing and Elastic Leadership; consulting and training on unit testing, TDD, team leadership and continuous delivery, plus about 20 years of blog posts.
- [The Art of Unit Testing](https://www.artofunittesting.com): The Art of Unit Testing, 3rd Edition (Manning): readable, maintainable, trustworthy unit tests, plus TDD workshops in C#, JavaScript, Java and Kotlin.
- [5whys.com](https://www.5whys.com): Roy's writing on software team leadership: self-organizing teams, surviving chaos, learning mode and the Elastic Leadership model.
- [Pipeline Driven](https://pipelinedriven.org): Continuous delivery and DevOps practices: build patterns, pipeline-friendly testing, DevOps metrics and enterprise DevOps.
- [Elastic Leadership](https://www.elasticleadership.com): Elastic Leadership, Roy's framework for software team leaders (survival, learning and self-organizing modes).

### Social

- X: https://x.com/royosherove
- LinkedIn: https://www.linkedin.com/in/osherove/

### Contact

- Contact form: https://forms.gle/jQSx5KoXvrVA1JCK7

### License

Licensed under CC BY 4.0 — reuse, quote and adapt with attribution (Roy Osherove, link to the original page). License: https://creativecommons.org/licenses/by/4.0/
