Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oliverswitzer/7c1387e28e3a2791330fbf52fd109b16 to your computer and use it in GitHub Desktop.
Save oliverswitzer/7c1387e28e3a2791330fbf52fd109b16 to your computer and use it in GitHub Desktop.
POODR Chp. 9 to 9.3 Notes
# Intro
Writing changeable code: three different skills
- 1. understand OO design. Code that is easy to change IS well designed.
- 2. skilled at refactoring. Quote from Fowler
--> also maybe insert quote from Kent Beck:
"make the change easy (warning: this may be hard), then make the easy change"
- refactoring is how you morph code to accommodate new requirements. Prefactor vs refactor?
- 3. High value tests!
- Good tests don't need to be changed when you refactor.
- they give you confidence to refactor constantly
OPINION: Go fast forever -> code that can change -> well designed code -> refactor -> confidence -> tests
**** Let's break here to think about:
- QUESTION: Examples of things done in tests that can make them highly coupled to a particular design
# Intentional testing
Are they saving you time?
- The learning curve of over-testing when you first start writing tests
- OPINION: testing atrophe, what is it? When argument is made for testing only when the problem your testing is complex; when you haven't built up your testing muscle to prepare you to do the Olympic weightlifting-equivalent of test writing for the complex piece of code you want to write.
- knowing what and when to test is key
# Knowing your intentions
- Finding bugs
- supplying (living) documentation
- deferring design decisions
--> like a climber putting a stake in the wall every few feet; this allows them to continue with minimal risk and more options for movement
--> when you just don't know the right design yet, write tests against it that will validate that your soln. still works when your refactor later.
- supporting abstractions: as more abstractions enter your app over time from refactoring, tests are a great way to make sure they all still work together.
--> OPINION: I'm surprised the concept of integration tests wasn't introduced here
--> OPINION: is redux an example of something that could use integration testing? Would that be testing the framework? What's the room for error?
- exposing design flaws.
--> When the design is bad testing is hard.
--> good design is for source code AND test code
--> loosely coupled tests!
# knowing what to test
- many programmers get discouraged bc they write too many tests that are costly to write.
--> OPINION: They deal with the cost of writing hard to write tests rather than take a step back to re-evaluate the design of the code they're testing.
- Black boxes/space capsules with airlocks metaphor
--> OPINION: your tests should percieve the classes in your application just like the rest of the classes percieve other classes: they do not know more than they need to know about e/o
- only write tests against an objects public interface
- concentrate on testing incoming and outgoing messages
--> QUESTION: Foo test should not test that it sends a message to Bar bc Bar already tests it's own public interface? Is Sandy metz arguing here for never testing outgoing messages from a class? Should I never expect a spy to be called again?
- NOPE! You should still do that. We need to differentiate outgoing commands queries and commands
- Query: outgoing message with no side effect. They return a value to the sender without making stateful changes to any other part of the app (or db, or anything, really)
- Command: outgoing message WITH a side effect (file gets written, db record saved, etc...). The sender must prove that they are properly sent and thus must be tested.
Things to test:
- Incoming messages for their return values
- Outgoing command messages to ensure they get sent
- Outgoing query messages should not be tested
--> OPINION: But we can stub these outgoing query messages right? When we write assertioms on the return value of a stub indirectly through a class, is that implictly testing a query message?
# Knowing when to test (197)
- You need code to be re-usable to write tests.
- Novice designers have a good shot at becoming better designers by trying to write tests first
- *Warning*: writing tests first does not guarantee good design
# Knowing how to test
- TDD vs BDD.
--> OPINION: Are these really in opposition? Isn't BDD a form of TDD?
- Differentiate object under test and everything else. Test should remain ignorant of "everything else"
- " This is a bad idea, how- ever, because it allows knowledge that should be private to the object to leak into the tests, increasing coupling between them and raising the likelihood that changes to code will require changes in tests. "
# Deleting unused interfaces
- Do not test incoming messages that have no dependents:
--> What is an example of this? The table doesn't really list any examples.
# Providing the public interface
OPINION: Is it actually ok that Gear creates a Wheel under the hood? Is there REALLY a performance hit to instantiating a new wheel?
- "When tightly coupled objects are tested, a test of one object runs the code in many others."
OPINION: Is testing through an object always a bad thing? Sometimes it gives you more flexibility to refactor later, since you don't have to inject a dependency of the thing your testing in your test.
- A possible resolution to this is injecting Wheel into Gear as a "Diameterizable" object.
- Injecting "Wheel" into Gear does not make it clear to future developers that Gear expects a more generic "Diameterizable" object and not specifically a Wheel.
- We haven't really saved test time by doing this, since we're still instantiating a new wheel in our test.
OPINION: Talk about concept of Fakes and how they can save you time in tests.
# Injecting dependencies as Roles
- You are testing the abstract behavior of a "Diameterizable," not a wheel. How can we convey this?
# Creating test doubles
"A test double is a stylized instance of a role player that is used exclusively for testing."
OPINION: Good piece about Different types of "Test Doubles": https://blog.cleancoder.com/uncle-bob/2014/05/14/TheLittleMocker.html
- Dummy: just need to inject something bc the object needs it, but we don't care about how the object under test interacts with it
- Stub: need to inject something that returns a fixed value
- Spy: need to inject an object and make assertions on the messages it is sent (the arguments, number of times it is called)
- Fake: need to inject an object that exhibits business behavior, but not the real kind. They are like a simulator. They often can warrant having tests of their own.
# Using tests to document Roles
- But now that we created a DiameterizableDummy, how do we guarantee that it's API doesn't go out of sync with the other Diameterizables (re: the example she gave earlier of a developer accidentally changing the message that Gear sends to Wheel without updating the Wheel/Diameterizable interface.
- OPINION: Would an "Abstract" parent class help here?
- "Injecting the same objects at test time as are used at runtime ensures that tests break correctly but may lead to long running tests. Alternatively, injecting doubles can speed tests but leave them vulnerable to constructing a fantasy world where tests work but the application fails."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment