Chapter 3: Sensing and Separation

Chapter 3: Sensing and Separation

Note of Working Effectively with Legacy Code

The writer told us that there are two reasons to break dependencies:

  • Sensing: We break dependencies to sense when we can’t access values our code computes.

  • Separation: We break dependencies to separate when we can’t even get a piece of code into a test harness to run.

First of all, I should announce that I didn't understand Sensing until I went through the Sale Test example. I am not a native English speaker, so I don't know the word's meaning.

Let's go through it together.

Faking Collaborators

So, here comes a problem:

public class Sale {
  ...
  public void scan(String barcode) { 
      // do something strange here
  }
  ...
}

We want to test the scan method, but how? the whole Method looks like a giant black box; we don't even know what other Method it will call or what other Objects it will use.

The scan Method uses a displayer to show the information in this case. That's the problem: What if The Displayer is an SDK? If so, we will know nothing about it but the Input and Output of the Interface. It's sad.

Fake Objects

Imagine that if this is a war, what would we do if we don't know much about our enemy?

We send a spy to know them better internally; we will know all the work content the spy is sent to finish. After all, this spy is our guy.

That is what we will do. But how? As we all know, to inject something into an instance, we should change the constructor to let it accept the parameters that we want to inject.

public class Sale {
  private Display display;
  public Sale(Display display) {
    this.display = display;
  }
  public void scan(String barcode) {
    ...
    String itemLine = item.name()
    + " " + item.price().asDisplayText();
    display.showLine(itemLine);
    ... 
  }
}

public class SaleTest extends TestCase {
  public void testDisplayAnItem() {
    // here we play the magic
    FakeDisplay display = new FakeDisplay();
    Sale sale = new Sale(display);
    sale.scan("1");
    assertEquals("Milk $3.99", display.getLastLine()); 
  }
}

As you can see, now we can use a fake Display instance to write our tests. The strange thing is, we still don't know what the real Displayer does, but we don't care about it anymore; we focus on the scan Method only; if you ask me, also the 'ShowLine' Method of Display instance.

The Two Sides of a Fake Object

The Fake Display fulfills the Display Interface's responsibilities and the Test needs. We can add a GetValue Method to validate any value of the Fake Display Instance.

Fakes Distilled

What if our project is written in non-OO language? The writer says:

In non-OO languages, we can implement a fake by defining an alternative function, one which records values in some global data structure that we can access in tests.

That is precisely why I am a fan of the OO language.

Mock Objects

public class SaleTest extends TestCase {
  public void testDisplayAnItem() {
    MockDisplay display = new MockDisplay();
    // right here we do the Validation.
    display.setExpectation("showLine", "Milk $3.99"); 
    Sale sale = new Sale(display);
    sale.scan("1");
    display.verify();
  }
}

Mock Objects give us the ability to validate the Input of a Method.

I must say that this book is a bit out of date, but the cool thing is that we still use Mock Object to do unit tests today.