Growing Object-Oriented Software, Guided by Tests

Growing Object Oriented Software Guided By Tests

What is the Point of Test-Driven Development

  • Feedback is the most fundamental tool at our disposal
  • TDD is a practice that supports change
  • You should never write new functionality without a failing test.
  • This first chapter basically explains the benefits of TDD

Test-Driven Development with Objects

  • Objects should communicate their intent to other objects
    • Tell Don’t Ask principle
  • Mock Objects when you use “Tell Don’t Ask” principle

An Introduction to the Tools

  • Testing Tools are introduced in the chapter
    • JUnit and JMock are mentioned
  • JavaScript Test Runners like ava.js, mocha.js, and jest
  • sinon.js, and testdouble.js serve as mocking libraries
  • Test Fixtures are mentioned in this chapter as well

Kick-Starting the Test-Driven Cycle

  • Using Test Fixtures and Mock Objects is mentioned
  • Examples are using JUnit and JMock
  • A test fixture is the fixed state that exists at the start of a test.
  • A test fixture ensures that a test is repeatable
    • Every time a test is run it starts in the same state so it should produce the same results.
  • A fixture may be set up before the test runs and torn down after it has finished
  • Assertion Libraries are talked about but mainly how to write tests using assertions

Maintaining The Test-Driven Cycle

  • Start each feature with an acceptance test
  • An acceptance test basically is how you test a feature in a UI
  • An acceptance test should fail unti the feature is implemented
  • Find the simplest success case
  • User Acceptance Tests (UATs) should be easy to read
  • You should be unit testing behavior and not methods
  • Pay attention to trouble areas that are hard to test
    • This indicates a possible area for refactoring

Object-Oriented Style

Object Oriented Design:

  • Have clear separation of concerns
    • Behavior should be grouped in logical places
    • This helps isolate changes to areas of concern
  • Group components into Higher Levels of Abstraction
    • Don’t program with variables and control flow in mind but instead with larger abstractions
  • Encapsulation is a concept where behavior of an object is only affected through its API
  • Information Hiding deals with the fact that Objects should hide lower level work in its API from public use
  • Every object should have only have responsibility aka Single Responsibility Principle
  • The Composition of several objects should be simpler than the sum of all the objects
  • Objects should have enough context of their environment
  • Whatever information you need should be passed in or to put it another way you can use dependency injection

Achieving Object-Oriented Design

  • Writing test first influence the design of a system
  • TDD with mock objects expresses intent that you should mock an object’s peer dependencies but not the internals of an object
  • Value types is mentioned with explicit caveat that they are immmutable while Objects are stateful
  • An object should be broken apart if it becomes difficult to test
  • Interfaces should be not be fat and with that being said you will need more interfaces
  • You will need more interfaces in order to keep your interfaces lean
    • This means you should refactor interfaces as well
  • System Behavior should rely on composition and not inheritance

Building on Third-Party Code

  • You should only mock object that you own
  • Don’t mock third party code
  • Instead write an Adapter Layer
  • Write a Database Adapter Layer so that if you change databases you can easily swap them out

Commissioning on Auction Sniper

  • A user scenario is described in this chapter detailing the auction process

The Walking Skeleton

  • In this chapter the first test is described which is an end to end test
  • One example library that comes to mind is Nightwatch.js and Cypress.io
  • These libraries help you write end to end tests in modern web UIs

Passing the First Test

  • A TODOLIST is used in this chapter and the first item is crossed out
  • An important concept that I derive is that User Workflows help influence the design and indicate important UI elements

Chapters 12 to 18

  • These chapters essential detail iterating through software design
  • Small slices of behavior should be tested and implemented iteratively
  • Don’t be afraid of changing design of the system while iterating through a User Workflow
  • Architecture should be developed incrementally
  • Types in a system should be defined with Domain Types
  • Modelling systems with Domain Types helps better describe a software system
  • Avoid using Magic Numbers or Strings

Handling Failure

  • Use smaller methods or utility methods that act as helpers for objects
  • Logging should be designed as a feature
    • Avoid scattering logging statements all throughout a system

Listening to the Tests

  • An interesting point is made that implicit dependencies are still dependencies
  • A good corollary to this can be made with npm dependencies
  • Typically npm dependencies have dependencies of their own and if not isolated they carry an implicit dependency of their own
  • Don’t mock values or immutable values since you can just create instances but instead mock stateful objects
  • Bloated constructors ones that take many parameters should signal a refactor possibly add a service or a message broker
  • Bloated constructors can also signal that an object has too many responsilities
  • You shouldn’t have too many test assertions in any given test this probably signals that you should broke apart a test
  • A test should have few assertions/expectations
  • Knowledge of an object should be as close to the origin
    • Keeping knowledge close helps make pluggable components
  • We should explicitly name a behavior to help control the behavior
  • Try to add more names in order to extract more domain information
  • Pass behavior rather than data

Test Readability

  • Test Names should describe Features of a system
  • Tests should follow a structure like this:
    • Setup
    • Execute Actions
    • Teardown
  • Test structure should help explain behavior and should help share valuable information
  • Delegate to subordinate objects
  • Assertions/Expectations should communicate the exact behavior of a system
  • Avoid magic variables and instead use constants that explicitly show intent

Constructing Complex Test Data

  • A builder pattern helps in constructing complex test data
  • Builder pattern helps break apart complex test data into some chainable methods

Test Diagnostics

  • Tests are made to fail
  • We want tests to fail or report failures when they occur
  • Write small and focused tests that are well named
  • Add explanatory statements in test assertion/expections to help find error in a stack trace
  • Use test matchers for detailing methods
  • Tests should be self describing
  • Tracer objects help map where failures occur
  • Write a Failing Test ==> Make Diagnostics Clear ==> Make Tests Pass ==> Refactor

Test Flexibility

  • Write precise assertions/expectations or just test the relevant parts of a method

Chapters 25 to 27

  • Persistence or CRUD actions can be tested with fine-grained integration tests
  • Unit testing concurrent code can be difficult and it is easy to receive false positives
  • Test Asychronous code by testing incremental changes in the UI
  • Using timers can deliver false positives on UI changes
  • Take snapshots of the UI throughout the lifecycle of the User Workflow
comments powered by Disqus