intuitive testing - buildplease.coming i could do. i wanted to one day find the tdd bliss that...

35
INTUITIVE TESTING WITH LEGACY BY NICK CHAMBERLAIN CODE

Upload: others

Post on 18-Jun-2020

0 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

INTUITIVE TESTINGWITH

LEGACY

BY NICK CHAMBERLAIN

CODE

Page 2: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

1

Preface

When I started writing code professionally, my first project was someone else's first

project. I inherited it from another junior developer who was also brand new to the

game. It was a "safe" project for a junior developer because it only read data from var-

ious sources and put them on a web page for people in the business to look at and

send to the customers.

I sat down with my new project to fix a bug - one of many bug-fixes I was assigned

to in my junior role. I thought "what a mess." That's a knee jerk reaction... more of a

"jerk" reaction. I was judging this developer who had, like me, just got out of college.

His first assignment was this: "take this huge legacy application and completely re-

write it as a modern web application." It wasn't the same as my: "fix this bug” assign-

ment.

Reading a couple books about design, SOLID, and testing doesn't make you

ready for every task in your new junior developer job. Had I known this, I wouldn't

have had this attitude of "man, what was this guy thinking?... why did he put so much

logic in these controllers?... why didn't he use a Repository for this?... he's barely using

MVC the right way..." What I would've said was: “wow, this guy didn't have a lot of

time to deliver this project... he must not have had anyone to help him out... this is

classic 'trial by fire'... poor guy."

Eventually, I was given a bigger assignment. This time I was right in that unfamiliar

territory that my predecessor was in. I was going to be put in my place.

The task was this:

Page 3: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

2

1. The app is an ASP.NET MVC 2 web application with about 10 controllers

and 50 views

2. Every controller's actions read data from various sources including SQL

and IBM UniData (o.g. document database)

3. International customers who are getting quotes for our products need in-

formation in their native language and currency (internationalization)

4. Make the MVC views for these quotes "editable"

5. Information on the quotes should be editable in place

6. If the MVC view is edited on the page, saving the page will write to a sepa-

rate SQL database for later retrieval

I had to turn my comfortable, junior-safe, read-only application into a CRUD app. It

looked like I needed JavaScript too. My first real work that wasn't bug fixes. It was both

exciting and terrifying. I felt just like my friend did before me... a hard deadline, no ex-

perience, and smelly code.

What kinds of smells am I talking about?

- LINQ to SQL entity models first built for SQL access, then abandoned and

only used as POCOs... keeping the LINQ to SQL-generated infrastructure

code (unnecessary coupling)

- Scattered usage of Models, ViewData, and logic inside of controllers

- Models had single, 1000+ line methods containing data access and busi-

ness logic code (single responsibility principle violation)

- CRUD was a mixture of both Repositories and LINQ to SQL code inter-

mixed with connections “new’d” up throughout single controller methods

(coupling to dependencies)

Page 4: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

3

- Most of the beneficial layering provided by MVC was thrown out the win-

dow (untestability)

My issue was not with being able to read the code, or fix bugs in it, I'd already been

doing that. It was with safely touching a larger piece of it for this feature.

- There was money on the line,

- There was a marketing team that chartered the project

- There was potential downtime

- There was my future as a junior developer on the line

At the time, the concept of unit testing felt out of my reach. Developers who were bet-

ter than me did testing. Developers in big software companies with actual teams did

testing. Only developers who wrote brand new SaaS apps in Silicon Valley did TDD.

I knew how to test. I'd watched some videos and read some articles about testing

and TDD. I knew what mocks were. I knew how to run tests with a test runner. Most im-

portantly, I knew why to test. For me, the reasons for why to test applied directly to my

project. Once I moved past the my career is at stake, oh my god inner monologue, I

started thinking about what I was going to do. There were more constraints:

- I couldn't find any place in the code where I could start writing tests

- I didn't understand that this particular app wasn't testable

- I needed to make this app testable

- I needed to make this app testable while delivering the feature

- I didn't have time to waste

- I was adding JavaScript to a mission critical part of a C# web app

- I was introducing a risky feature

- I was adding a feature that was ultimately customer-facing

Page 5: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

4

- I needed to write some very reliable code

- I was getting bug-fix requests while I was writing this feature

- I needed to avoid adding to my bug fix backlog

- I needed to move on to other projects after I finished this feature

- I would get change requests after I delivered the final product

I had a strong gravitational force inside me saying try writing tests for this. Do full-scale

TDD right? Wrong...

I studied up some more. I learned that Test Driven Development was great for

writing new code. I found plenty of resources on writing failing tests, making them

pass, moving on to the next. I learned different styles of TDD like BDD, “Detroit” style,

and “London” style. I saw a dream of writing test and code in perfect rhythmic harmo-

ny. Then I looked back at my project.

I couldn't imagine applying TDD to this project. I couldn't visualize writing a single

test for the project. The tests I had seen through reading and studying were for classes

that had clear responsibilities and, more importantly, were decoupled enough so that

they could be exercised in isolation. In my project, the areas that needed testing

seemed completely inaccessible by outside code. I had no idea how I was going to

get these components to a state where I could isolate them and write automated tests

for them. I almost gave in to the cowboy coder way, trying to ignore all of those risks I

was afraid of. Operating in debug-and-hope-for-the-best mode isn't something I get

excited about.

In addition to it being obviously hard to test, I didn't know what to test. Every-

thing?

- I could have tried to test everything

- I could have picked all the controllers and tried to test them thoroughly.

Page 6: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

5

- I could have set a goal: 30% code coverage before I start writing any code.

None of these would have been effective. I had to be more intelligent and precise

about my efforts. I had to ask myself different questions.

- How far into existing code should I test?

- How should I write tests for the new code I'm writing?

- How do I isolate existing code into testable pieces?

- How should I balance integration with unit tests?

There was a huge, intimidating gap between the testing I wanted to do and the test-

ing I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only

saw that bliss far into my future as a senior developer writing a brand new, shiny app

from the ground up. The reality is that even senior developers inherit legacy code.

They may write greenfield code, but it becomes legacy as soon as they deliver it. Hav-

ing come across multiple, seemingly untestable apps, I can confirm that having the

ability to adapt your testing strategy quickly will undoubtedly make you a better pro-

grammer.

What this Book is About

My goal for writing this book is simple. I want developers who are just starting with au-

tomated testing to bridge the gap between how to write unit tests and how to put unit

tests to work in their daily coding life.

Page 7: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

6

Merriam-webster defines "intuitive" as the power or faculty of attaining to direct

knowledge or cognition without evident rational thought and inference1. As develop-

ers, we are demanded to act with intuition to solve complex problems by breaking

them down into parts. We attack those parts that we can solve from intuition first. This

frees up time for us to work on the parts that we can't solve with intuition.

I called this book "Intuitive Testing with Legacy Code” because testing can be an

activity that we approach with intuition. We gain intuition by practice.

It's understandable that training resources use sample applications where they are

able to demonstrate key principles of testing greenfield apps. I believe, however, that

the best way to gain intuition is to be challenged with difficult situations. Your first in-

herited legacy application will likely give you difficult testing situations. You will need

testing intuition to be successful. Having a good strategy is going to make gaining

and applying this intuition easier.

I'd like to present some techniques that will help you build the strategy for ap-

proaching your first few automated testing situations with a legacy application. A strat-

egy that you can use to go from knowing how to test to:

- knowing what to test

- knowing what not to test

- knowing how much to test

- knowing how to design a test strategy

1 "Intuitive". Merriam-Webster. Merriam-Webster, n.d. Web. 12 June 2016. http://www.merriam-web-ster.com/dictionary/intuitive

Page 8: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

7

What this Book is Not About

This book is not about how to use a certain testing framework. I have chosen a stack

for writing tests that I enjoy, and I have tried several others. The choice is up to you,

and it's not a permanent choice. Structuring your test project and SUT the right way

will help you prevent being locked into one choice for testing frameworks. I'd suggest

not wasting much time on the choice and just choose one now, if you haven't already

It is also not about why you should unit test. I want to talk more about how to be

sure you are focused on testing for the right reasons. I know that you are convinced

that automated testing is good. It's important to make sure that you always have your

eye on the prize with these benefits. I'd like to help you develop a strategy for staying

focused on generating value with your tests.

I'm also not going to tell you what specific code you need to test in MVC or any

other framework. Obviously, every application is different. Different hands have

touched it and different techniques have been used to solve similar problems. To tell

you to test a controller method a certain way isn't going to help you build a strategy

when you run into a slightly different implementation of that Controller method.

Finally, I don't want to spend too much time defining TDD, BDD, and Integration/

Unit/Regression/etc. testing. Those definitions are important and we may use them to

frame our testing strategies. If we spend too much time on them, though, we may not

reach our true objective - to start shipping tests. I want you to generate wins from this

book. I want you to reap what you sow.

Page 9: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

8

Who Should Read this Book

This book is primarily for people beginning, or wanting to begin using automated

testing. It will also help anyone who knows the subject deeply, but is unable to move

forward with using it in practice. This is the difficulty with a lot of programming prac-

tices. Learning a brand new subject in software isn’t usually that hard. The hard part is

applying it to what you do professionally.

I also believe that you lose skills that you don’t use professionally. You may be-

come an expert in a subject by studying and practicing it for years. It begins to deteri-

orate as soon as you stop using it professionally. This deterioration requires you to go

back and apply some deliberate practice to refresh.

Testing is a ubiquitous skill for modern developers. The consensus, for the most

part, is that testing is beneficial for software development. There are fads and buzz-

words that come and go, but testing is timeless. I want this book to help the reader

keep their testing skills fresh throughout their career.

If you’re worried that you know about testing but you’re not using it enough to

benefit from it - this book is for you. Tests are more than a requirement. They are not a

chore. I want to show you how to use tests like you use all of your other go-to devel-

opment tools. When testing becomes a tool that you can’t live without, you will not

have to worry about this skill deteriorating. I’m hoping that you may use this book to

develop testing into a tool that you use to benefit both yourself and your code.

How this Book is Organized

This book is organized into two main sections:

- Discussion of Legacy Testing Problems and Approaches

Page 10: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

9

- Walkthrough of Testing a Legacy Application

Chapter 1: Legacy Testing Problems and Approaches

- Testing Legacy Projects Requires a Different Approach

- Your task with the legacy project will likely be one of four change

types. There are risks that you will be taking by introducing any of

these changes to a legacy codebase. Testing is a risk mitigation

strategy for these changes.

- Asking More Questions of a Legacy Project

- It’s ineffective to simply try to cover a legacy project with tests. We

need to ask different questions than simply “What should I test.” We

need to be strategic with legacy testing.

- Seams and Hard-To-Test Code

- The hardest thing about testing legacy code is finding the right

places to isolate functionality and exercise it with tests. We need to

listen to tests and make good refactorings based on what they tell

us.

- Using Tests to Gain Productivity

- The debugger isn’t the best place for rapid development with a

legacy project. Testing is faster. Testing allows us to maximize our

productivity by writing code that stays with the project and leaves

breadcrumbs as you dive into the legacy app.

Chapter 2: Introducing the Walkthrough

- The Scenario

Page 11: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

10

- We are simulating a unique legacy testing scenario that, from the

surface, is not conducive writing tests. Once we begin, however, we

will reap the benefits of using tests to take command of the legacy

app.

- The Project

- We have a 10-day deadline to add a feature to a legacy project with

very little information about how it’s used and how it was built.

Chapter 3 thru 9: Adding a Feature to a Legacy App using Tests

- The project will be broken down into a timeline to gain an understanding

of our productivity losses/gains. We use tests to learn the codebase and im-

plement the feature.

Chapter 10: Conclusion and Final Thoughts

Page 12: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

11

Testing Legacy Projects Requires a Different Approach

To build on why there is such a difference between learning testing and actually test-

ing a legacy project, let's talk about how comfortable you are making change.

In Michael Feather's book “Working Effectively with Legacy Code” he outlines

“four reasons to change software”:

- Adding a feature

- Fixing a bug

- Improving the design

- Optimizing resource usage2

These different types of changes spawn different ideas of how much change and risk

you are introducing to the existing code. The amount of risk you're assuming should

guide your testing strategy. I know I said I wasn't going to talk about why we test, but

this reason should be clear. You are testing to reduce risk.

What kinds of risks are you taking when you start working with a legacy code-

base?

- Risk of breaking some existing functionality

- Risk of your new code not doing what it's supposed to

- Risk of your stakeholders rejecting what you did

- Risk of the behavior of the application changing

2 Feathers, Michael C. Working Effectively with Legacy Code. Upper Saddle River, N.J: Prentice Hall PTR, 2004. Print

Page 13: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

12

- Risk of losing trust with your users

- Risk of being reprimanded by your boss

- Risk of downtime costing the company money

- Risk of losing confidence in yourself as a developer

- Risk not being able to come up with excuses for your code not working

All of these risks have something in common. They cost money. Some costs are more

obvious than others, e.g downtime keeps users from being able to do their jobs. If a

salesperson can't sell a product to a customer for a day, that's opportunity cost. If you

lose trust with your users, that's opportunity cost. If your stakeholders reject your fea-

ture, that's your time and salary sunken.

If you think of your time as costing money, the not-so-obvious costs become clear.

Losing confidence in yourself as a developer can make you cautious and afraid to try

new things. Having to constantly come up with excuses for why your code isn't work-

ing is a huge drain on your internal resources. Being reprimanded by your boss caus-

es you stress. It forces you to waste time figuring out how to deal with that stress.

These things take you away from focusing on what is essential to both your career and

the people that depend on you. These things cost time and time is money.

So what does this have to do with testing? You're more likely to be working with a

legacy project than not. Therefore, you are more likely to be doing one of the above

four changes. You need a toolset for reducing risk. It's not just about being able to

write tests, or even being able to write good tests. It's about your current situation and

what is essential for your success at this point in your project.

For example, you will probably have to sacrifice writing a terrible test just to get

started. By terrible, I mean you may have to smell up the test code - too many mocks,

multiple assertions, copy/pasted test fixtures, even copy/pasted implementations of

Page 14: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

13

SUT code! All of these may happen, but in the end you will have started. You'll be

shipping. You'll be learning about the code. Most importantly, you'll be writing code

instead of reading it. This has an immense effect on your productivity.

My favorite analogy is to a rock climber. A rock climber examines his route up the

side of a cliff before he starts his ascent. He can’t anticipate every spot he's going to

place his feet, every crevasse he's going to grip, every loose pebble he's going to en-

counter. He also can't spend all day at the bottom of the cliff. He needs to make

progress so that he can get to 50 ft and get a better idea of his next move. There will

always be a chance that he will encounter something unexpected. He will not know

everything until he gets there and puts his hand on the rock. He will use ropes and an-

chors to establish intermediate points of protection on the way up. After setting an an-

chor, he’ll make small adjustments to his route. Worst case, he’s safe to fall back to his

last anchor if he makes a mistake. This is a much better approach than planning the

entire route and expecting his plan to work out perfectly.

This is the same approach you can take to testing. Nothing will go perfectly. As

David Heinemeier Hansson says in his blog post "Software has bugs. This is normal.":

"The only reliable, widely used way to ensure impeccable software quality is to write

less software that does less stuff."3 If you're like me, you try to do more planning in or-

der to lower risk. You and I, like the rock climber, aren't clairvoyant. We can read code,

but until we actually touch it we can't know everything that will happen when we

touch it.

3 Hansson, David H. "Software Has Bugs. This Is Normal. - Signal v. Noise." Medium. N.p., 2016. Web. 12 June 2016. https://m.signalvnoise.com/software-has-bugs-this-is-normal-f64761a262ca#.78z2skaf3

Page 15: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

14

Our approach to legacy testing is going to be different. It requires a different ap-

proach that draws from the various dogmatic testing strategies out there. Our strate-

gies will focus on lowering risk, lowering cost, and shipping code.

Legacy testing can’t be treated the same as greenfield testing. We are usually

faced with a unique scenario that we have never seen before. We don’t know every-

thing about the legacy app, especially when we first open it in the editor. We are tak-

ing risks because we don’t know everything about the app. The biggest risk is making

change to the code and not knowing our effect on the existing behavior. If we don’t

mitigate risk, it costs money and time.

Tests are used to mitigate risk, both in greenfield and legacy development. With

legacy code, we can use less conventional testing strategies as long as we continue to

mitigate risk. Instead of considering testing a requirement, we should consider it a

tool - just like your editor, your favorite patterns, or the debugger.

Page 16: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

15

Asking More Questions of a Legacy Project

Let's dig deeper into why legacy projects require a different approach to a test strate-

gy. As Feathers describes in “Working Effectively with Legacy Code” we have to con-

sider how much behavior we are impacting with our change. All of the four types of

change: feature, bug, refactoring, optimization will affect some behavior of the appli-

cation. In addition to the behavior that the change will directly address, there is exist-

ing behavior that we need to preserve. This is the unknown. This is the risk that we

want to mitigate.

In an ideal world, we would cover every code path with tests to achieve 100% cov-

erage across the entire system. Maybe our boss realizes this is not possible and de-

clares that we need 50% code coverage. The trouble is, we can't lay a blanket over all

of the existing behavior - we don’t have time.

Same is true for bullet-proof vests. An police officer equipped in a complete

bomb-squad suit loses mobility. While he would be fully protected, he has other jobs

to do. He must be able to get in and out of his car, talk to victims, and run at a reason-

able speed. A bullet-proof vest is lighter weight and protects his vital organs. The vital

organs have the highest risk of fatality from a gunshot. In our legacy project, the exist-

ing behavior that will be affected by our change has the highest risk.

What do I Test?

I've asked myself "What should I test?" many times. In a greenfield application, you

likely know what bugs are lying underneath a block of code that you wrote. If you

Page 17: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

16

practice TDD, then what to test is clear to you since you are driving your implementa-

tion with tests. With legacy code it's ineffective to randomly pick code out and test it.

Depending on the size of the project, you might start out strong, testing each class

one-by-one. Like a sprint, however, you will eventually feel like you're not getting any-

where. That's why I suggest writing tests in parallel with any feature, bug, refactoring,

or optimization that you're tasked with.

You're likely tasked with something to do with the production codebase that’s not

test-related. If that's the case, I recommend focusing on writing tests that benefit your

efforts to deliver on whatever you're tasked with. One major example of this is a fea-

ture. It's likely that a feature isn't going to be developed in isolation from the existing

legacy code. The legacy code affected by the new feature is a prime candidate for

writing tests. It not only protects existing functionality, but it makes you familiar with

that code and how you're going to integrate your new code with it. There are more

benefits, and we'll see them when we discover a good slice of existing legacy code

that we want to test in our sample project.

What am I Doing?

Yes this is an obvious question. Probably one that you know the answer to. “I’m fixing a

bug with this legacy app.” There’s more depth, however. What are you doing in refer-

ence to your role as a resource for your employer/client? You may be fixing a bug, but

is the bug impacting business continuity? Is it a bug that is stopping the business from

gaining revenue? Who is asking for the feature? Is it a habitual “feature-requester?”

Who’s telling you to refactor that code - is it your manager, or another developer who

just read a blog post and says “you have to use X pattern, it’s so much better than

what you’re doing…”

Page 18: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

17

It’s important to know what impact you’re making on the business with your

change. If it’s an impact to something that gives your business its competitive advan-

tage, then testing should play a bigger role for you in the project. You’ll want a high

percentage of your time to be focused on writing good, maintainable tests. If the

change is to something that doesn’t give the business its competitive advantage, this

is a chance for you to try new things with your tests, e.g. new mocking frameworks.

Your situation should play a role in your testing strategy. You don’t want your test-

ing efforts to be seen as a waste of time. You need to always have an idea of the ulti-

mate value of your tests. This is another reason that having a test coverage metric isn’t

as effective as testing to facilitate a change.

With legacy testing, a test coverage metric like 50% isn’t going to give you the

most value out of writing tests. If you were aiming for a test coverage metric, you

might ask: “What do I test?” With legacy testing, we should ask questions like:

- “How much will this change affect the existing behavior?”

- “Where am I going to integrate my new code with the existing code?”

- “How business-impacting is this bug?”

- “How often is this code executed in production?”

- “Will my teammates be able to debug this if I’m not around?”

- “What’s at stake with making this change?”

It’s important to stay focused on what’s essential when trying to test a legacy app. You

can end up wasting your time building testability into the app when it’s not essential. It

will be harder to create testability where there is none, so make sure you prioritize

your testing efforts to maximize their value.

Page 19: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

18

Seams and Hard-To-Test Code

There are a few reasons it is hard to measure the amount of existing behavior that is at

risk when we are making a change. A prominent reason has to do with highly-coupled

dependencies. In a legacy project, you will find that code with high-coupling is hard to

test. It will be difficult to find where to begin infiltrating the code to get to the actual

"unit" you want to exercise with your tests. Consequently, it will be difficult to simply

pick a spot and start testing.

Feathers defines a “seam” as ”a place where you can alter behavior in your pro-

gram without editing in that place"4. When I first began testing legacy code, both

MVC and non-MVC, it was difficult for me to find seams. My predecessors were un-

aware of the benefits of testing. They were also unaware of refactoring principles and

how to identify code smells as described in Refactoring by Martin Fowler5. The result:

long methods with too many responsibilities.

One particular responsibility in these long methods made them hard to test. This

was the new keyword. Multiple new keywords in these long methods were a clear sign

of coupling for me.

So if you are asking the question: "What do I test in this project?" you may have to

ask: "What can I test?" instead. Knowing where you're going to have to pry apart

4 Feathers, Michael C. Working Effectively with Legacy Code. Upper Saddle River, N.J: Prentice Hall PTR, 2004. Print

5 Fowler, Martin, and Kent Beck. Refactoring : Improving the Design of Existing Code. Reading, MA: Addison-Wesley, 1999. Print.

Page 20: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

19

seams will affect your test strategy moving forward, regardless of what items you have

identified as "test-ready".

The seams discussion leads to a more general reason we have to adapt a test

strategy for the legacy project - Hard-To-Test Code. Again, you might decide with au-

thority: "I need to test that this controller action method returns this ActionResult.” If

the action method is 100 lines and news up four dependencies, the method is Hard-To-

Test. This is a test code smell from Gerard Mescaroz's book “xUnit Test Patterns: Refac-

toring Test Code”6. When smells originate from the System Under Test (SUT), Hard-To-

Test code causes a lot of difficulty writing tests.

6 Meszaros, Gerard. XUnit Test Patterns: Refactoring Test Code. Upper Saddle River, NJ: Addison-Wesley, 2007. Print.

Page 21: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

20

Using Tests for Productivity

Remember Feathers’ four primary reasons to change software:

- Adding a feature

- Fixing a bug

- Improving the design

- Optimizing resource usage

Without testing, how quickly can you get up and running with one of these changes to

a legacy app? Looking back at the way your stakeholders give you tasks to complete,

how easy is it going to be to kick that feature out?... fix that bug?... clean up code?...

make it faster?... At a minimum, the only information you have is the code and maybe

a UI. Without tests, you'll inevitably be immersed in debug mode. Actually, your world

will probably look like this:

- Open Visual Studio

- Open up the email with the project requirements

- Set breakpoint where you might find something

- Hit Debug

- Wait

- Go to http://localhost/SomeRoute/

- Fill out a form

- Click Submit

- Wait

Page 22: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

21

- Hit breakpoint

- Examine locals, hover mouse over variables, run something in immediate

window, etc

- Make a mental note of something learned

- Write some production code

- Repeat...

There's also the possibility that you set your breakpoint too late. You are then forced

to repeat before you are able to gather any information. That repeat step is what I hate

most about the debugger. At a breakpoint, you are given a single time span to gather

information about the state of your app. You are freezing the app in a state that you

assume the user will be in. This is definitely useful, until your human mind gets in the

way.

Have you ever set a breakpoint after you needed to? Have you ever left a break-

point from a previous debugging session, forcing your app to stop where it didn't

need to? Have you ever forgotten to set a breakpoint and debugged anyway just to

blow past any useful state information you may have gathered? I have... and it was

slow, painful, annoying, unproductive and left me in search of a better way to fully un-

derstand legacy code.

Shipping

Again, I don't want to tell you why automated testing is good for the health of the ap-

plication. This area has been explored and many experts have their own take on why

we test and how to get the most out of tests. What I'd like to focus on is how you can

Page 23: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

22

apply a testing strategy to a legacy application. A huge reason to keep reading is that

when you write tests, you are shipping.

How many lines of code did we write while we were repeating debugging ses-

sions over and over again? Maybe you're a "rockstar" and it only takes a single debug

session to add a feature to an unfamiliar codebase. Good for you. I'm not a rockstar.

I'm a beginner automated tester and I don't know where to start with this legacy app. I

have deadlines, I want to get better, and I need to get started.

I want to show you how testing lets me actually ship code early. Instead of the

above basic debugging scenario, let's learn about the app by writing some tests:

- Open Visual Studio

- Open up the email with the project requirements

- Find somewhere in the code that is related to your requirements

- A sibling feature

- A parent feature

- An API call

- A Database call

- Existing infrastructure code that you'll need

- A confusing method

- Copy/paste-able code

- Find a seam (if possible)

- Write a test

- Make the test build

- Make the test pass

- Ugly or pretty, whatever it takes

- Discover results

Page 24: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

23

- Send property values to stdout

- Exceptions thrown

- Build errors

- Assert something

- Refactor the test a bit

- Rename it clearly

- Keep moving forward

The first time we write a test may take time. We will need to get our test project set up,

add a test framework, and make sure our test runner recognizes the test framework.

Once we have this “walking skeleton”7 in place, our ability to write tests and exercise

production code improves drastically.

It's this moving forward that is where we gain the most from writing tests early. It

allows us to ship something early. Shipping early makes you a better developer. It

keeps your hands on the keyboard. You're as close as you can get to writing produc-

tion code, when you’re writing code in a test project.

What sounds better?

- debug...

- breakpoint...

- continue...

- what was that property value?...

- debug...

- breakpoint...

7 Freeman, Steve, and Nat Pryce. Growing object-oriented software, guided by tests. Upper Saddle River, NJ: Addison Wesley, 2010. Print.

Page 25: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

24

- write something

or

- write test...

- learn something…

- write assert...

- write rename/refactor...

- write test...

- learn something...

- write assert...

- write rename/refactor...

- write test…

It's always better to write. Our approach in our sample project is going to be focused

on shipping test code quickly so that we can ship production code quickly. To further

illustrate this, I'll break our progress down into a time frame to see if we can keep the

number of days lower than it would normally be. We'll use tests to squeeze out pro-

ductivity all the way up to our deadline.

Testing isn’t usually sold to potential practitioners as a way to increase their pro-

ductivity. It’s a benefit that I only realized once I started testing legacy code. Writing a

test that stays in the test suite or in source control is more permanent than debugger

information. Having a test written for a part of my coding progress is a breadcrumb for

me to remember exactly what I was trying to accomplish at that time. It’s also faster to

not have to spin up the debugger every time I want some state information.

Refactoring your code to the point where you can get state information through

tests is a good approach. Having clear outputs from a function is one reason why func-

Page 26: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

25

tional programming is said to be more robust and bug-resistant. If you can refactor

your code to the point where you can test it and find out exactly what it was meant to

do, you likely have a good design.

Writing code is always going to be more productive than reading it. While you are

writing code, you are learning what’s going on. You are also unearthing very precise

refactoring to make to the production code. You learn the history of the development

of the app - testing a branch of logic can tell you why it’s there. The debugger can

give you this information too, but it will always cost you time if you rely on it too much.

Page 27: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

26

The Scenario

I want you to consider our sample project “raw footage” of a scenario that I've found

myself in countless times. By raw footage, I mean we're going to make mistakes and

write bad tests. We'll go down the wrong paths. It won't be clean. If you think about

my purpose in writing this book, you'll remember that I'm trying to help someone who

is in a very specific position. Someone who inherited a legacy app and wants to be

successful with the assistance of automated testing.

Let's talk more about this very specific position. Many experienced developers

who are proficient in writing unit tests have strong opinions about good and bad tests.

If you are one of these developers, then you might disagree with my approach. I'm

going to lay it out for you first. I know it is real because I've listened to many develop-

ers talk about being in this same situation.

You're a one or two man shop in a non-software related industry:

- Manufacturing Plant

- Food-processing Plant

- Financial Management Firm

- Public School System

This is your first software job:

- You're on your own

- You don't have any mentors

- If you have mentors, they aren't helpful to you

Page 28: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

27

- You inherited 30+ legacy apps

- None of the original devs work there any more

- You're not just going to write code, you’ll manage infrastructure

- You'll manage SQL Servers

- You'll set up IIS

- You'll have to learn about Active Directory

- You'll see new languages

- You'll learn VB6

- You'll learn VB.NET

- You'll learn UniBasic (whaa?)

You have around 10 bosses/stakeholders that you may answer to:

- Your Manager

- Plant Manager

- Project Manager

- CTO/CIO

- COO

- CEO

- Marketing Manager

- Sales Manager

- Comptroller

- Superintendent

- Product Manager

Your bosses/stakeholders communicate with you like this:

Page 29: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

28

- "I need this app to do this right away because all of our customers are

freaking out!"

- "Why doesn't this app do this correctly, we talked about this bug months

ago, why doesn't IT ever get to any of my feature requests?"

- "Nick, you need to stop everything and get this feature out, it's your highest

priority right now"

- "We've got to figure out why this app does this all the time, Sales can't deal

with this"

- "I don't care if it's written well, if you don't have it done by Friday you need

to have a good explanation"

- "Testing, yea whatever just make it work"

- "Yea Nick's all about testing, it's a huge waste of time in my opinion, it

doesn't make the app work"

You're seeing job postings that require unit testing experience:

- You're studying the basics

- You're learning xUnit

- You're reading books

- You're reading blogs

- You're feeling comfortable with the idea

- You understand the benefits of automated testing

The books and blogs make assumptions:

- That you have a team of several developers

- That you're writing an amazing SaaS app

- That you're writing a brand new app

Page 30: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

29

- That you have a kanban board

- That you have a Senior Developer to talk to

- That you have a Continuous Integration server

- That you have a Product Development team

- That you have weekly code reviews

The reality of your situation is:

- Boring (sometimes) enterprise stuff

- On-premise email server

- 5 physical servers

- 10 virtual servers

- SQL 2005 all the way up to SQL 2014

- 10 year old ERP system

- 5 year old CRM system

- 30+ custom applications

- 30+ integrations between various applications

- 0 tests have been written in your company

In summary, you're a junior developer who inherited a lot of legacy code. You want to

do automated testing because better jobs demand experience in testing. What you're

reading/watching/learning doesn't tell you how to incorporate tests into your day-to-

day legacy code work.

Page 31: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

30

Why is our scenario important?

Why does it matter that you're in this really specific scenario? I think there are many

people out there that have never written a single test for their own code - but they

want to. They are worried that:

- If they started testing, it would take away all their time spent delivering re-

sults

- If they don't start testing, they won't develop the necessary skills to move

forward in their career

A legacy app isn't a clean slate for someone to start applying new techniques and

ideas easily. Imagine that no developer in the history of your employer has ever writ-

ten a test. You sit down on a regular basis and look at thousands of lines of legacy

code and wonder "even if I wanted to test this code, where would I start?"

What do you stand to benefit from actually starting to write tests for this app? Is it

worth it actually finding a place to start testing and keeping with it? I say yes. I believe

that tests can provide you with an opportunity to work with legacy code more effec-

tively. It may take more effort, but this effort gives you insight into what's actually go-

ing on with a legacy app. The effort that you put into testing a legacy app helps you

learn the ins and outs of the app. Learning quickly means you deliver quickly.

Page 32: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

31

The Project

To set the stage for our sample project, we'll be using Contoso University from Mi-

crosoft's "Getting Started with Entity Framework 6 Code First using MVC 5."8 We'll as-

sume that we're a sole developer for the University responsible for implementing a

feature for this app. We were just hired by Contoso University, so we've never seen the

app. Our manager is giving us a 10-day deadline to have most of this feature imple-

mented. It'll determine if we make it out of our probationary period or get fired...

The app currently manages Students, Instructors, Courses, and Enrollments. Our

manager has been asked by their boss (and boss's boss) to quickly implement a fea-

ture that allows Instructors to update Grades for the Students in the Courses that they

teach.

Here's the requirement in more of a specification format:

When viewing the Gradebook for an Instructor

I should see a list of Courses that the Instructor teaches

When I select a Course

I should see a list of Students in the Course

I should see each Student's Grade for the Course

When I select a Student's Grade fron the list of Students

I should be able to edit a Student's Grade

When I edit a Student's Grade

The Student's Grade should be updated in the Instructor's Gradebook

This seems simple, but we just started working at Contoso University. We're unfamiliar

with the size, complexity, and functionality of the application. We are expected to

quickly assume ownership of the app and start shipping code.

8 https://www.asp.net/mvc/overview/getting-started/getting-started-with-ef-using-mvc/creating-an-entity-framework-data-model-for-an-asp-net-mvc-application

Page 33: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

32

What's our progression going to look like? Not the progression you would expect.

We'll spend more time getting familiar with the legacy codebase than is probably

comfortable for you. We must remember that getting familiar and writing tests coin-

cide with each other. While we write tests against the legacy code, we are navigating

the codebase and getting comfortable with it.

We will write several different kinds of tests that violate automated testing princi-

ples that have been established by the testing community. One example will happen

right away. We will write tests I like to call "exploratory" tests. These are throw-away

tests. The reason they may not be accepted as proper testing technique is that they

will simply test how we would use LINQ to query our Entity Framework DbContext. It

may only be testing that the .NET framework actually does what it's supposed to. The

test is written to get our bearings for how we're going to write new logic inside of the

production system. As we explore our options by testing built-in .NET framework code

to do a specific task, you might wonder if we are wasting our time. I don't believe we

are.

Any time you're writing code, you're being more productive than reading code.

You may write bad code in your tests, but a test suite is a safe haven. We will find times

where we need to refactor the production code so that we can test it, but other times

we will simply be playing in the sandbox that is our test suite. The other thing to re-

member is that you can either delete or skip tests. You can't always delete or skip pro-

duction code.

We wil spend time testing what is testable in the legacy code before actually writ-

ing any of our Gradebook feature. This establishes another level of familiarity for us.

We can step into the mind of the original developers, whether it was last year or 5

years ago when they wrote the code. Adding code to a legacy codebase requires

some level of alteration of the existing codebase, unless it's written extremely well - so

Page 34: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

33

well that the original developers anticipated every change that the business would re-

quire of the app... unlikely. Writing tests for the legacy code in parallel with develop-

ing a feature tells us how to stay consistent with code that is already there. It allows us

to set up feedback so that our affects on the legacy code will be clear to us, especially

if we break it.

Here's a rough outline of our progression for adding this feature with tests:

- Day 1: A Walking Skeleton

- Test 1

- Day 2: Navigating the Legacy Data Model

- Test 2

- Test 3

- Test 4

- Test 5

- Day 3: Testing Existing Code

- Refactoring 1: Extract Method

- Test 6

- Test 7

- Test 8

- Refactoring 2: Dependency Injection

- Refactoring 3: Fake DbContext

- Day 4: Getting our Rhythm

- Test 9

- Test 10

- Test 11

- Test Refactoring 1: Rename

Page 35: INTUITIVE TESTING - buildplease.coming I could do. I wanted to one day find the TDD bliss that practitioners spoke of. I only saw that bliss far into my future as a senior developer

34

- Day 5: Drive the Feature

- Test 12

- Test 13

- Test 14

- Test Refactoring 2: Object Mother

- Test 15

- Implementation 1: Gradebook Index View

- Day 6: Copy and Paste

- Test 16

- Test 17

- Test 18

- Day 7: Building the Feature

- Implementation 2: Gradebook Edit View

- Test 19

- Test 20

- Test 21

- Finishing Touches: Navigation

With 21 tests total over 7 days, we should meet our deadline with time left over to

refactor. We may even decide to deliver the feature 3 days early and accept feedback

from the stakeholders. Let's get started!