Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save rgkobashi/71c264af07f66c4b185a372586096386 to your computer and use it in GitHub Desktop.
Save rgkobashi/71c264af07f66c4b185a372586096386 to your computer and use it in GitHub Desktop.
Personal notes/summary of Clean Code Component Design Video Series by Uncle Bob

Advanced TDD (Episode 19.1)

  • Laws:
    1. Dont write any PROD code until you have a failing unit test
    2. Stop writing the test as soon as it fails (including compiling errors)
    3. Stop writing PROD code as soon as the test passes
  • Cycle:
    1. Red
    2. Green
    3. Refactor
  • Write degenerate tests first
  • Dont go too complex to fast
  • Single assert (triple A) rule
    • Single logic assertion
    • OK: arrange, act, assert, assert, assert
    • NOT OK: arrange, act, assert, arrange, act, assert, arrange, act, assert

Advanced TDD (Episode 19.2)

  • As the test gets more especific, the code gets more generic
  • If you get stuck, most likely it was because:
    • The above rule was broken
    • Didnt write the degenerate tests firsts

Clean Tests (Episode 20)

  • There are 4 phases
    1. Arrange
      • Fixtures:
        1. Transient fresh
          • Created and destroyed on every test
          • No need of tear down, nothing survived
        2. Persistent fresh
          • Survives from test to test
          • Needs tear down function to remove persistance and make them fresh so it is ready for the next tests
        3. Persistent shared
          • Initalized on every test but allows some state to be accumulated from test to test
          • Needs suite tear down
        • They are not mutually exclusive
        • The freshier the better and transient is tops
    2. Act
      • 2 or more actions function means there is some state sharing, meaning is not writen in a functional style
      • In some cases where multiple actions is requiered due the nature of the test, multiple actions can be composed, this can be useful to keep actions clean
    3. Assertion
      • Is a boolean expression
      • One logical assertion which can be composed by many assertions (composed assert)
      • In the composed assert the action can be also hidden, this can be useful
    4. Annihilation
      • Undo everything was done on arrange
  • Setup method: Common arrangements among group of tests
  • Tear down: Common annihilations among group of tests
  • Test hierarchy
    • Nested setup methods
    • This helps to avoid having long setup methods
  • Compose a sequence of test into a single test
    • This can be controversial
    • This is a specual use case

Test Design (Episode 21)

  • Place test under test directory
  • Ideally one test file per class
  • Tests is also software hence also needs design, same rules apply (SOLID, etc)
    • Otherwise you will end up with fragile tests
  • No need to test privates since public interface is tested, which also includes privates
    • If testing privates is needed, you will have to promote the access to public or something similar
  • Naming:
    • Using Given, When and Then aproach is useful (does not always applies though)
    • Avoid using magic numbers and that kind of stuff on tests
    • The idea is to be able to understand the tests without a perfect knowledge of the requierements
    • Test can remind you of the requierements

Test Process (Episode 22)

  • Fake it until you make it
    • It is an incremental process until the poing where faking it is not needed
    • Simpler tests first, leave to the end the complicated ones
    • Some tests will allow you write the next tests, and after you can delete the previous ones
  • Assert first
    • Write the test backwards by starting with the assertion
    • It force us to deal with errors, compile and execution
  • Triangulation
    • A way to create a more general solution
    • By untrivializing implementation makes test pass
    • One to many technique:
      • When dealing with collections of thing, test one thing first
      • Make it pass first in the singular case and then the plural case
  • Refactoring tests
    • Refactoring tests is important
    • Test are part of the system, not extra things, not inferior, not disposable, they are integral part of the system which cannot live without for very long
    • Treat them with same care and discipline or even more, than the PROD code
    • They are the key to enable refactor
    • The two disks story:
      • PROD code is in one disk and test code is in another one. If one breaks with no backup, which one would you preffer to be?
      • Creating PROD code from tests is easier and better than creating tests from PROD code
  • Tests are specification
    • Tests are specification so they should read like specifications
    • The best test reads like well written specification
    • Write the test that you would want to read
    • Test behavior not APIs: write what behavior do you expect independent of the API
  • In all things the tests comes firsts:
    • When writing them
    • When refactoring them
    • When cleaning and maintaining them

Mocking (Episode 23.1)

  • Test doubles
    1. There is no such a thing as a test double
    2. Dummy: functions performs no actions and return as nothing as possible
    3. Stub: Is a Dummy + returns a value consistent with the needs of the tests
    4. Spy: Is a Stub + records facts about its invokations (if something was called, params sent, etc)
    5. True Mock: Is a Spy + it knows what should happen, the tests ask the mock if everything went as expected
    • Fake: not Mock, not Spy, not Stub, not Dummy, is entire different, it is a simulator
      • Has lot of logic
      • Pretends to have realistic behavior
      • They can grow thus they get more complicated
      • Usually for unit tests stubs and spies are enough, not need of fake
      • For integration tests fakes can be very useful
      • Avoid fakes whenever you can

Mocking (Episode 23.2)

  • Behavior vs State (Uncertainty Principle)
    • Mockist (London school):
      • Use of spies which are useful when testing things that crosses boundaries
      • Tolerates higher coupling because the increased assurance
    • Statist (Chicago/Detroit/Cleveland school)
      • Emphasis on values returned
      • Preffers to decouple tests
      • Better when you are not testing something that crosses a boundary
  • Mocking patterns
    1. Test specific subclass
      • When testing a function in a class but you want to modify the behavior of other functions in that class
      • Override functions so they do nothing
      • Kind of a stub and spy
    2. Self-shunt
      • Test class implements interface needed on the tests, the test class would need to be injected
      • Kind of a stub and spy
    3. Humble object
      • Isolate testable code from code that is hard to test (extract hard to test code)
      • Code hard to test is the humble object
      • This way untestable code depends on testable code
      • This can be useful to test IOs, GUIs, etc
  • Mocking frameworks
    • Usually not needed since mocks are easy to write
    • Useful when overide sealed interfaces (like private stuff) is needed
      • However on a well designed system it shouldnt be needed
      • Useful on legacy environments for example

Transformation Priority Premise (Episode 24.1)

  • Refactor: Changes in structure without significant changes on behavior
  • Transformation
    • Changes on behavior without significant changes on structure
    • Small changes to the code that generalize behavior
  • The Transformation list (first approximation, possible listed by priority)
    1. Null: Initial starting point of any function, function that does nothing, returns null or zero
    2. Null to constant: function that returns null to return something not null
    3. Constant to variable: change a constant into variable or argument
    4. Add computation: add one or two simple computations but cannot change state of any existing variables
    5. Split flow: split flow of execution into only two parts
    6. Variable to array
    7. Array to container: generalize an array into something more comprehensive like dict, set, stack, queue, etc
    8. If to while: splitted flow needs to be repeated
    9. Recurse
    10. Iteration: operation needs to be repeated but not by using recursivity
    11. Assign: Change the state of a variable but not when initializing first state
    12. Add case: When we have a splitted flow we want to split further
  • *Duplicated code is always specific, never general

Transformation Priority Premise (Episode 24.2)

  • The Transformation list is also arranged in complexity order
    • High priority: simple and very low impact on the code
    • Low priority: complicated and high impact on the code
  • The premise: When there is a fork on the road, make the test pass with the highest possible priority transformation
    • Choossing low priority transformation leads to a bad algorith
    • Choossing high priority transformation leads to a good algorith
  • Based on this we can conclude that when getting stuck is when having to pass a test case using a low priority transformation. Thus to avoid this, find a different test case which can be passed with higher priority transformation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment