5 min read

(For more resources related to this topic, see here.)

In this category, developers pick a story or use case and drill into low-level unit tests. Basically, the objective is to obtain high-level design. The different system interfaces are identified and abstracted. Once different layers/interfaces are identified, unit-level coding can be started.

Here, developers look at the system boundary and create a boundary interface depending upon a use case/story. Then, collaborating classes are created. Mock objects can act as a collaborating class. This approach of development relies on code for abstraction.

Acceptance Test-Driven Development (ATDD) falls into this category. FitNesse fixtures provide support for ATDD. This is stateful. It is also known as the top-down approach.

An example of ATDD

As a health professional (doctor), I should get the health information of all my critical patients who are admitted as soon as I’m around 100 feet from the hospital.

Here, how patient information is periodically stored in the server, how GPS tracks the doctor, how the system registers the doctor’s communication medium (sends report to the doctor by e-mail or text), and so on can be mocked out. A test will be conducted to deal with how to fetch patient data for a doctor and send the report quickly. Gradually, other components will be coded.

The preceding figure represents the high-level components of the user story.

Here, Report Dispatcher is notified when a doctor approaches a hospital, then the dispatcher fetches patient information for the doctor and sends him the patient record. Patient Information, Outbound Messaging Interface, and Location Sensor (GPS) parts are mocked, and the dispatcher acts as a collaborator.

Now we have a requirement to calculate the taxable income from the total income, calculate the payable tax, and send an e-mail to the client with details.

We have to gather the annual income, medical expense, house loan premium, life insurance details, provident fund deposit, and apply the following rule to calculate the taxable income:

  • Up to USD 100,000 is not taxable and can be invested as medical insurance, provident fund, or house loan principal payment
  • Up to USD 150,000 can be used as house rent or house loan interest

Now we will build a TaxConsultant application using the outside-in style.

Following are the steps:

  1. Create a new test class com.packtpub.chapter04.outside. in.TaxConsultantTest under the test source folder.
  2. We need to perform three tasks; that are, calculate the taxable income, the payable tax, and send an e-mail to a client. We will create a class TaxConsultant to perform these tasks. We will be using Mockito to mock out external behavior. Add a test to check that when a client has investment, then our consultant deducts an amount from the total income and calculates the taxable income. Add a test when_deductable_present_then_taxable_income_is_less_than_the_total_income() to verify it so that it can calculate the taxable income:

    @Test
    public void when_deductable_present_then_taxable_income_
    is_less_than_the_total_income () {
    TaxConsultant consultant = new TaxConsultant();
    }

    Add the class under the src source folder. Now we have to pass different amounts to the consultant. Create a method consult() and pass the following values:

    @Test
    public void when_deductable_present_then_taxable_income_is_less_
    than_the_total_income () {
    TaxConsultant consultant = new TaxConsultant();
    double totalIncome = 1200000;
    double homeLoanInterest = 150000;
    double homeLoanPrincipal =20000;
    double providentFundSavings = 50000;
    double lifeInsurancePremium = 30000;
    consultant.consult(totalIncome,homeLoanInterest,
    homeLoanPrincipal,providentFundSavings,
    lifeInsurancePremium);
    }

    In the outside-in approach, we mock out objects with interesting behavior. We will mock out taxable income behavior and create an interface named TaxbleIncomeCalculator. This interface will have a method to calculate the taxable income. We read that a long parameter list is code smell; we will refactor it later:

    public interface TaxbleIncomeCalculator {
    double calculate(double totalIncome, double homeLoanInterest,
    double homeLoanPrincipal, double providentFundSavings, double
    lifeInsurancePremium);
    }

  3. Pass this interface to TaxConsultant as the constructor argument:

    @Test
    public void when_deductable_present_then_taxable_income_is_less_
    than_the_total_income () {
    TaxbleIncomeCalculator taxableIncomeCalculator = null;
    TaxConsultant consultant = new TaxConsultant
    (taxableIncomeCalculator);

    We need a tax calculator to verify that behavior. Create an interface called TaxCalculator:

    public interface TaxCalculator {
    double calculate(double taxableIncome);
    }

  4. Pass this interface to TaxConsultant:

    TaxConsultant consultant = new TaxConsultant(taxableIncomeCalculat
    or,taxCalculator);

    Now, time to verify the collaboration. We will use Mockito to create mock objects from the interfaces. For now, the @Mock annotation creates a proxy mock object. In the setUp method, we will use MockitoAnnotations.initMocks(this); to create the objects:

    @Mock TaxbleIncomeCalculator taxableIncomeCalculator;
    @Mock TaxCalculator taxCalculator;
    TaxConsultant consultant;
    @Before
    public void setUp() {
    MockitoAnnotations.initMocks(this);
    consultant= new TaxConsultant(
    taxableIncomeCalculator,taxCalculator);
    }

  5. Now in test, verify that the consultant class calls TaxableIncomeCalculator and TaxableIncomeCalculator makes a call to
  6. TaxCalculator. Mockito has the verify method to test that:

    verify(taxableIncomeCalculator, only())
    calculate(eq(totalIncome), eq(homeLoanInterest),
    eq(homeLoanPrincipal), eq(providentFundSavings),
    eq(lifeInsurancePremium));
    verify(taxCalculator,only()).calculate(anyDouble());

    Here, we are verifying that the consultant class delegates the call to mock objects. only() checks that the method was called at least once on the mock object. eq() checks that the value passed to the mock object’s method is equal to some value.

    Here, the test will fail since we don’t have the calls. We will add the following code to pass the test:

    public class TaxConsultant {
    private final TaxbleIncomeCalculator taxbleIncomeCalculator;
    private final TaxCalculator calculator;
    public TaxConsultant(TaxbleIncomeCalculator
    taxableIncomeCalculator, TaxCalculator calc) {
    this.taxbleIncomeCalculator =
    taxableIncomeCalculator;
    this.calculator = calc;
    }
    public void consult(double totalIncome, double homeLoanInterest,
    double homeLoanPrincipal, double providentFundSavings, double
    lifeInsurancePremium) {
    double taxableIncome = taxbleIncomeCalculator.calculate
    (totalIncome,homeLoanInterest, homeLoanPrincipal,
    providentFundSavings,lifeInsurancePremium);
    double payableTax= calculator.calculate(taxableIncome);
    }
    }

    The test is being passed; we can now add another delegator for e-mail and call it EmailSender.

  7. Our facade class is ready. Now we need to use TDD for each interface we extracted. Similarly, we can apply TDD for TaxableIncomeCalculator and EmailSender.

Summary

This article provided an overview of classical and mockist TDD. In classical TDD, real objects are used and integrated, and mocks are only preferred if a real object is not easy to instantiate. In mockist TDD, mocks have higher priority than real objects.

Resources for Article:


Further resources on this subject:


LEAVE A REPLY

Please enter your comment!
Please enter your name here