Skip to content

Instantly share code, notes, and snippets.

@michaellihs
Last active September 11, 2022 07:12
Show Gist options
  • Save michaellihs/541927e083dcaa78532a8f6fef9bd280 to your computer and use it in GitHub Desktop.
Save michaellihs/541927e083dcaa78532a8f6fef9bd280 to your computer and use it in GitHub Desktop.
Outside-In TDD

Transcript from Outside-In TDD

Part 1

  • write acceptance test first (name it ...Feature in a package feature)
    • identify side effects - what are we testing (e.g. text printed to console)
    • identify the trigger of the side effect (e.g. print statement on account)
  • drive implementation from test
  • use UnsupportedOperationException for all methods... that are not yet implemented, to see where things are failing
    • change template of creating a new method in the IDE to create a throw statement in the body of the method
  • acceptance testing means:
    • run trigger for side effect
    • assure expected side effect has happened
  • as acceptance test is failing for the right reason, it's time to "park" the acceptance test and go to the inner loop --> unit testing
  • META split IDE editor: test left, implementation right
  • Outside-In TDD is doing design while writing the tests
  • use void command methods (no return value)
  • do not store things in objects that you can calculate on the fly
    • only if calculation takes too much time
  • META create a shortcut test in the IDE that generates a test method stub (template)
  • start your tests with <CLAAS>Should
  • do not use / implement methods just for the purpose of testing
  • when mocking a class / classes, use @RunWith(MockitoJUnitRunner.class)
  • in acceptance test use real repository, in unit test mock it
    • in unit test, mock external world
  • with introducing a repository, a logic is pushed further down into the system (from Account into TransactionRepository)

Part 2

  • Question to ask when doing outside-in TDD:
    • is the responsibility of class under test to do all of that
    • or is it the responsibility of someone else
  • keep all the methods of a class on the same level of abstraction
  • every time a collaborator of the class under test in the acceptance test is created, a method body of this collaborator will throw a UnsupportedOperationException
    • this way we will be guided on what needs to be implemented next...
    • therefore the Acc tests runs the real class, not the mocks
    • as part of the outside-in process, this means that we start with the class under test and are then guided to the missing points of implementation in the collaborator classes
  • each collaborator then gets a unit test as the next step
    • within the unit test, we can think of what the collaborator class should do
    • collaborators of the class under test in the unit tests are mocked
  • how to mock things like a date
    • e.g. the transaction is saved in the database with a given date, which normally will be created "in time"
    • rule: you can't test what you can't controll (e.g. System.date())
    • you need to controll this randomness in your tests
    • so you need the System.date() to come from something that you can control / mock, e.g. a Clock class
  • META* check Idea shortcut for "recent runs" --> select recent runs of unit tests
  • when you need to test some functionality that you can't control (e.g. a clock / date / time), put the call to this functionality into a protected method and override the class / this method in your test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment