Refactoring Kung Fu: Part III

Gil Zilberfeld explains refactoring for legacy code, about method extraction and constructor injection for refactoring legacy code
Standard
In this series we're taking a look at how to refactor un-refactorable legacy code.
Part IPart IPart III

Check out the original version of the code (Java, and also in C#, in tag 1.0).

Right, time to move some code around. Before we do that, remember step #1 in our process: Draw a line around our tested code. For us it’s mainly the “cook” method. That’s the heart of our code and the code we want to refactor.

The “cook” method is the entry point. Where does our system end? In our example, it is the private methods (which I didn’t add code to). Let’s take a look at them:

There are two types of methods here. The first two methods go outside the class (or would if there was an implementation). In the real world, the may call another REST service, or send an async message. In the real world, that code can also be the combination of inlined logic, and external calls.

The last four are “regular” private methods. This is code we’ve extracted, that, in our example, stays in the class. Or call another class that’s still within the system boundaries.

In general, these are the two kinds of methods we’re going to deal with: The ones that call outside the boundary of our system, and those that stay within it. That is important, because if we want something outside, we want to establish a clear boundary, and we use *methods* to do it.

Now that we have a boundary, let’s push code outside it. And how do we do it? Introduce an interface You’ve probably seen that coming. Let’s call this interface dispenser. In our case it has two methods:

Basically, we extract the code the code in the private method and move them beyond the interface, and whatever parameters we pass to the private method, are going to be parameters in the interface method. This is the kind of low-risk, and even possibly automated, refactoring for introducing interfaces and method extraction. If the code in the private method we’re moving accesses local or instance variables, we should pass them as well to the interface.

And it doesn’t have to be a single interface, like in this example. We can define two interfaces: IngredientDispenser and PastaDispenser. Whatever makes sense in terms of cohesion. The boundary we’re drawing can be temporary for our refactoring, but also can remain later because of separation of concerns, and a better design.

We need to introduce the interface somehow into the class. I decided to inject it through the constructor, but adding a setter works as well.

So here’s the constructor.

Again, this is a modification to the code, that is a low risk change.

But now we have a clear boundary, and we can use it later. The next step though is test case analysis.

The modified code is under tag 1.1 (Added Dispenser interface through constructor)

Refactoring Kung Fu – Part II

Gil Zilberfeld explains the principles of refactoring legacy code
Standard
In this series we're taking a look at how to refactor un-refactorable legacy code.
Part IPart IPart III

Last time I gave you a sneak peak at our PastaMaker. You can check the whole project here.
Let’s take a closer look.

The main logic is in our PastaMaker, so focusing on testing it makes sense. Most of the logic is there, and so are the changes we need to make. We’ll need to do that very carefully, as the system is not test-friendly.

As you can see, our PastaMaker has one main method, “cook”. It takes two parameters, analyzes them, and based on their value does all kinds of things. The “all kind of things” are in private methods, that can call outside the class. I’ve left most of them empty for our example. In the real world, the content of some of the private methods was inlined in the main method. Also in the real world, there are a dozens of conditions and dozens of lines of handling code in that class, so the scale is larger. The principles that we’ll use still apply though.

Do you see the if-else-if-else-if logic? That’s the main problem. It’s not only hard to understand; the order of the clauses is important. It’s hard to change it without risking changing the functionality. Also, what we do in them, can impact subsequent conditions.

How did we get here?

No one plans for this kind of code, but you can see how it gets built, right? You don’t want to touch the “already working” cases. You find the least risky, most probable place to add your new case, close your eyes, commit the code and run away screaming. Rinse and repeat this process for a few years, and we get to this structure.

Our PastaMaker is a great candidate for refactoring. There’s also something that we can see from the code that can help us: We can identify the different cases (but are they all the cases?). In the original code, the conditions are similar – the inputs to the methods are analyzed in the same way (Strings instead of enums). However, the combination number of cases can be astounding.

Our approach to refactoring this code needs some discipline, and some guiding principles.

  • Put (limited) trust in our tools

Our tools (in this case my IDE) will help with automatic refactoring. Depending on the tools we use, we can usually trust renaming, extracting, and sometimes even moving methods around. We can also rely on identification of “unreachable code” after a return.But we don’t trust our tools totally. For example, to invert if-else-if clauses automatically is dangerous. The semantics of our application is not known to our tools. Our trust has limits, and tools can take us so far.

  • Minimize risks

While I’d like the IDE to refactor everything for me, when I move code around, or introduce interfaces, and make other changes my tools can’t do reliably, I need to trust myself.
If this is a critical component, like the PastaMaker, I don’t.When we refactor critical code, we do it with a pair. Or in a group. The process may be long, and we’re going to make mistakes. A partner can help identify the errors, pick up on missing cases, and in general keep us moving forward. We minimize risks where and where we can and that includes another set of eyes.

  • Refactoring means NOT adding code as we go

This is very important. We’re already knee-deep in code we don’t understand. Even worse, we may think we do, and that may not be true. Until we finish making change, and have more confidence the code works, when it has tests around it, we don’t add any new functionality. This is, by the way, one of the genius pillars of TDD: separate the functionality from the refactoring. Only this time, we’re going the other way around.

  • New code is only added after, and with accompanying tests

Once the code is ready in terms of form, and is stable with tests, only then we can add new functionality. And use test-first.

  • Leave the code better than how we found it

We don’t refactor for nothing. We want better code we can add new functionality to. That means, better, simpler, factored code with tests. Oh, and since we’re doing this in a pair, at least two people agree that the code is better than the original.

Principles are not enough

So what’s our plan of attack? The following techniques help us.

  • Define the boundaries of our system-under-test

We need to decide where our system starts and ends. We’re going to move code inside it, inject inputs to one end, and sample on the other end. But we start with the boundaries.

  • Build adapters around those boundaries

Once we’ve decided where the other end is, we’ll need to create adapters to mark our territory. Those adapters will call the original code, and can help us decouple dependencies. In further testing we can use the adapter for mocking..

I’m pretty sure when you here “adapters” you think “interfaces”. We’ll use them, but that is a language construct. If we’re working in C, for example, we’ll need something similar, since there’s no “interface” construct in C.

  • Put as many safeguards in place as we’re refactoring
    We don’t have tests yet, but we will eventually. We need to think about all the cases we want to cover, and we want this comprehensive list early, before we touch the code.
    Later, we’re going to put information in the code that not only help our tests, but also to debug them when they fail. Feedback is our friend.

Next time, we start moving code around.

Refactoring Kung Fu – Part I

Gil Zilberfeld goes through an example of refactoring legacy code, on the way to creating tests for it
Standard
In this series we're taking a look at how to refactor un-refactorable legacy code.
Part IPart IPart III

As long time readers, you know that if we’ve got legacy code, we want tests for that. However, the code doesn’t always play along.

As you’ll see in this example, this is the kind of code that really needs tests. It’s full of logic. It’s alive and working, and it’s so core to the application, that we need to keep going back into it, and we tremble whenever we need to make changes in it.

So what? Tests are important, so write them.

However, things are not that simple. As you look at that legacy code, you know it’s going to be quite a task. The code is messy and full of dependencies. And, that’s not even the main problem: we don’t actually know how it works. Oh, it’s working, for years now, but there’s no updated documentation, and the code itself looks like a recipe out of a pasta book.

Blind testing

Tests have basically a simple construct – define a system with boundaries, pour some known inputs, and see if the outputs come as expected. Our problem starts with the boundaries of the system. It’s hard to draw them. Once we do, we don’t have a comprehensive list of inputs, and of course, what is the expected outcome for them.

Do we back down? Hell no. That is not the kung fu way.

We can still write tests. This special breed of tests is called “characterization tests”. We write characterization tests for a system with unknown or uncertain behavior. They start out like tests without asserts. We pump in inputs, see what comes out in the other end. We agree that the system works as-is, meaning we can trust that for the given inputs, the outputs are the expected outputs. Then we convert these outputs into asserts. Presto, we have tests.

Sometimes, the system is so messed up, characterization tests are the only way to go. However, it’s still defined by its architecture. We may know where the entry points are, but we still need to define what “the other end” is. Is it the database? Calling an external API? An internal state change?

Also, in a medium-to-ginormous size system, with so many possible states, can we cover the code with tests reasonably well? Can we collect and collate all these outputs?

Not so presto

Wouldn’t it be easy if we could make the system boundary a size that is reasonable, and in which we can identify most states, and therefore control what we can, and in fact, write effective valuable tests?

Yes, yes it would.

That requires some guts, a few guiding principles, some tools, and above all, a repeatable method. We can build them, we have the technology.

In fact, when we don’t write tests, and just change the code, we already cross out the guts part (along with the stupid/courageous border). Once we acknowledge that, we can move forward with solving the problem.

The example we’ll explore is based on an actual code base. Imagine a controller doing many things. The code is not necessarily contained just in the controller class. It reads and writes to the database, calls out to other APIs, doing some logic of its own, and returns results through its API.

Our “system” starts out with a REST API in the front, and includes the controller, the data layer, the database, other services, and even a middleware, like Spring. That’s a big system. We want to cut it down to unit-testable size, if possible.

Since the original code is the definition of spaghetti, I thought of re-creating our sample example: The PastaMaker class. Here’s a sneak peek:

Hey, if you want to cook some pasta, you have to break some eggs. Or at least untangle the spaghetti first.

If this looks familiar in some way, stay tuned. We’re going to slay the legacy code dragon.

Real Life TDD With Spring – The Road to the First Test III

Gil Zilberfeld explains the differences between the APIs for the TDD spring example.
Standard
How does TDD look like in real world development of microservices or web interfaces? That's what this series is all about.
1. Introduction2. The requirements3. Test case analysis I4. Test case analysis II
Test case analysis III6. Test case analysis IV7. Setting up a project with Spring Boot8. Which tests to write?
9. API design

We’ve come up with a few alternatives for the APIs (their draft version, anyway). I’m going to weigh them here, and remind ourselves – this is the internal API forma, but also has a REST counterpart.

Let’s talk about each of the options, but this time, not just about how they look, but also about their design implication.

String pressKey(String key);
One API to rule them all. There’s an issue with its design though. If the UI doesn’t tell the server: “I’m ready”, or if it was refreshed in the middle of an operation, there could be a disconnect between what the UI displays and the server’s state. And where there’s disconnect, there will be bugs.

String pressKey(null);
Still a single API, but this time, we’re talking with the server. In order to reset, we’re using an API called “pressKey“, when no one has actually pressed any key. Exposing a special meaning with “null” to the outside world is not something I like to do. The last guy who did it called it “the billion dollar mistake”. Not only is anything being pressed, no nulls have been pressed in the making of this post. We’d like the interfaces to speak the language of the domain as much as possible, and this one kind of breaks it.

So how about that reset-as-a-startup?:
String pressKey("C")"

It’s a more attractive option, since the initialized system should behave as if it was reset with the “C” key. There is still the issue that nobody actually pressed anything. And although I don’t like planning and designing for future requirements, there may be a future where resetting results has a different behavior than the one on startup. In that future, we might want a different functionality when pressing “C”.

void pressKey(String key); String getDisplay();

If we separate the get/set operations, (and follow the command-query separation principle) we can get a nice, clean interface. At the start, we’ll call the “getDisplay” API to show what the calculator’s state is at the moment. Which may not be zero. It can be the last held result).
This is an interesting feature, although this kind of behavior was not requested. We need to show “0” in the beginning.

We can assume that “getDisplay” will result in 0, but that gives a separate meaning to “getDisplay” – if used at the start it shows zero, regardless of what the server holds. Additionally, this APIs require two calls to the server every time, which may be costly and slow.

void initializeCalculator();
Which leads us to a separate API for initialization. We probably need it – to tell the server the UI is ready. However, where does the zero come from? We still don’t want to press anything. So maybe the initializing method can return what to display:

String initializeCalculator();

While this is promising, there is some inside knowledge about what the return value that we’ll need to document, so people from now on will know. Tests will help, but maybe a better name can help:

String initializeAndGetDisplay();

It’s better, but still does two things. For now it will do. Next time I’ll tell you my pick and write some tests.

But before we go, you’re probably thinking – what we did here is a bit of cheating. We should have the API discussion while we write the tests. This is where we should actually do that thinking, not before writing even “@Test“. In fact, before the glorious TDD days, we’d write a design up front (in a 500 page document), and invent that API (hopefully thinking also about alternatives). Did we just jump back in time?

As opposed to a series of posts, our (and my) thinking is not linear. Neither of the “proper” TDD, or “proper” design is how we think. TDD, and development in general, is an iterative process. In this series, so far I’ve been writing and deleting stuff, just because I’ve learned things on the way and even (gasp), changed my mind a couple of times. It’s ok for thinking up front, (I’m a huge fan of thinking!), and it’s ok to let things change on the way. That’s how we think, learn and work and we should embrace it.

Real Life TDD With Spring – The Road to the First Test II

Gil Zilberfeld describes options for API design for TDD Spring applications
Standard
How does TDD look like in real world development of microservices or web interfaces? That's what this series is all about.
1. Introduction2. The requirements3. Test case analysis I4. Test case analysis II
Test case analysis III6. Test case analysis IV7. Setting up a project with Spring Boot8. Which tests to write?
9. API design

Last time, we started thinking about considerations for picking the right test to start with. We decided to start with integration tests. So, which cases do we want to cover?

Obviously, they should capture the main functionality of the system. Going over our test cases, the following are the main capabilities of our system:

  • Pressing keys
  • Displaying results
  • Calculations

All the other cases are either relatively edge cases (error handling, overflows, etc. ) or more complex scenarios. While these are proper behaviors in our system, they can be covered by unit tests (so it seems right now). For our first set of integration tests we want the simpler stuff. Later we might consider adding more cases.

So we can choose the following cases for our integration tests:

  • Pressing keys and displaying results: “1”,”2″,”3″ => “123”
  • Calculations: “1”,”+”,”3″,”=” => 4

While these capture the main capabilities, I’d like to add the initialization case too:

  • Nothing => “0”

Why?

First, this could be important for building the system as a whole. Delivering an end-to-end story that displays “0” is both easy to do (looks like it, anyway) and valuable. It is good feedback for us that our system works.

Second, this is obviously a quick way to get to a working feature. If you’ve already thought a couple of steps ahead (and that’s not a bad thing to do, even with TDD), you’ve figured that passing the calculation tests would take a lot more work than the initialization test. A quick win is something we all can use. Plus, it looks like a very simple test, that will also drive the first API design.

What’s not to like?

Initial Analysis

Let’s think it through. We have a few core assumptions already:

  • We’ll be using TDD for the inner logic, which we didn’t design yet (obviously)
  • We’ll be using REST services as the API, with input and output as strings
  • Input will come in the form of “pressed keys”
  • Output will come out as the form of “displayed result”

If that’s all we go on, we can presume that we need an API that looks like this:

and its REST service wrapper. We press a key, then return what to display. Let’s call it the “pressKey” API for short.

For all other cases, we’ll need this API. However, with the initialization case, no key is being pressed. Hmm.

We have a couple of design options regarding our API suggestion.

  1. The UI will present the 0. The initialized system does not need to be asked what to display. Once the initial zero is displayed, we can use the “pressKey” API.
  2. Use the “pressKey” API, but give it another meaning. For example, use it like this in the beginning:

    or

    In both alternatives, the back-end will know to respond with “0”..
  3. We can split our “pressKey” API into two separate APIs, each in charge of a different operation.

This way, on initialization we just call the “getDisplay” API.

4. Or maybe, we need a separate API for initializing, in addition to the original “pressKey“.

We would call this in the beginning, telling the calculator we’re starting and we’re off. In this version the front-end will display the zero (as with option 1). Or we can use this  version:

Awkward a bit, why would “initialize” return a value?

Decisions, decisions. But you see, we’re starting to think about how we operate the system, as part of the use cases. We want to find the best alignment. The tests will follow.

I’ll give you my pick next time, but here’s your chance to suggest other options (with explanation) and name your favorite.

Real Life TDD With Spring – The Road to the First Test I

Gil Zilberfeld describes the thinking about selecting tests for TDD with Spring
Standard
How does TDD look like in real world development of microservices or web interfaces? That's what this series is all about.
1. Introduction2. The requirements3. Test case analysis I4. Test case analysis II
Test case analysis III6. Test case analysis IV7. Setting up a project with Spring Boot8. Which tests to write?
9. API design

When we set up the project, we ran a couple of tests (integration and unit) to see if the environment we have runs them. There was no actual logic involved, but we got initial feedback that the platform is ready to work as-is.

The next logical step is to set up a couple of real tests. But what kind of tests should we start with, and how do they fit our TDD effort?

The right stuff

In our test analysis, we ran through a lot of cases, and some of them can serve as acceptance tests. As acceptance tests, they prove that the system works for the customer as specified. As such they would operate the UI and the entire system.

The main issue with testing through the UI is volatility – the UI tends to change a lot more than functionality. Which will cause us to change our tests frequently – a big effort, with relatively small value.

Another issue with testing through the UI is that we add another layer (or more) for the tests to pass through. Although they give as a “thumbs up” when they pass, we’re adding more pitfalls for the tests, to fail for the wrong reason.

It’s true that integration tests can fail because of everything in that black box we write. But our UI tests, can fail because of more network issues, security and configuration between the UI and the API.

What happens then?

Let’s talk for a minute about the cost of “maintaining the tests”. Although the test failure is the signal, what happens next is we start investigating everything – the tests, the code, the environment they ran, the build system, and other things as well. This takes time (sometimes a lot). If we find out the tests failed for “the right reason” – the code doesn’t do what we intended it to do – that’s good. They fulfilled their job.

If the tests failed for any other reason – tests are no longer correct, environment issues, missing files that were not added to source control – we call that “waste”. This is not just a waste of time – these failures erode our trust in the tests we have, and reduce our motivation to keep writing them. On top of the wasted investigation time, it sometimes results with homework: Fix the tests, add the file, re-configure the environment.

If maintenance cost is high in integration and system tests, why not just skip all those and just write unit tests? They wouldn’t fail for the wrong reason – they are isolated, they test very small scope, and they are cheap to write.

If we only wrote unit tests, that don’t check the whole system, we won’t have a proof that the system actually works. So as we write tests, selecting the right type of tests becomes a balancing act. The testing pyramid model describes the resulting set of tests.

Ok, so far we agree that we don’t want UI tests, and we’ll need to select API and unit tests. How is that related to TDD?

The Big, The Small and the Ugly

Test-first adds another dimension to the game. In order to use TDD to make progress quickly, we get pulled toward unit tests. Using TDD from the API level is possible, but we won’t move as quickly. We’ll need to build APIs and call them in the tests, add beans to configurations, setup dependencies, all  slower than writing tests for the code itself. In TDD we expect very short cycles of development. Every couple of minutes we have another passing tests. Going outside to the API level, depending on how complex the system is, the cycle can turn to hours (and sometimes days, I’ve seen it) between passing tests.

On the other hand, we still want those bigger tests. We’s want to set up a few API tests, that act as guardrails. They make sure that we’re on the right track, while we’re messing with internal things inside the system. Having these big scope tests can lead us in the right direction.

If we write them BDD-style, they can even tell the story in the domain language, not “ugly” REST API dialect. Remember those test cases? we wrote them in our own language, not using any technobabble. We can use BDD tools (e.g. Cucumber) for that. Or we can simply write tests in a readable manner.

Another advantage of having the bigger tests, is help us think about the APIs of the system. With Test-First (in any scale) we think about how we talk with the system. The integration tests can help us there as well.

If we had all the time in the world, we would cover all the code with all kinds of tests. But we don’t. So we need to choose which cases we want to use as the API tests, and which to leave as unit tests.

Let’s start off with a couple of API tests, then move to TDD at the unit level.

Real Life TDD With Spring – Initial Project Setup

Gil Zilberfeld describes the initial Spring project, tests and TDD setup
Standard
How does TDD look like in real world development of microservices or web interfaces? That's what this series is all about.
1. Introduction2. The requirements3. Test case analysis I4. Test case analysis II
Test case analysis III6. Test case analysis IV7. Setting up a project with Spring Boot8. Which tests to write?
9. API design

Finally! Actual TDD and code and Spring!

Well, we’ll start with Spring first. But first a reminder.

We’ve already constrained ourselves with the architecture. This is going to be a web based, REST services based Spring application. In the general case, the architecture should support building the app in the most reliable, quick and cheap way. In our case, it’s because I want to improve my Spring skillz.

If you want to follow up, I’ll be posting the code on github. I’m going to have a single project, and tag each version of with the post’s name and date. For example, this post is tagged 1.0.

Generation S

The first thing I’ll do is create a project in Spring Boot. It’s the quickest way to set up a Spring based project these days (I used version SpringBoot 2.0).

What needs to go into our project setup? We’ll select a maven project with the following metadata:

Group: tddWithSpring
Artifact: calculatorDisplay

I’m selecting two dependencies for the generation of the project:

  • Web – Obviously, we’re building a web app.
  • JPA – For database access.
  • H2 – In memory database for our integration tests

JPA and H2 are not needed to begin with, since if you recall, we don’t have any persistency requirements yet. We can start without them and add them later, based on our needs. But since I’m running the show, we’ll add both dependencies now. (One of the other thing I did is added and commented out properties for those two in the application.properties file under resources).

Now, let’s press the “Generate Project” button, and… don’t you love technology? We’ve got a starter project. The regular Maven setup includes source files and tests.

But we still need to do some small modification and cleanups to the project before we start

Remove all clutter

First, let’s remove the mvnw files that were created as part of the project generation. We don’t need them. Let that be a lesson to you – not everything that can be generated is actually good for you. .

The generated project already contains a CalculatorDisplayApp class, and an empty CalculatorDisplayApplicationTests test class. Note that this class is for integration tests – you can see it annotated with @SpringBootTest and @RunWith(SpringTestRunner.class). I’ve renamed the class IntegrationTests for now.

Let’s run the integration test (which I’ve renamed also), the one that was generated:

The empty test passes, imagine that. But there’s a whole lot of logs going on while it’s running. These Spring logs maybe helpful (maybe), but for us, it’s just a distraction. Digging around StackOverflow, I found the solution to remove all logging below ERROR level (that’s unneeded DEBUG and INFO lines). It involves adding a logback-test.xml file to the test/java/resources folder. Each line tells the minimal logging for each package. For example:

That’s a lot less noise on the console. With those in, we’ve minimized the logging to something more acceptable.

Run, test, run!

The first thing I usually do to make sure a system runs is to fail a test and then pass it. I do it with the integration test (I’ve added  assertTrue(false)  and then change it to true). The test runs as expected.

However, running this single test clocks in between 11-15 seconds from the command line (using “mvn test“, not including compilation). Interestingly enough, running the same test from within Eclipse (“Run as JUnit Test“) takes around 5-7 seconds. (Times are on my laptop, YMMV).

That tells us a lot about the overhead of running the Spring framework, and also about Maven. The more complex our software gets, we’ll see this overhead grow, and that’s bad for our feedback cycle. We need integration tests, but for our TDD purposes, we’ll need quick running unit tests as well.

Here’s a thought: If I’m already in learning mode, why not use  JUnit 5? Sure, why not. Add the following to the POM file:

To check that JUnit works, I’ve added a regular test class (unsurprisingly called UnitTests), with a small test (same as before). This is a regular JUnit test, and no @RunWith annotation:

This test alone, runs in Eclipse in less than 50 micro-seconds. That’s a very good feedback time.

However, when we run it with maven (“mvn test“), which runs both integration and unit tests, we get again to 10-15 seconds. To run just the unit test, we can run Maven with a specific test class, just the UnitTests class:

Hmmm. This brings the time to around 5-7 seconds. Still not micro-feedback, but a lot better, without getting the whole Spring framework grinding us to a halt.

So, what have we learned?

  • We have a project ready for work, that was sort-of easy to set up.
  • We know our tests are running.
  • We know that integration tests that need Spring have a big overhead to do nothing. We also learned that running tests through Maven adds another overhead that we might want to work around.

Next up: Deciding which tests to write.

Code for this post is here.

Real Life TDD – Test Case Analysis, Part IV

Gil Zilberfeld talks about more tdd examples for the Spring microservice project with integration testing
Standard
How does TDD look like in real world development of microservices or web interfaces? That's what this series is all about.
1. Introduction2. The requirements3. Test case analysis I4. Test case analysis II
Test case analysis III6. Test case analysis IV7. Setting up a project with Spring Boot8. Which tests to write?
9. API design

PM: You know, you could have something built by now. You do call this series “integration testing with Spring TDD” or something.

Me: Yeah, and I would probably build something you did not want. Just a few more cases. Remember, we were talking about actual calculations and special cases?

PM: Ok. Let’s take a step backwards. What happens when we do this?

“5”,”+”,”-“,”3″,”=”

Me: Hmmm. User error. She pressed “+”, then decided “-“. So clearly she changed her mind. “-” cancels “+”, and we’ll see “2”.

PM: If I had a penny for every time a developer said “Clearly”…

Me: Gotcha. So am I right or not?

PM: Let’s go with your intuition. That means that the following sequence behaves the same:

“5”,”+”,”C”,”-“,”3″,”=” => “2”

Because the user canceled the operation and entered a new one. We need to support that too.

Me: It could be. But if we only look at the beginning of the sequence:
“5”,”+”,”C”

Do we see “5”? Or “0”? Do we want to cancel the operation? Or the last number we entered (this time we didn’t start to write it yet). We may have a conflict with a former example.

PM: You’re starting to annoy me.
Me: The feeling is mutual.

PM: Ok. For consistency, “C” should cancel numbers. That means:

“5”,”+”,”C” => “0”
“5”,”+”,”C”,”-“,”3″,”=” => “-3”

Me: Are you sure?
PM: For now.

Good enough. But we’re not done yet. Simple calculator my foot.

The Calculator Runneth Over

Let’s talk calculation overflow. We’ve already discussed the display limits when entering data, and the answer was we can’t – we’ll stop entering new digits. But we can overflow also as a result of a calculation. What happens then?

PM: There’s nothing better than an “E” for error. Show me an “E”.

“9”,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”+”,”1″,”=” => “E”

ME: We didn’t have an “E” before. It opens the door to other questions, like how do you get out of “E” mode.

PM: Any key starts a new thing.

“9”,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”+”,”1″,”=”,”2″ => “2”
“9”,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”+”,”1″,”=”,”C” => “0”

Me: Hmm. And what about an operation key?

PM: Hmm. Then don’t change the display until a digit or a “C” is pressed. Operations stay in “E” mode, and we don’t start a new calculation until a digit is pressed.

“9”,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”9″,”+”,”1″,”=”,”+” => “E”

Right, overflow is done. How about our favorite error, division by zero?

PM: Lucky we’ve already got an “E” mode. Use the same behavior.

“8”,”/”,”0″,”=” => “E”

This is the end

Me: Almost there, a couple more clarifications please, for the cases right after we’ve completed a calculation. What happens when we reset after a calculation?

PM: Show “0”, clearly.

“1”,”+”,”2″,”=”,”C” => “0”

Me: Clearly. How about if we press a digit key?

PM: That’s a start of a new calculation. First we show the new digit:
“1”,”+”,”2″,”=”,”4″ => “4”

PM: Then, it’s a new calculation:
“1”,”+”,”2″,”=”,”4″,”*”,”3″,”=” => “12”

Me: What if the next key is an operation? Is that the continuation of the calculation?

PM: Let me think. Yes it is.

“1”,”+”,”2″,”*”,”4″,”=” => “9”

Me: So we’re keeping the order of operations (multiplications before addition)?

PM: Of course. This is like 1+(2*4).

Me: I feel a disturbance in the force. What does this result in?
“1”,”+”,”2″,”*”,”=”

PM: Well, that’s a 3. I think…
“1”,”+”,”2″,”*”,”=” => “3”

It makes sense. But then, if we look at this one again:
“1”,”+”,”2″,”*”,”4″,”=” => “9”

It’s a conflict – not of an implementation, but of expectations.

PM: Ok, let’s keep it simple for now. We won’t keep track of the order of operations. We do it as input comes in.

“1”,”+”,”2″,”*”,”4″,”=” => “12”

PM: Don’t worry. We’ll get the additional budget for that feature yet. But for now, I think we’ve covered all the functionality. I think. I want to  believe. Did we?

Me: Yup. For now. Funny, it didn’t look like we needed so many examples to figure out a simple calculator.

It Is Nigh

If you’ve made it so far, you’re probably thinking – should I really know all these things prior to writing a single bit of code? Should I analyze all the possible cases before I even fire up Eclipse?

My answer is: You don’t. If you promise to come up with all these cases as you go.

The thing is, there are probably cases I’ve missed here. You’re going to miss some as well. And assuming you do, those cases will either not be coded or tested. That means that the system gets released that way, and in many cases, that’s a risk, sometimes a big one.

We usually don’t wait for the analysis to be over, and sometimes we don’t even wait for it to start. We’re eager to “create” although we still don’t know what we need to develop. That means we’re programming based on a lot of assumptions, some of them are wrong. Based on these assumptions, we’ll write some code that may contain bugs. And that is not so easy to maintain once written.

So, in a nutshell, yes, you do have to think those through.

Right. Enough planning. Time to set up a Spring project.

Real Life TDD – Test Case Analysis, Part III

Gil Zilberfeld continues exploring test cases for the Spring based calculator application
Standard
How does TDD look like in real world development of microservices or web interfaces? That's what this series is all about.
1. Introduction2. The requirements3. Test case analysis I4. Test case analysis II
Test case analysis III6. Test case analysis IV7. Setting up a project with Spring Boot8. Which tests to write?
9. API design

Product Manager: Man, you still haven’t touched your keyboard? I haven’t seen you doing any Spring or TDD or anything technical yet. Are we going anywhere with this?

Me: Of course! It’s calculation time! That’s what you wanted, right? Let’s write some examples.

  • Pressing “=” shows the calculation result. There are no intermittent results shown until it is pressed.

First let’s address the final part – we only have one display. It behaves according to all our examples above, and shows the result in that display. It’s one of those “illuminating” requirements that don’t add anything to our implementation.

Now, let’s start talking calculations. Since we’re not building a “calculator” from scratch, meaning we’re going to use regular computer’s “+”,”-“,”*” and “/” operations, we don’t need to check that the calculation is correct (e.g. 2 plus 2 is 4), but that the result is displayed correctly. This can be tricky in some cases. But let’s start with the plain vanilla things:

Regular operation calculation. Should we have something like this?

“2”,”+”,”3″,”=” => “5”

Yes, we probably should. But how many examples like this do we actually need? Numbers are infinite, we have four operations, and frankly, it’s not us that doing the calculations. We’d probably want one for each operation.

We haven’t discussed so far which tests we want to be unit, and which are going to be integration or end-to-end. But this looks more like a main flow of the calculator operation, so we’d want that kind of test to be wider than the regular unit. We still don’t know about whether it’s instead or in addition to the smaller tests. That’s ok for now.

Tonight’s Specials

Now for some special cases. Do we display negative numbers? We don’t have a way to enter a “-3”, but should we show it as a result?

PM: That’s true, let’s discuss the two cases separately. First, a negative result – yes, I want to show it.

“3”,”-“,”5″,”=” => “-2”

PM: Now, about entering “-2”. We currently don’t have the budget for a “-” button. The users will have to make do with that behavior until v2. (Or in other words, that can be left as an exercise for the student).

PM: What other special cases do you have for me?

Me: Well, similar to the negative numbers, we can talk about division results. We don’t have a “.” key. What do we show and how can the user calculate with floating points?

PM: Well the second part is easy. Since we don’t have budget for a “-” key, do you think we kept some mystery budget for the “.” key? No, we didn’t. That’s V3.

Me: But what about results?

PM: Well, no floating point results. We’ll show rounded results.

Me: You mean like this?

“1”,”0″,”/”,”3″ => “3”    – Rounding down
“1”,”1″,”/”,”3″ => “4”    – Rounding up
“5”,”/”,”2″,”=” => “3”    – Rounding the middle

That last two seem a bit strange. Do you really want that behavior? Because the plain div operation doesn’t round. It truncates, rounding down and then remove the non-integer part.

PM: Ok, I’ve learned a new word. Why do you have to have a separate word for everything? We don’t want weird behavior, we’ll truncate.

“1”,”0″,”/”,”3″ => “3”   – Rounding down
“1”,”1″,”/”,”3″ => “3”   – Rounding up
“5”,”/”,”2″,”=” => “2”   – Rounding the middle

PM: I’m happier now. Can we move on? I’ve got a lunch meeting.

Me: Nope, we’re not done yet. We’ve got a few more cases we’d like to put in example form.

Calculations with zero, but without pressing zero, for example. We know that:

“0”,”+”,”1″,”=” => “1”

But should this work after initializing?
“+”,”1″,”=” => “1”

PM: Yes.

Me: And after reset?
“1”,”C”,”+”,”1″,”=” => “1”

PM: Yes.

Me: And if “C” is pressed after an operation?
“1”,”+”,”1″,”C”,”=” => “1”
“1”,”+”,”1″,”2″,”C”,”=” => “1”
“1”,”+”,”C”,””=” => “0”

PM: Whoa. Hold your horses and cancel that lunch.

That’s becoming weird again. In the first example, “C” means “cancel the last digit”. In the second one, it means “cancel the whole second number”. And in the last one it resets the whole calculation.

Me: What does “C” really mean? And can we call it the Reset key? And if so, why does it called “C”?

PM: You have too many questions. Let’s make it simple. “C” cancels everything since the last operation. If it’s pressed after the calculation it resets the current number.

Let’s revisit the last examples and add a couple:
“1”,”C”,”+”,”1″,”=” => “1”            – Cancels the first number
“1”,”2″,”C”,”+”,”1″,”=” => “1”      – Cancels the first number
“1”,”+”,”1″,”C”,”=” => “1”            – Cancels the last number
“1”,”+”,”1″,”2″,”C”,”=” => “1”      – Cancels the last number
“1”,”+”,”1″,”=”,”C” => “0”            – Resets the display

Me: Riddle me this: what do you expect if we do this:
“5”,”+”,”C”,”-“,”3″,”=”

PM: What do you mean?
Me: Exactly. What does “C” mean after an operation? Does it cancel the operation? If it does, at the end I’ll see “2”. If however, “C” resets the display, we’ll get “-3”.

PM: Where do you come up with all these cases? No user would do this!
Me: If I had a penny for every time a PM said “No user would do this!”…

And for more things a user wouldn’t do, wait for the final part of test case analysis, coming soon.

Real Life TDD – Test Case Analysis, Part II

Gil Zilberfeld continues to discuss TDD test cases for Spring's API testing
Standard
How does TDD look like in real world development of microservices or web interfaces? That's what this series is all about.
1. Introduction2. The requirements3. Test case analysis I4. Test case analysis II
Test case analysis III6. Test case analysis IV7. Setting up a project with Spring Boot8. Which tests to write?
9. API design

So many TDD cases, so many posts. And no Spring in sight yet. I promise, we’ll get there. For now, let’s continue to explore the requirements. Where did we last stop?

  • Pressing any digit key, the display adds that digit to the existing display. The exception is ‘0’ which is presented only if a number different from ‘0’ wasn’t already pressed.

What’s the deal with the zero key behaving differently? An example for regular digits looks like this:

“4”,”5″ => “45”

But for zeros it’s different:

“0”,”5″ => “5”

Of course, we still need to remember that a zero still behaves as a regular digit after another was pressed:

“5”,”0″ => “50”

And let’s not forget the ever shifty:

“0”,”0″=>”0

Did you notice something? We’re collecting lots of examples, and none have yet to do any calculation. That display sure is tricky.

What’s the next requirement?

  • Pressing an operation key does not change the display.

What does that mean? An example,

“6”,”+” => “6”
“7”,”8″,”/” => “78”

However, when we add a number after the operation key, we’ll see new that number:

“6”,”+”,”1″ => “1”
“7”,”8″,”/”,”3″ => “3”

That makes sense. We’re starting to input a new number after the operation. Funnily enough, if we key two operations sequentially, the display works the same:

“6”,”+”,”-” => “6”

We don’t yet know what that means in terms of calculation, but that’s ok. We’ll explore that later.
A more complex set still yields the same behavior:

“6”,”+”,”4″,”-” => “4”

Time for a bit of semantic talk. Is “=” an operation key? We don’t expect the same behavior after doing the calculation. However, it’s a non-digit key, so how should we call it?

Product Manager: No, it’s not an operation key. Operations are “+”,”-“,”*” and “/”. Others are not operation keys, although, we don’t have a special name for their group.

Ok, we don’t have long to go. Before calculations.

  • Pressing “C” resets the calculator

That’s easy enough:

“1”, “C” => “0”

Wait. Is that still true after an operation?

PM: Yes.

Ok then.
“1”, “/”, “C” => “0”

Now, how do we know that the calculator is really reset? It may show me zeros, but is the last number still affecting calculation? That would need to be put into the calculation examples.

Which are coming up next.

All this, and the calculator is not helpful yet. Imagine that.