• Post Reply Bookmark Topic Watch Topic
  • New Topic
programming forums Java Mobile Certification Databases Caching Books Engineering Micro Controllers OS Languages Paradigms IDEs Build Tools Frameworks Application Servers Open Source This Site Careers Other Pie Elite all forums
this forum made possible by our volunteer staff, including ...
Marshals:
  • Campbell Ritchie
  • Jeanne Boyarsky
  • Ron McLeod
  • Paul Clapham
  • Liutauras Vilda
Sheriffs:
  • paul wheaton
  • Rob Spoor
  • Devaka Cooray
Saloon Keepers:
  • Stephan van Hulst
  • Tim Holloway
  • Carey Brown
  • Frits Walraven
  • Tim Moores
Bartenders:
  • Mikalai Zaikin

Using TDD and trying to make a simple calculator.

 
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:I would recommend having a private static Random in the Gesture class, and a static Gesture.random() method that returns a random Gesture using the static random number generator.

As for naming parameters, when I want to compare this instance to a method argument of the same type, I tend to name the parameter 'other', for instance: compareTo(Foo other) or beats(Gesture other).



For some reason I thought it was giving me an error for adding private. Of course, this works. Sheesh, not sure what I did.
As for comparing, I think that can make it more readable.
 
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:
Your assumption is correct. I felt it was redundant. Why is it unorthodox? What would you have used?


I'd pull from my past experience of implementing the equals() method and use "other".

(Edit: Yeah, see Stephan's answer?)

It's a common pattern convention used in methods that compare the current instance with another instance in some way. But you probably didn't know that so you went with what you recently picked up: to make the code read like a grammatically correct sentence or phrase. It's great. Maybe unconventional is a better word to use here rather than unorthodox (even though they're synonyms). Same difference.

Alternatively, I would have just used the singular form, opponent, which I would bet is what many experienced developers would lean towards, see Campbell's code. I don't know if it's just me but I seem to notice these things a lot more than other people I work with. Again, I think it's because I normally read the code out loud so I can hear myself say it.

There's nothing wrong with what you did, it's just a little bit different from what you'd normally see or expect to see. I don't think it's bad at all. On the contrary, I think it makes your code have its own "character" -- Dare to be different and all that jazz.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:I like the ability to call Gesture.getRandomGesture() without instantiating Gesture to call that. (now that I look at it Gesture.getRandomGesture() looks redundant)


You're making progress. You have some sensitivity to redundancy now. There are many others who would just walk right by that and not even bat an eyelash.

Michael Bruce Allen wrote:Still though, I do not like this code and I cannot figure out exactly why.


I'll tell you why I get a yucky feeling from reading it: it takes me too long to figure out what's going on and there's repetition. I think we all have an innate sense for what's elegant and what's not. This code definitely falls under the what's not. As I said before, in low level code, strive for succinctness. See how you feel about one of these options or come up with your own spin on the name:

You don't have to qualify the static values() method with the enum name when you reference it from within Gestures itself. That cuts down on the noise. Since values() returns an array, you can get to any particular element in it using array index notation.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:
You're making progress. You have some sensitivity to redundancy now. There are many others who would just walk right by that and not even bat an eyelash.
....
You don't have to qualify the static values() method with the enum name when you reference it from within Gestures itself. That cuts down on the noise. Since values() returns an array, you can get to any particular element in it using array index notation.



Thanks for noticing. I really feel like I am 10 times better in just 1 day.

I really like the values[index] i never would have thought. Really cool! Really cut out a lot of code.


Ok here is my draft for game mechanics. I want to call it final, but its a draft (I have to be honest with myself)

I renamed getRandomGesture() to randomHand() I just like that. opponentsHand = Gesture.randomHand() works for me. But please let me know what you think.
i also added the moduloAlgorithm. I feel that cuts out code here.
https://github.com/CodeAmend/RockPaperScissors/commits/second-time

//Game Class


//Gesture Class



//Tests




 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Only a couple of comments:

#1 - You don't normally make your test classes extend Assert. If your intent in doing so is to avoid having to qualify the methods in Assert like Assert.assertEquals(), etc. then all you have to do is

Your intent for using extends doesn't fit its semantics so even though it works for what you wanted, it's confusing/misleading to read.

#2 - Scope things as narrowly as possible to ensure that only things that use them can see them. These have too wide a scope:

These are instance variables accessible to the entire RockPaperScissors object and yet only the oneTwoThreeShoot() method uses them. Make them local to oneTwoThreeShoot().
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:I really feel like I am 10 times better in just 1 day.


The code certainly looks 10x better. Good job.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Ok, maybe one more comment to nitpick

I struggle to find a good name for that calculation. I guess what you came up with is as good as the ones I've used, usually "diff" which is kind of — I would just shrug this bit of smelliness off since it's pretty well contained in that one method.

Your return expression is fine, too. Alternatively, you could get cute and use the expression (moduloAlgorithm % 2) != 0 instead. That's the idiom for an odd number filter although you could argue that it would return true for other odd numbers even though you wouldn't expect them to ever occur. (moduloAlgorithm % 2) == 0 is the idiomatic expression to return true for even numbers (also called a "filter expression").
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Everything that just happened in the past few days has been amazing and I am extremely grateful to have people with the heart of a teacher that is willing to work with me. I went ahead and removed the extends Assert and this makes sense. Thank you. I did add a static import of Assert.* that is good enough and does what I need.

If it is ok with you, I would like to close the Paper Rock Scissors Lizard Spock and move back to calculator. I have lots of questions and I will start with them in the next post!


Junilu Lacar wrote:...
Your return expression is fine, too. Alternatively, you could get cute and use the expression (moduloAlgorithm % 2) != 0 instead. That's the idiom for an odd number filter although you could argue that it would return true for other odd numbers even though you wouldn't expect them to ever occur. (moduloAlgorithm % 2) == 0 is the idiomatic expression to return true for even numbers (also called a "filter expression").



I have to say that I do like my version and this is ONLY because of " you could argue that it would return true for other odd numbers even though you wouldn't expect them to ever occur". I feel like in a big project if I trust myself too much to not be mistaken, I might waste some unreasonable time. Anyway.. just a thought.

Here is my finished project on Github
https://github.com/CodeAmend/RockPaperScissors/tree/second-time



 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I am anxious to get the calculator started since I understand things on a new level. So lets start with the questions, what does this calculator do??

I read about finate state machine...
I read about the infix and postfix...

I want to build a rpn calculator that takes a algebraic string if needed.
I would hope to eventually add a GUI to this and depending on the mode it would display either a rpn stack or algebraic.

So how do I go about something like this? What am I thinking of? You told me to forget the user. So here I am trying to think of the most atomic level of TDD. What is first?

I think it would be to take a number and output that number. So input of 9 outputs 9.


Now as far as naming.. I have no idea. I honestly need help figure that stuff out. Pair programming would be amazing. Do you ever do classes or workshops?
 
Saloon Keeper
Posts: 15491
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
For the calculator, I think you need to take a small step back with the TDD, and think about the overall design a little bit first. After all, you can't write tests if you don't know what you're going to work with.

Eventually, you want to create a graphical application that can perform mathematical calculations in both infix and postfix notation. You need a common interface to be able to plug it into the graphical calculator. So what do infix and postfix calculations have in common? They make expressions out of input tokens:

I'll just give you a quick start, so you know how this would look like in the RPN case:

Now, it's your job to think what you want to know about a Token, and write tests for the expectations you have of Token and Calculation. Finally, you can implement this behavior.
 
Stephan van Hulst
Saloon Keeper
Posts: 15491
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
If you want to write tests that you don't want to duplicate for different implementing classes of an interface, you can do it like this:


 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Stephan van Hulst wrote:For the calculator, I think you need to take a small step back with the TDD, and think about the overall design a little bit first. After all, you can't write tests if you don't know what you're going to work with.

Eventually, you want to create a graphical application that can perform mathematical calculations in both infix and postfix notation. You need a common interface to be able to plug it into the graphical calculator. So what do infix and postfix calculations have in common? They make expressions out of input tokens:
...
Now, it's your job to think what you want to know about a Token, and write tests for the expectations you have of Token and Calculation. Finally, you can implement this behavior.



Stephan, I appreciate the response. I have to say this is a bit advanced for a couple reasons (and perhaps I should work on something more simple): First, I do not have the experience to understand what the inner workings of a calculator app is. Second, I think this is probably too much to learn all at once. Now, my goal is to not get overwhelmed and to stay excited about this stuff. If you think this isnt too much for me, maybe I am underestimating myself, but I do not think I can grasp the scope of what a calculator app is right now. I would like to do maybe a few notches up from the Rock Paper Scissors project. So what if I just create a infix to postfix converter? Such as http://scriptasylum.com/tutorials/infix_postfix/algorithms/infix-postfix/

This is still a small step toward the calculator app. Maybe this is what you are saying, but that design you did with those classes and interfaces was clearly an experience leap over what I have the ability of seeing. I am open to your thoughts on this.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Michael, you may not know it but I think you've learned more than you may realize. Your latest response tells me that you've learned how to slow down, and that's really the option that TDD keeps open for you all the time. You can choose to speed up and skip a lot of those small steps, or you can slow way down and do teeny-tiny steps. I tend to agree with you about Stephan's suggestions. His thinking is probably racing way ahead of yours and he's just naturally drawing from his experience. You don't have that kind of experience so it's necessary for you to kind of "dumb it down" so to speak and start simpler, with concepts that you find more digestible at this point in your learning journey. Don't try to walk the tight rope before you learn how to walk across the practice balance beam, right?
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Thank you for the response Junilu. And thank you as well Stephan. I am going to keep Stephans response available until I do a few more small steps and then I will delve into that structure a bit more. It looks really interesting.

 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I did do some research type testing to learn how to work with strings and pull out integers. I was unsure how to set this up. Beings that I am making a calculator I made a BasicCalculator project and added a package called PostfixConverter. This will just convert strings to postfix from infix. I had to learn how to this by trial and error and reading the docs, but this is my process so far.

I started out just extracting a integer from the string and now I want to pull out the second one and later the nth one. This is for learning now. I feel like I am writing these tests just to learn how the code works. I might not be thinking of the calculator internal workings as much right now. I also feel like getFirstInt() will not be used. If anything there will be a recursion that puts the ints to a stack. So I feel my naming convention is wrong.

The problem is I am learning to code and I am also trying to make a calculator. So how can I accomplish this same thing while creating the right tests?
Should tests be usuable the whole time? An example would be if I renamed the getFirstInt() its not going to be a method name in the program, but I still need to get these ints for testing purposes. I am now at a point where I am going to need an array soon and I feel like all my old tests are no good. What do you think?



// PostfixConverter source
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:This is for learning now. I feel like I am writing these tests just to learn how the code works.


Yes, exactly! You use tests to specify how you think the code works or how you think it should work.

Michael Bruce Allen wrote:So how can I accomplish this same thing while creating the right tests?


Rather than worry about whether you're writing the right tests, first ask if you're focusing on the right behavior. Ask yourself, "What behavior should this thing exhibit? What behaviors do I want to focus on first? Does this behavior affect other behaviors later on? Is this something I can build on?"

Then you can ask "Is this test really about this behavior? What does this say about the behavior I want to see in the software?"

It's kind of like when you're building a house, you ask yourself how many rooms you want before asking how many sets of curtains you want to buy. By knowing how many rooms you're going to have, you'll get a better idea how many windows there will be and from there you'll know how many sets of curtains to buy. Does that make sense?
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I've said this before and I'll say it again, but I really think you should skip parsing for now. Parsing is a lot more complicated than you think and I'm afraid you'll just get bogged down in the details. I suggest that you assume you've already parsed the input into the proper data structures for postfix notation.

This goes back to the questions I suggested before. Is parsing the behavior you want to focus on right now? Can you skip it and assume it has happened? If you skip parsing, what other behavior is there?

There's a bit of a balance to strike between just going ahead and testing and doing some up front design. Now, it may seem like you're skipping design when you write a test first but you're really not if by writing the test, you're trying to answer the question "What should the API for this behavior look like?" That's actually a design question. Even the question "What data structures do I need so I can run this test?" involves some design thinking. It's just design at a very low level of detail. I find that design thinking needs to bounce back and forth between high- and low-level details when you're doing TDD. When you're writing your test, you're at the low level. When you're refactoring and implementing, you should also look at the high level. Before you start the next TDD cycle, step back and consider everything from a very high level, then jump back down to low level detailed design. In essence, design thinking is continuously happening when you're doing TDD, at many different levels of abstraction.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:I've said this before and I'll say it again, but I really think you should skip parsing for now. Parsing is a lot more complicated than you think and I'm afraid you'll just get bogged down in the details. I suggest that you assume you've already parsed the input into the proper data structures for postfix notation.



If I have a string "2*3-4/5" and I want it to output "23*45/-" Then how would I skip parsing?
Or are you saying do not do this step right now at all?
My mentor said I should write a "big integration test" that does not pass (obviously) and he said you will do smaller unit tests and pass them until the big integration naturally passes or with a little bit more work passes. This is something that would keep me on track. How would you suggest I write this?

asserEquals("23*45/-", postfix.convert("2*3-4/5")); ??? Then write smaller test?

How would I write tests without parsing this string? Perhaps convert expressionCharArray = "2*3-4/5".toCharArray();??? and use those inputs like that?

I am just looking for a goal or plan here. I do not mind figuring out the parsing. I think I understand how to do it and with recursion it should be doable. But then again, I have never written anything with recursion or ever parsed strings like this, so what do I know
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Do you understand WHY you should start with a big integration test that does not pass? At what point are you supposed to expect this big integration test to pass?

I'm not asking these to disagree with your mentor. What he says makes sense but you have to understand the motivation behind it.

BTW, the answer I'm looking for is not "To keep me on track." If you think it's that, then what's this track that you speak of and why or how would you get off of it if you don't have a big integration test?
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:I am just looking for a goal or plan here. I do not mind figuring out the parsing. I think I understand how to do it and with recursion it should be doable. But then again, I have never written anything with recursion or ever parsed strings like this, so what do I know


Exactly. Recursion is not an easy thing to wrap your head around, even when you've done it before.

I was going to be snarky and remark: "With recursion it should be doable," said no programmer who didn't spend the next two nights trying, ever.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Let me show you how I would short-circuit the whole parsing bit in this calculator problem then. Here's the conversation I'd have with my programming partner(s) or myself if I was doing solo TDD:

Ok, let's skip this whole parsing from infix thing and just pretend that it has already happened. What we'd have is some kind of queue or stream from which we'd read either operands or operators. From what we know about processing postfix notation, we track incoming operands and then perform operations as we encounter operators.

What would the calculator API look like? What are the sequence of events? Let's write the code that we want, from the client's perspective.

Let's start with simple addition. Let's try this:

So, with this code, we're pretending we first see a 5.0 from wherever. Since it's an operand, we tell the calculator to track it for now. Same thing goes for the 2.0. Then, we "see" the "+" operator and trigger the addition operation in the calculator. The calculator does what it needs to do so that when we check the result, we get the sum of 5.0 and 2.0, which should be 7.0, right?

That seems like a reasonable API and I don't see anything in that code that's obviously goofy except for these compile errors.

We'll fix it in a jiffy.

(Pretend we're using Eclipse)

Ctrl+1 - create the Calculator class
Ctrl+1 - add the push(double) method
EDIT: Ctrl+1 - add the add() method
Ctrl+1 - add the result() method, make sure it returns 0.0;

There, it compiles now! Let's run the test (Alt+Shift+X,T)

Red Bar! Yay, progress!
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
(Continued)

So what now? Do we implement a stack?

Well, we could but let's take it slower.

Let's do just enough to make the test pass. What's the least amount of code that would make this test pass?

Well, it's stupid and wrong but we could just make it return 7.0, right?

Ok, let's do it!

(change 0.0 to 7.0)

And now run the test. (Alt+Shift+X,T)

Green Bar - Yay! Progress again!
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
(Continued... simulated TDD conversation)

So now can I implement the add() method?

Well, it's tempting to do that but let's stay with the TDD cycle. There's not much to refactor right now, even though we know result() has a bogus implementation. The next step is to repeat the cycle and "Write a test and see it fail."

What test should we write now?

There are a couple of ways we can go about writing a new test :
1. Write another test to specify a different behavior we want to see from the calculator or
2. Write another test to show that the implementation of the result() is really bogus

Which do you think is the right one at this point? Why?
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:(Continued... simulated TDD conversation)

So now can I implement the add() method?

Well, it's tempting to do that but let's stay with the TDD cycle. There's not much to refactor right now, even though we know result() has a bogus implementation. The next step is to repeat the cycle and "Write a test and see it fail."

What test should we write now?

There are a couple of ways we can go about writing a new test :
1. Write another test to specify a different behavior we want to see from the calculator or
2. Write another test to show that the implementation of the result() is really bogus

Which do you think is the right one at this point? Why?



I say number 1, because until I am very sure that it is calculating properly, I want to use that "bogus" implementation. But I truly do not see what it is bogus right now.

A while back I posted something similar to your push() and add(). Thought mine was more complex, was I going in the right direction at all? Lets say I made it less complex, how would that have been?
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:
I say number 1, because until I am very sure that it is calculating properly, I want to use that "bogus" implementation. But I truly do not see what it is bogus right now.


Hmmm... maybe you didn't follow along with the "conversation" above. The program code at that point would look something like this:

This is the least amount of code that will make that test pass. It's quite bogus, as you can hopefully see now, because it is meant to pass just that one single test.

Another way to look at it is that it is meant to "prove" the theory put forward by the simple_addition() test case. That's this code's singular "truth." However, if you put in any other context, that is, if you try to do any other addition, this code will actually lie to you. Why? Because the only answer it ever gives right now is 7.0! It's like a broken clock: it's right twice a day but wrong the rest of the time. Except this calculator will be right only when the answer is 7.0.

So, how do you unveil the lie and show the world the program code's true nature? Because you're doing TDD, you don't just go ahead and write the correct implementation code as you know (or think you know) it to be. Rather, you write a new test. This new test should "cross-examine" the program code and catch it in its lie. That Red Bar is what gives you "just cause" to go and change that lying program code.

This really goes back to some of things that I wrote earlier in this thread: TDD allows you to focus on one aspect of the code's behavior at a time. Each passing test acts like a vice that locks down one small aspect of that behavior. Each failing test will show you another aspect where the code's behavior is not quite right. In TDD, you oscillate between bad and good, RED BAR - GREEN BAR - RED BAR - GREEN BAR ... etc. You force a slight imbalance, then shift a little to get back on balance. Rinse and repeat. This is the strategy for bringing complexity under control.

So again, I'll ask:

There are a couple of ways we can go about writing a new test :
1. Write another test to specify a different behavior we want to see from the calculator or
2. Write another test to show that the implementation of the result() is really bogus

Which do you think is the right one at this point? Why?

 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:
....

So again, I'll ask:

There are a couple of ways we can go about writing a new test :
1. Write another test to specify a different behavior we want to see from the calculator or
2. Write another test to show that the implementation of the result() is really bogus

Which do you think is the right one at this point? Why?



Of course 2. 100% I think that responding last night after a hard day at a stressful job might have been the culprit.



Michael Bruce Allen wrote:
I say number 1, because until I am very sure that it is calculating properly, I want to use that "bogus" implementation. But I truly do not see what it is bogus right now.



I was thinking you were saying that the method result() was bogus. I did not put 2 and 2 together. Whoops.
 
Stephan van Hulst
Saloon Keeper
Posts: 15491
363
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:Thank you for the response Junilu. And thank you as well Stephan. I am going to keep Stephans response available until I do a few more small steps and then I will delve into that structure a bit more. It looks really interesting.



You two are correct, I was probably getting too enthousiastic :P

I'll be following the progress with a lot of interest.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Continuing the simulated conversation...

Ok, so we're ready to start another cycle with a new test. This test should reveal the current implementation for the liar it really is.

Wow, that's kind of a negative mindset, isn't it? How about we say we're going to put it in another situation so that it can grow and become less one-dimensional?

That does put us in a more positive mindset. I like it! Right now, this calculator only knows how to handle expressions that result in 7.0. Let's throw something new at it. Let's try this:

Now run the tests (Ctrl-Shift-X,T)... Red Bar. Yay, progress!

We can change the program code again since we have a failing test. What's the simplest thing we can do to make this pass? Just making it return 8.0 won't work because then we'd be breaking the first test. Our change has to make all tests pass.

We know we need a stack so can we add it now?

That feels like a pretty big step but I don't see any simpler way around. Let's see if we can limit our changes to 10 additional lines of code though. We still don't want to write too much new code.

Ok, we did it with only 7 additional lines of code and a change to 1 line in result().

Let's run the test. Ctrl-Shift-X,T, - Green bar. Yay! More progress!
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Next step: Refactor

I have found that three simple refactorings covers 80% of the code smells you can eliminate in code:

1. Rename - to clarify intent
2. Extract - to remove duplication and minimize/localize dependencies
3. Compose method - to simplify through abstraction - hiding details of implementation while clarifying intent of the high-level code.

As an exercise, see if you can use any or all of these to clean up the calculator and/or the test cases.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Well, last night I did a bit of coding and about the time when I realized that I wanted the array to have the information in reverse, I thought, perhaps there is a Stack class. Thats when I went to bed... but here is what I did.



 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
That's a lot of code. How much refactoring did you do? From what I see, it can't have been much. That's way too much code to write without refactoring. In fact, the 7 lines of program code and about as many lines of test code that I added already is smelly enough to warrant a few minutes of refactoring.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:I did a bit of coding and about the time when I realized that I wanted the array to have the information in reverse, I thought, perhaps there is a Stack class.


You have to learn to catch yourself when you do that. That is, when you thought, "I want the array to have the information in reverse", you have to ask "What's telling me that?" and if the answer is not "This failing test is telling me that." then back up and write the test.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Junilu Lacar wrote:That's a lot of code. How much refactoring did you do? From what I see, it can't have been much. That's way too much code to write without refactoring. In fact, the 7 lines of program code and about as many lines of test code that I added already is smelly enough to warrant a few minutes of refactoring.



review my commits here if you like.
https://github.com/CodeAmend/calculator/commits/basic-functions

have questions on commits:
When should I commit? At a fail? Or at both a fail and a pass? Or at every little detail?


You have to learn to catch yourself when you do that. That is, when you thought, "I want the array to have the information in reverse", you have to ask "What's telling me that?" and if the answer is not "This failing test is telling me that." then back up and write the test.



Right, once I wanted to do multiple additions or subtractions, I realized I needed a better feature then an ArrayList and it was either write some code to manipulate ArrayList or find a Stack type list.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:
have questions on commits:
When should I commit? At a fail? Or at both a fail and a pass? Or at every little detail?


It really just depends. When you're first learning this technique, it's helpful to save at least the state of the code after each TDD step: Red, Green, Refactor. This allows you to review what you did. When I'm programming for fun or when I'm trying to learn a new technique, I'll usually do that. Usually, however, I'll just save after Green and Refactor because I can safely assume that the new test had failed first before the program code was added to make it pass. As with other things in TDD, the granularity of your commits can be as fine or coarse as you want it to be. You just have to understand that the less often you commit, the harder it will be to recreate and relive the thought process.

One note about your refactoring: it's good that you want to eliminate duplication. However, the way you want to eliminate the initialization code is to do this:

You can eliminate all those calls to initialize() by annotating it with @Before. The JUnit framework will automatically execute a method annotated with @Before before executing any method annotated with @Test. Any valid name will do and initialize() is fine but the normal convention is to name it setUp().

Conversely, a method annotated with @After will be executed after any method annotated with @Test is executed. The conventional name for that method is tearDown() but again, you can use another name if you want.

While we're on the subject of naming conventions, test classes are normally named after the class that is being tested. For example, CalculatorTest is the test class for the Calculator class. RockPaperScissorsGameTest is the test class for the RockPaperScissorsGame class. This comes in handy when you use build management programs like Maven. This is a commonly used convention and it's better to follow it because more and more, build systems will assume this is used by default and you can cut down on having to make custom configurations. Integration tests, by convention, are named after the class being tested + "IT" (for Integration Test).

See this article for more information about these naming conventions: http://zeroturnaround.com/rebellabs/the-correct-way-to-use-integration-tests-in-your-build-process/
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:Right, once I wanted to do multiple additions or subtractions, I realized I needed a better feature then an ArrayList and it was either write some code to manipulate ArrayList or find a Stack type list.


This is why I like to work with lower ranks in the dojo: they always bring a fresh new perspective that is untainted by past experience because they don't have much past experience. I love when this happens because it makes me more aware of my own bias, both in the dojo and when I'm thinking about design.

Because you didn't know better, you used a data structure that you already knew about, the List. The code that you wrote using a List instead of a Stack could actually spur some interesting discussions about design choices and refactoring options. It can certainly lead to a better appreciation for why Stack is a more appropriate data structure to use for this problem. I, on the other hand, couldn't see beyond my bias that came from my past experience in solving this problem. Sure, coding to a Stack right away can save me time but if I were programming with someone who didn't have the same experience, I would have deprived them of a good learning opportunity.

This is another similarity between my Aikido practice and TDD. We often allow new students to fumble around and make mistakes before correcting them. This gives them a chance to see and feel what it's like to do a technique poorly. When they learn how to do it better after some guidance, they have a better understanding of the principles involved, or at least an experience from which they can glean a better understanding of the principles later on when they have sufficiently progressed beyond the mechanics of the technique.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
One more thing about convention: I gave the example of CalculatorTest for the Calculator class. When you have a lot of test cases, putting them all in one test class can get unwieldy. You don't want to have hundreds of test methods in just one test class. When that happens, I start separating categories of unit tests under their own sub classes. For example, I might have these: CalculatorAddSubtractTest, CalculatorMultiplyDivideTest, CalculatorTrigFunctionsTest, CalculatorStatisticalFunctionsTest, CalculatorGraphTest, etc. These may or may not extend a CalculatorBaseTest or CalculatorCommonTest that contains common fixtures and helper methods.
 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator


That's a lot of code. How much refactoring did you do? From what I see, it can't have been much. That's way too much code to write without refactoring. In fact, the 7 lines of program code and about as many lines of test code that I added already is smelly enough to warrant a few minutes of refactoring.



So the question is, now that you have seen my commits, you feel I have moved to quick?
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
Backtracking a bit, let my try to explain why I'm inclined to start out with these tests and skip all that string parsing stuff. I think I said this before: it's about focusing on the essential behavior. Parsing user input can be considered as an "infrastructure" concern and as such is not "essential" to the problem at hand. To see why it's an infrastructure concern, pretend that there are alternative ways of getting the expression to calculate, like say from a file, or a database, or a web service call. These are all infrastructure concerns. So to simplify things, I eliminate the source as a consideration when I write the initial tests.

You might argue that you're still dealing with a String that needs to be parsed, whatever the input source may happen to be. That's debatable but regardless, I'd go back to separation of concerns and say that I'm going to imagine that there's something else that's responsible for parsing a String into proper operands and operators. That thing is what will drive the calculator. When you think of the design this way, it makes more sense to define the calculator API to accept proper numeric values and have methods that are called in response to encountering an operator, the way I showed.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator

Michael Bruce Allen wrote:So the question is, now that you have seen my commits, you feel I have moved to quick?


No, not at all. You're actually moving along nicely. Looks to me like you get the idea behind it now. Keep going, it's awesome to see your progress.
 
Junilu Lacar
Sheriff
Posts: 17644
300
Mac Android IntelliJ IDE Eclipse IDE Spring Debian Java Ubuntu Linux
  • Mark post as helpful
  • send pies
    Number of slices to send:
    Optional 'thank-you' note:
  • Quote
  • Report post to moderator
I'll give you a nudge in the direction of refactoring though. All those calc.push() calls in the tests are starting to get noisy. That's a code smell. You have to pay attention to the cleanliness of your test code as well. Think of a way to cut that noise down by boiling down those calls to their essence. What is really happening when we call calc.push() multiple times in row? Talk it out with yourself. Try to explain it to yourself in more abstract terms. The refactoring I'm thinking about is "Extract Method" and perhaps a few renames.
 
reply
    Bookmark Topic Watch Topic
  • New Topic