• Post Reply Bookmark Topic Watch Topic
  • New Topic

Using TDD and trying to make a simple calculator.  RSS feed

 
Michael Bruce Allen
Ranch Hand
Posts: 87
2
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I am learning TDD and using SOLID principals to make a calculator that can take a string "6+8/(2-4*5)" and calculate it properly. So I decided to start with "1+2" and want it to return "3". I realized really quickly that I do not know what to even look up. I started doing searches and learned about a couple libraries with Classes called Matcher and Pattern, I learned of tokenizer, and I also learned of split. The problem is, I have no idea where to start reading. Would you be so kind in to helping me without telling me the answers?

so lets say I have "12345+123" then perhaps
would i use a for loop and use charAt(int)?
Woud I use reg expression to split?
I really jave no idea how to go about this.
 
Jeanne Boyarsky
author & internet detective
Sheriff
Posts: 37395
531
Eclipse IDE Java VI Editor
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Michael,
Welcome to CodeRanch!

I can think of two simpler scenarios for you to test first.

1) What should happen if you pass in an empty string? Throw an exception? Which one?
2) What should happen if you pass in "3". I'm thinking it should return "3".

If you are new to programming, I wouldn't use a regular expression. You can just use a loop. Once you have the two cases above coded, you can look for an operator in the string and use substring for before/after it.
 
Winston Gutkowski
Bartender
Posts: 10573
65
Eclipse IDE Hibernate Ubuntu
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Michael Bruce Allen wrote:I am learning TDD and using SOLID principals to make a calculator that can take a string "6+8/(2-4*5)" and calculate it properly. So I decided to start with "1+2" and want it to return "3".

Sounds reasonable; however, you then (I presume) might also have "1-2", "1*2" and "1/2". What do you want them to return? Particularly the last one.

I realized really quickly that I do not know what to even look up. I started doing searches and learned about a couple libraries with Classes called Matcher and Pattern, I learned of tokenizer, and I also learned of split. The problem is, I have no idea where to start reading. Would you be so kind in to helping me without telling me the answers?

I agree with Jeanne. At this point, I'd avoid regular expressions - which basically rules out Matcher and Pattern - and concentrate on going through your String character by character (which will need charAt()).

I really [h]ave no idea how to go about this.

Not surprising. This is not a simple problem.

My first suggestion: Forget TDD, forget SOLID - in fact, forget Java for the moment.

Imagine you have a reasonably intelligent 10-year old child who doesn't know Java. How would you explain to them how to evaluate the string"6+8/(2-4*5)"? In detail.
Specifically:
  • What do they have to do, precisely, and why?
  • Is there any knowledge they need before they start? (Hint: what's the result of "2-4*5", and why?)

  • Once you have a set of instructions in English (and not before), then think how to translate them into Java.

    And when you do that, think about what might go wrong. For example, suppose you get the string "6?8/(2-4*5)", or (more difficult) "6+8(2-4*5)" - what do you want your program to do?
    (Hint: You may find that you have to go through the string more than once)

    Another thing that might be useful to know: The way we write arithmetic is called "infix notation" - and it's not a natural fit for programs. An alternative form is called "postfix", which is used in Reverse Polish notation, and that is extremely easy for a program to parse.

    And luckily for you, a very clever chap called Edgar Dijkstra invented a way of translating one to the other called the Shunting Yard algorithm.

    Now how exactly you fit all this in with TDD I'm not quite sure. I'm not an expert. But I'll bet that all the thinking (and reading) you're going to have to do will probably suggest some very good tests, and hopefully I've given you somewhere to start.

    Good luck.

    Winston
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    First off, I want to applaud you for wanting to learn TDD -- more programmers need to be doing this.

    The calculator program is probably not ideal for a first-time TDD project although if you already have experience in Java and know about infix to postfix it might not be too bad. That said, if your goal is to learn TDD then I would suggest you start out with a simpler version of the program, without the infix-to-postfix parsing. Assume that's already done. Start out by setting up a postfix stack programmatically and then use TDD to work out the code to perform the calculations properly. Once you have that working, you can add on infix-to-postfix parsing that will populate the stack. This is kind of working backwards but you have to get used to that because TDD feels a lot like working backwards anyway.

     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    I'd imagine you'd have tests like this:
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    You'll also want to read about finite state machines and calculators
     
    Michael Bruce Allen
    Ranch Hand
    Posts: 87
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    I really appreciate all of the comments!

    I was going to ask about rpn. I have a HP50g calculator and I learned rpn on that. I love rpn. So maybe this is the best way to go about it.

    Junilu Lacar : Those examples actually helped me a lot. And yes, I found a great mentor that is helping me learn to program very well. He insists on SOLID and TDD and Law of Demeter and Polymorphism and all these cool things.

    Winston Gutkowski : Thank you for all that! Especially, the Shunting Yard algorithm. I like how you say forget all coding and think like explaining to a 10-year old.

    Jeanne Boyarsky : These are good tests thank you. I will have to learn about throwing exceptions soon.


    I just talked to my mentor today and I FINALLY grasped an aspect of TDD. He said just make a test that returns the number 3. So send it a string "3" and literally build a method that returns 3. Then change the input string to 4 and the test will fail. Now make both tests work. Then put different numbers and no number. And so on... Pretty cool. This is big for me, because I just did not understand TDD, I guess, much at all.


    Thanks to all your help. If you guys like I can attach a git link.
    https://github.com/CodeAmend/calculator/tree/testing

    I now created one passing test (very very simple code which will work)
    the fail test shows when I change the number - now I need to refactor the code and add a bit more.

    Keeping it that simple, now helps me see how I am going to bit small chunks of this code off.


    Thanks again.


     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Michael Bruce Allen wrote:
    I just talked to my mentor today and I FINALLY grasped an aspect of TDD. He said just make a test that returns the number 3. So send it a string "3" and literally build a method that returns 3. Then change the input string to 4 and the test will fail. Now make both tests work. Then put different numbers and no number. And so on... Pretty cool. This is big for me, because I just did not understand TDD, I guess, much at all.

    Some things to note about this:
    1. TDD is not so much about making very small steps like this but rather keeping your options open to making very small steps like this when you're not quite sure how to proceed. As you get more comfortable with TDD, you'll probably start making bigger steps but the option should always be there to go smaller.
    2. The idea is to work backwards from the result rather towards a desired result. I find that this helps you visualize where you want to be while still keeping your options open for how you can get there.
    3. You need to have at least a general plan in mind though, especially when the problem and its solution is fairly well-known. I have found that you can easily end up painting yourself in a corner if you just "wing it," especially through TDD.
    4. For me, TDD is really more about keeping as many design options open for as long as possible. Tests are used to help drive you towards making better design choices.
     
    Michael Bruce Allen
    Ranch Hand
    Posts: 87
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Junilu Lacar wrote:
    Some things to note about this:
    1. TDD is not so much about making very small steps like this but rather keeping your options open to making very small steps like this when you're not quite sure how to proceed. As you get more comfortable with TDD, you'll probably start making bigger steps but the option should always be there to go smaller.
    2. The idea is to work backwards from the result rather towards a desired result. I find that this helps you visualize where you want to be while still keeping your options open for how you can get there.
    3. You need to have at least a general plan in mind though, especially when the problem and its solution is fairly well-known. I have found that you can easily end up painting yourself in a corner if you just "wing it," especially through TDD.
    4. For me, TDD is really more about keeping as many design options open for as long as possible. Tests are used to help drive you towards making better design choices.


    It looks like I did misunderstand the mentor. I started with the most atomic way I could think, this way, I can learn one thing at a time. He also mentioned that I should have one big integration test that fails, and then make smaller tests that knock away at getting little pieces of the big integration test done and finally after the small tests are passing, naturally (or maybe with a bit of work) the big integration will pass as well.

    I think this might be what you are saying?
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Michael Bruce Allen wrote:He also mentioned that I should have one big integration test that fails, and then make smaller tests that knock away at getting little pieces of the big integration test done and finally after the small tests are passing, naturally (or maybe with a bit of work) the big integration will pass as well.

    I think this might be what you are saying?

    That's certainly sound advice but it's not exactly the point(s) I was trying to make. What I shared were just a few things that are from my experience in learning and understanding TDD, things that might not be very obvious to someone like yourself who is concentrating on the step-by-step mechanics. I find that TDD really makes me focus on design -- the tests are just a means to an end really.

    You might hear "Shu Ha Ri" mentioned at some point. This is kind of what I'm getting at.

    For example, the thing with just returning "3" -- that's a very small step. If you were not doing TDD, you'd probably never do that. In fact, I've often been told in the past that it's a "Bass Ackwards" way of thinking and I agree, it's kind of backwards from what you'd "normally" do but after a while, you get used to it and it becomes the new "normal". But you don't always have to do it that way when you're TDD'ing. The option is just there for you to do it.

    So as a beginner in the Shu stage of learning, you want to follow the form and do those very small steps. Following the TDD cycle helps you get a feel for the technique, understand the mechanics, and see how they affect your design choices and software. When you get better, you move to the Ha stage and start pushing the edges. You can start moving away from all those small steps and perhaps make bigger steps. Maybe you figure out when it OK to not refactor right away or write code before you write tests. That's not unheard of. Kent Beck has admitted to doing that in the past and so have other gurus of TDD. They do note, however, that they are certainly more comfortable and more confident in their code when they write tests first.
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 7928
    143
    • Likes 1
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    I have nothing much to add to the discussion (Junilu is handling it nicely!) but I just want to tell you to keep your head up, Michael. I think it's awesome that they're starting early with these concepts.
     
    Winston Gutkowski
    Bartender
    Posts: 10573
    65
    Eclipse IDE Hibernate Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Junilu Lacar wrote:For example, the thing with just returning "3" -- that's a very small step. If you were not doing TDD, you'd probably never do that.

    Hmmm. Not sure I agree there ... but I'm also not sure if what I'm "disagreeing with" is actually what you're saying.

    I certainly think that one of the "tasks" I would come up with if I was designing this (and therefore one of the tests in my test rig) is "convert numeric string to number", and another would be "find number in expression", but I suspect I would come at it from a completely different standpoint, which is:
    "I have an expression which has a bunch of "things" in it (one of which is a sub-expression), so I need to understand what those "things" are."
    - which I suppose you could call "syntax analysis".

    I have to admit to being totally baffed as to how I would approach a problem like this from a test-based point of view though - even though I think I'm pretty aware of its "nature". Which of course is why I'm not a TDD expert.

    Any pointers for some good reading on the subject would be much appreciated, Junilu. Preferably something slim to start with.

    Winston
     
    Stephan van Hulst
    Saloon Keeper
    Posts: 7928
    143
    • Likes 1
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    One of the biggest eye openers I've had was while reading the following article: http://www.objectmentor.com/resources/articles/xpepisode.htm

    When I write new software, I start thinking of the things I need, write types and method stubs for them (so I have something to look at and think about) and when I think of the behavior I expect from such a method (and JavaDoc it!), I immediately write the test to validate that behavior. When I'm happy with my design, I start implementing the methods, and turning the tests green.

    Of course, along the way, I will want to break up my implementations using simpler methods and types, so then I repeat the process for those smaller parts: write outlines, write tests, implement methods.

    Often, I will hit situations I haven't thought of, or need a new feature. This may require me to refactor the code. No problem! Add new types and methods if necessary, update the tests to reflect the new expected behavior, and then make them pass.

    Note that it's not important to write all encompassing tests suites. It's important to have a measure of confidence in the quality of your code. If you add something new to your application and all your tests are still passing, that means that the new feature didn't break existing code. When you discover a bug, you found a situation that you didn't test for, so now is a great opportunity to write that test as well, so it doesn't happen again.

    TDD isn't about writing all the tests for every situation before you do anything else. It is about describing what you expect your code to do, in a language that the testing software understands. I consider tests to be very close to documentation actually, especially if you write them in a clear way.
     
    Winston Gutkowski
    Bartender
    Posts: 10573
    65
    Eclipse IDE Hibernate Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Stephan van Hulst wrote:One of the biggest eye openers I've had was while reading the following article: http://www.objectmentor.com/resources/articles/xpepisode.htm
    ...
    TDD isn't about writing all the tests for every situation before you do anything else. It is about describing what you expect your code to do, in a language that the testing software understands. I consider tests to be very close to documentation actually, especially if you write them in a clear way.

    Weirdly enough although, as I say, I come at it from a different standpoint, I agree.

    Going through the link right now. Very interesting. In the words of Bart Simpson: Have a cow, man.

    Winston
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Stephan van Hulst wrote:I consider tests to be very close to documentation actually, especially if you write them in a clear way.

    Yes, absolutely. Well-written tests are living detailed design specifications -- M$ Word and Visio need not apply
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Winston Gutkowski wrote:
    Junilu Lacar wrote:For example, the thing with just returning "3" -- that's a very small step. If you were not doing TDD, you'd probably never do that.

    Hmmm. Not sure I agree there ... but I'm also not sure if what I'm "disagreeing with" is actually what you're saying.

    What I meant was that if you aren't doing TDD, I would be very surprised if you started by writing this code:

    And then follow that immediately with a test that's like this:

    That doesn't follow the usual thought process when you're not doing TDD. If, however, you go about it backwards and write the test first so you can specify how you want the add method to be used, the next very small step when you are doing TDD is to skip all the thinking and coding that would go in between and go straight to making the test pass. It's a very small step but it's one that many people find hard to take because it feels silly and not very productive. "Why would I fake it? That doesn't make sense! Why don't I just go ahead and implement the algorithm?" is a typical reaction I've heard.

    The typical thought process, as implied in your response, is to start from the input and work out the details for parsing strings, yada, yada, yada. I would skip all that and go straight to dealing with the "native" format of the domain: numbers. I don't care how I got those numbers, I'll deal with that later.

    Now, this totally could just be my style of attacking problems that has emerged from doing things in this "Bass Ackwards" manner but I find that it fits in with the general strategy. If you would normally do something one way when you're not doing TDD, make a 180 degree turn (I like to say tenkan) and do it the opposite way or from the opposite point of view.

    There are a lot of concepts in programming besides TDD that fit in with "tenkan" in my mind. For example, using Dependency Injection instead of a service lookup demonstrates a 180 shift in approach. The Tell Don't Ask principle in object-oriented design is another.
     
    Michael Bruce Allen
    Ranch Hand
    Posts: 87
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    My mentor is very good, but sometimes I think he doesnt realize how hard these things are for me lol.

    Current situation.
    https://github.com/CodeAmend/calculator/tree/testing

    I am litterally at a standstill in what to do next. The assertEquals("1+3", "3") . I just have no idea.....
    I think there are a few core concepts that I should know and I want to learn them.

    last couple lines from email from my mentor
    So it's OK how you have done it because you have started to by testing that a single number evaluates to itself, which is really the most "atomic" concept to start with. Now write also an "unit" test which evaluates to 9, like this:

    assertEquals(calc.evaluate(new Number(9)), 9.0, 0.00001);

    Then make this one pass.

    And then go to the next feature by writing an integration test like:

    assertEquals (calc.evaluate("1+2"), 3.0, 0.00001);

    And then the unit test:

    n1 = new Number (1);
    n2 = new Number (2);
    add = new Addition (n1, n2);
    assertEquals (calc.evaluate(add), 3.0, 0.00001);

    And then make this pass.




    I am just unsure what to learn next.
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Michael Bruce Allen wrote:I am just unsure what to learn next.

    Besides the basic flow of Red-Green-Refactor, what exactly have you learned so far? This seems to be the biggest hump you have to get past when learning TDD. I ran into this when I was learning and I think so do most folks. After becoming familiar with the mechanics of the technique, you feel lost because now you have to think deeper about design and how the tests are helping you make better design choices.

    Think back: have you asked yourself any of the following questions?

    1. Is calc.evaluate(whatever) the best API? Is it natural to program to? Is it intuitive? Does it present any difficulties in adding more functionality to the calculator? Does it make testing difficult or easy?
    2. Are there other ways I could make this API? What about calc.add(...), calc.multiply(...), calc.subtract(...), etc. ? Would that be a better way to go? Why? Why not?
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Here's another key for me when I was learning TDD: Understanding the role and importance of Refactoring.

    I think refactoring tends to take a backseat in the TDD learner's mind, at least in the early going. It's the last step of the cycle and it doesn't have much to do with testing. But IMO, testing is not really the most important part of TDD even though much of your focus seems to revolve around writing and running tests. For me, refactoring and all the thinking that goes along with it is really what makes TDD an effective technique. Tests and frequent testing are what facilitates the mind shift and modes of thinking necessary to effectively refactor.

    So, after learning the flow and mechanics of Red-Green-Refactor, I suggest you read the book "Clean Code" by Robert Martin and start applying clean code practices (choose good names, eliminate duplication, clarify intent). This covers 80% of the refactoring you will do: rename, extract, and compose method and will lead you to a deeper understanding of the benefits of TDD. Read and re-read the code more often and get really good at recognizing bad names and finding better names to use, get better at smelling out and eliminating duplication, and using refactorings like Compose Method to get your code to a more SLAP-y state.
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Michael Bruce Allen wrote:
    assertEquals(calc.evaluate(new Number(9)), 9.0, 0.00001);

    Be really careful with the semantics of the assertEquals method. The signature is assertEquals(expected, actual). You are using it in reverse though. It may not make much of a difference as far as outcome is concerned but the mistake has at least two undesirable consequences:

    1. It makes your test less readable -- programmers who are used to the semantics of expected before actual will read it and most likely have to do a double take. As small as it may be, it is still additional cognitive load on the reader.
    2. The rookie mistake casts doubt on the rest of the test code. If you didn't catch this detail, what other details have you missed?

    I prefer using the more fluent APIs, like this:

     
    Michael Bruce Allen
    Ranch Hand
    Posts: 87
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Junilu Lacar wrote:
    Michael Bruce Allen wrote:I am just unsure what to learn next.

    Besides the basic flow of Red-Green-Refactor, what exactly have you learned so far? This seems to be the biggest hump you have to get past when learning TDD. I ran into this when I was learning and I think so do most folks. After becoming familiar with the mechanics of the technique, you feel lost because now you have to think deeper about design and how the tests are helping you make better design choices.

    Think back: have you asked yourself any of the following questions?

    1. Is calc.evaluate(whatever) the best API? Is it natural to program to? Is it intuitive? Does it present any difficulties in adding more functionality to the calculator? Does it make testing difficult or easy?
    2. Are there other ways I could make this API? What about calc.add(...), calc.multiply(...), calc.subtract(...), etc. ? Would that be a better way to go? Why? Why not?


    I am not sure that I can truly answer these right now. I imagine as I go farther I will rename and refactor a lot of code. I am starting to sense now that I may need to refactor soon. I figured also the the questions you are asking me will answer as I run more and more TDD tests.

    https://github.com/CodeAmend/calculator/blob/testing/test/com/calculator/SimpleAdditionString.java
    Here is my current tests.

    What do you think?

    I feel I have learned a bit about how TDD works and I have also learned about String subString method and a bit of regex.
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Michael Bruce Allen wrote:

    1. Is calc.evaluate(whatever) the best API? Is it natural to program to? Is it intuitive? Does it present any difficulties in adding more functionality to the calculator? Does it make testing difficult or easy?
    2. Are there other ways I could make this API? What about calc.add(...), calc.multiply(...), calc.subtract(...), etc. ? Would that be a better way to go? Why? Why not?

    I am not sure that I can truly answer these right now.

    Given your code now, you actually can, it's just that you probably haven't thought about it enough. Let me rephrase a little bit:

    1. You already know that a typical calculator program involves RPN - is that where your calculator design is heading? I don't get a sense that it is.
    2. Did you have an overall design in mind when you wrote those tests? If so, how do these tests/methods fit into that design?
    3. What about this parser class, is that going to help convert from infix to postfix?
    4. How do the parser methods parseCalculationFromString() and getOperand() fit into the overall design? How do you imagine these will be used in your program?
    5. Are the tests representative of how the methods are used in the calculator program? Are they good examples?

    Reading through your code, I get a sense that you're programming in the small without having a clear "big picture" in your mind. This will get you in trouble. I've done that before and more often than not, I'd end up painting myself into a corner which I bet is probably how you feel.

    Like I said before, the program design for a calculator is fairly well known and you have to at least have an idea of how the different parts of it will come together. You're going to end up with a few classes and corresponding test classes. Look through your SimpleAdditionString test class and ask yourself "Do these tests belong here?", "Do these tests belong together?" and "Are these tests at the same level of abstraction?" For example, are simpleStringAddition and findOpperands at the same level of abstraction? BTW, there's only be one 'p' in "operand." And how does testing for other operands besides addition fit in with the test class name SimpleAdditionString?

    Your test code needs to tell a coherent story (as does your program code). The test class name gives you a hint of what kind of tests are in it. The test names themselves should give a hint as to what aspect of the design is being focused on. Your current "test story" is all over the place and doesn't have a coherent theme running through it. This is a smell that is telling you to refactor right now and perhaps gather your thoughts and get them a little bit more organized.

    Michael Bruce Allen wrote:I have also learned about String subString method and a bit of regex.

    That's good but not really what I was driving at. I was looking more for something like "There's a lot more to think about when I'm doing TDD than just testing." Again, for me, TDD is not about testing. It's about design. You write tests to try out a design. Each test should tell you something about a certain aspect of the design. Overall, when you read through a test class you should get a sense of a "story" developing about what the class does, starting from the list of test cases which serves as kind of an outline, and then each test itself going into a specific detail of the design. Do your tests fit the bill?

    I'll reiterate my suggestion: Skip all the parsing stuff and go straight to an RPN with a prebuilt stack. The "big picture" thinking involved with this approach: I have something that will do parsing and something that will do the calculations. I want to keep these separate so I will concentrate on the calculation for now. The calculation part will work with Reverse Polish Notation.

    I would probably even try to fill in a few more high-level details such as where the stack of operands comes in, which program part will be responsible for managing the stack, what kind of interactions/collaborations will the different parts have.
     
    Campbell Ritchie
    Marshal
    Posts: 56221
    171
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Moved to different forum because the discussion seems too difficult for “beginning”.
     
    Michael Bruce Allen
    Ranch Hand
    Posts: 87
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Junilu Lacar wrote:
    1. You already know that a typical calculator program involves RPN - is that where your calculator design is heading? I don't get a sense that it is.
    2. Did you have an overall design in mind when you wrote those tests? If so, how do these tests/methods fit into that design?
    3. What about this parser class, is that going to help convert from infix to postfix?
    4. How do the parser methods parseCalculationFromString() and getOperand() fit into the overall design? How do you imagine these will be used in your program?
    5. Are the tests representative of how the methods are used in the calculator program? Are they good examples?


    1) I want to use rpn and I actually wanted to ask that before it was mentioned above. This project is probably too advanced for me right now, but that only means I will have to keep going back and refining my thinking. Understand that I am learning something new in many ways all at the same time. TDD, Regex, Polymorphism, SOLID (which I still find hard to grasp right now), and all these new terms. I have to think of all this at once. To be honest, I just want to make some simpler designs right now that dont involve so much thinking about many different NEW things. My mentor is abroad and will be back soon, I will talk to him, but I do not mind keeping this up.

    2) The overall design is important to me, and no I do not have that in mind. I think this is why I do not feel so good. The mentor told me to make a calculator app that adds "1+2". That was my vision. If I had to think about what I would want for me personally, it would be default RPN user input. It would do + - / * and forget the parentheses right now, because it doesnt matter. Then when I am done with this, make an algebraic mode that can read parentheses correctly. I like this vision. I want to keep it simple though.

    3) probably not. my vision was too small when I made it. I just wanted to solve "1+2"; Errors in user input are not even accounted for yet...

    4) Again, these were to solve a very small problem. perhaps paseCalculationFromString might be useful for algebraic mode only.And maybe getOperand can be used, this I am just not sure of yet.

    5) These are all tests to get to "1+2"


    Question: If I wanted to make a RPN console based calculator that shows a number stack of the most recent 10, how would I write this story in tests? How would I write the big integration test? I have no idea what it would look like.


    Reading all this - and I appreciate every single bit of it - I am starting to feel better about TDD and the big picture. This type of thinking just seems to fit what I want to do. Thank you. It seems my mentor is saying the same things as you in most of these topics. What he is going to do is probably pair program with me soon and walk through my thinking. If not pair program an email intervention. Then we will refactor. He knows that I am pretty new at this stuff. He just wants me to try to use some of what we have learned on my own, then later we refactor.




     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Michael Bruce Allen wrote:3) probably not. my vision was too small when I made it. I just wanted to solve "1+2"; Errors in user input are not even accounted for yet...

    As simple as it may seem, this is too big of a step. First, it involves parsing and that's a bigger task than "1+2" would lead you to believe. That's why I suggested to skip it and go straight to the calculation part. Assuming that the parsing task has already been completed and with the RPN algorithm in mind, what would the program state look like? What would be in your stack? What triggers the expression to be evaluated?

    This is what I said before about working backwards from the end state. It's a bit like the kind of thinking you need when solving the Tower of Hanoi problem recursively. You think all the way to the very end, to the very last step and then work your way backwards. In the Tower of Hanoi problem, you take the end case of having to move N-1 disks from the intermediate pole to the target pole after moving the Nth disk from the origin pole to the target pole. Then work back to moving N-1 disks from the origin pole to the intermediate pole. and so on.

    Likewise, you can Tower of Hanoi your calculator problem by skipping right down to the end, where you've already done all the parsing and building up the stack and all you have to do is pop two operands and perform the calculation specified by the operator. And if you think about it enough, you'll realize there's an even smaller step that comes after this: There are no more operators and all you have to do is pop the operand that's on the stack to give you the final result. Again, you have to keep the overall design picture in mind and be aware that you are going to have some kind of state machine. The test I would experiment with would be something like this:


    The comment there suggests that your test code should be an example of what actually happens when you have no more operands operators. That might lead me to write:

    And then I can think about how I should set the program state so this makes sense. I'd also ask "is calc.pop() at the correct level of abstraction?"

    It's kind of hard to do this in this medium -- pair programming would be much better but at least if you run this through in your head as you read it and imagine a conversation between a programming duo, you can kind of get a feel for where the thought process is heading.
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    There may be some flaws in the Tower of Hanoi thought process analogy but the point is that you want to avoid getting lost in the details. By abstracting away a bunch of small problems and treating it as one big problem, you can focus on just one simple problem. In the Tower of Hanoi, you can think of the big, abstract problem as "moving the stack of N-1 disks from pole A to pole B" and the small, simple problem as "moving disk N from Pole A to Pole C".

    Likewise, with the calculator, your big, abstract problem is "parsing a string into operands and operators and ordering them in RPN" and your small problem is "perform simple addition".

    It's not a new technique, just kind of a new spin to the old "functional decomposition" approach because you're using tests to drive the thought process.
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    When you're doing TDD, it's important to keep a TODO list. Here's what I might have after writing the code in my last response:

    The TODO list lets you stay focused on the current step in the TDD cycle while still remembering all the different things that pop up as you're working though the current step. When programming in a pair, the navigator usually has the duty of maintaining the TODO list. This way, the driver can focus on the current step and not get sidetracked.
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    I suggest you study the Postfix Algorithm -- the test I gave is already mentioned in it. Try to code that out and then write tests and code for the rest of the algorithm. Don't worry about parsing strings just yet. Just set up a stack as a fixture in your test and populate it programmatically as part of your test setup.
     
    Michael Bruce Allen
    Ranch Hand
    Posts: 87
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Junilu Lacar wrote:When you're doing TDD, it's important to keep a TODO list. Here's what I might have after writing the code in my last response:

    ....


    I am still a bit unsure if stack should be mentioned yet or if calc.pop() is a good enough level of abstraction. This is my limited experience. So based off of everything I have learned, I tried to start fresh and write a store, a simple one, that will take rpn input. Let me know if this is the right idea. I tried to make this as clear as possible without worrying about much of any of how the code actually works, but more on what the inputs would be. I also made it so that anyone who reads this would see that it would show on a console as well.




    And yes.. the postfix algorithm I read the other day and saved it for rereading. Thank you.
    edit: You mentioned that the test you gave me already mentions this, is this the bowling score app? Otherwise, which test?
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Michael Bruce Allen wrote:I also made it so that anyone who reads this would see that it would show on a console as well.


    Waaay too much code for a first test or for any test, in fact. It also violates FIRST, in particular that tests should be self-validating. That is, you should have an assert statement in there somewhere that would cause the test to fail if the program wasn't working the way it should be working.

    You are experiencing how difficult it is to divorce your thinking from user input. You have to learn how to do that though. Forget about processing user input for now. And start with a simpler expression. Have you tried starting out with a trivial case? Like an expression with just a single number and no operator? What should happen when the calculator is given only a single number and then you press the "=" button? That about as trivial as you can get.
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Also, is stack the right object to have all those methods? I don't think so.
     
    Michael Bruce Allen
    Ranch Hand
    Posts: 87
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Winston Gutkowski wrote:
    ...
    My first suggestion: Forget TDD, forget SOLID - in fact, forget Java for the moment.

    Imagine you have a reasonably intelligent 10-year old child who doesn't know Java. How would you explain to them how to evaluate the string"6+8/(2-4*5)"? In detail.
    Specifically:
  • What do they have to do, precisely, and why?
  • Is there any knowledge they need before they start? (Hint: what's the result of "2-4*5", and why?)

  • Once you have a set of instructions in English (and not before), then think how to translate them into Java.

    And when you do that, think about what might go wrong. For example, suppose you get the string "6?8/(2-4*5)", or (more difficult) "6+8(2-4*5)" - what do you want your program to do?
    (Hint: You may find that you have to go through the string more than once)
    ...


    Junilu Lacar:
    I really tried to implement this in the last example (at least the simplicity of it).

    Would you be open to juse forget the calculator for a second and give me a VERY small project that demonstrates what you are trying to do? Maybe you can tell me something like : create _______ and write a first integration test and tell me your thoughts. But lets forget the calculator for a second and do something more simple, is this possible?
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Yeah, you really have to go even simpler than that. And the thing I'm trying to do is show that there's a lot more thinking that goes into doing TDD than just around testing. You need to be constantly thinking about design and striving for simplicity.

    The exercise I used in the last TDD workshop I gave at work was a simulated game of Rock Paper Scissors with you playing against the computer. It's a fun, quick little exercise in TDD and when you get it working you can extend the program to do Rock Paper Scissors Lizard Spock.
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    You don't always have to start with an integration test. In fact, if you're just starting out with a small program to learn TDD, you can skip the integration test. Integration tests help you keep the big picture in mind and focuses your thinking on the external API of the program components you end up creating. With a program as small as Rock Paper Scissors, an integration test is a bit of an overkill.
     
    Michael Bruce Allen
    Ranch Hand
    Posts: 87
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Perfect! This will get me out of the slump of the calculator and refresh my brain a bit.

    FIrst I will:
    Decide what code will do...
    Write a test that will pass if code does what it should do...
    run test and see test fail...
    write the code...
    run the test and see it pass...

    Here is what the program does
    checks user input and compares with AI.
    AI randomly generates 1 of 3 possible choices.
    User picks their choice
    program returns "You win" or "You lose"

    I am going to try to walk through this slow. So what should my first test be? What is the simplest thing that this does? It seems to be a VERY simple app, thought writing a simple test seems a bit tough. I can make the AI first make a choice, but how do I test it when it will have 1 of 3 results?

    So here is my GUESS at a first test.


     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Michael Bruce Allen wrote:
    So here is my GUESS at a first test.


    Don't guess. Try to make your test tell a story. What story is this test telling? Here's how it reads to me:

    I'm testing user input. Starting with a new RockPaperScissors game, I ask the game to rock. I expect the user choice to be "Rock".

    That story does not make sense at all.
     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Here's my thought process: I have three valid moves: Rock, Paper, or Scissors. The result will be either a tie when the computer and I make the same move, or I will win, or the computer will win. Scissors cut Paper, Paper covers Rock, Rock smashes Scissors.

    Ok, I have moves. Seems like I need an Enum for these. What would the code look like? Let's try this:

    This is about as simple a test as you could have. Notice that I don't worry at all about how the user inputs his move. Stop worrying about user input!

    So, how do I read this test? Here's how it goes in my head: I'm testing the rule that Scissors cuts Paper. If I ask whether SCISSORS beats PAPER, the result should be true. If I want to make the test code a little more fluent, I'd use Hamcrest like so:

     
    Junilu Lacar
    Sheriff
    Posts: 11435
    176
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Since that test tells a coherent little story, I now have a small base on which to build my design. I have a Move, of which SCISSORS and PAPER are instances. Another instance of Move would be ROCK. I can ask a Move if it beats another Move. This is what the expression SCISSORS.beats(PAPER) exemplifies. This looks like a reasonable API so I go ahead and write the code to make this test pass.

    Go ahead, take a shot at writing the code for the Move enum that would make this test pass. And try to make it as simple (even stupid simple) as you can make it.
     
    Michael Bruce Allen
    Ranch Hand
    Posts: 87
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    What is interesting is ENUM triggered in my head, but I do not even understand what it is. I guess I will have to learn something new. Ill be back with the proper code.
     
    It is sorta covered in the JavaRanch Style Guide.
    • Post Reply Bookmark Topic Watch Topic
    • New Topic
    Boost this thread!