11. Testing the Facades with Mockito

- 测试Facade

    - 动机

        - 这个 Trail 的目的是演示你应该如何使用Facade设计模式,通过实施Facade类和使用Mockito而不是stubs进行测试。Facade classes are designed to wrap complexity and present simple interfaces to their calling classes. Mocking is the practice of using a class which implements a real interface in your system but mimics the real behaviour of the system. Unlike a stub, which is similar, a mock class can dynamically change responses for useful system testing and also test coverage.

---------------------------------------------------------------------------------------------

    - 任务/讨论

        - Facades 背景

        - 导入Mockito

        - 测试1

        - 编译测试1

            - 创建StadiumFacade

            - 创建StadiumDataTO

            - 创建MatchSummaryDataTO

        - 通过测试1

        - 测试2

        - 编译测试2

            - 创建StadiumService接口

            - 以下内容添加到StadiumFacade

            - 以下内容添加到StadiumFacadeImpl

        - 测试2通过

            - 调整Facade来调用Service和Marshall的结果。

            - 确保Facade集结的结果变成预期StadiumDataTO。 

        - 总结

---------------------------------------------------------------------------------------------


- Facades 背景 

    The intent of a Facade is to "Provide a unified interface to a set of interfaces in a subsystem. A Facade defines a higher-level interface that makes the subsystem easier to use.": see The Facade Design Pattern. In our case, the Facade is the front-most API to which the client (web-pages) has access.

    Consider for example a rich client in which the communication between the client and server should be kept to a minimum.  If the client needs data from methodA in ServiceA, methodB in serviceB and methodC in serviceC,  it would be more efficient for the client to be able to make one call to a Facade on the server that itself calls those 3 methods on the 3 services, rather than to call the 3 services itself.  And since the Facade is making the calls, we can also ask the Facade to package the particular data we need (which might be somewhat duplicated in the 3 return values from the 3 services, or might not yet be complete), and to pass that back to the Client.  This is the purpose of the Data Transfer Object: see Transfer Object Design Pattern and Data Transfer Object Assembler.  Our facade will be performing both roles itself.  You might choose to have a separate DTOAssembler in your own code (in accordance with the design principle Separation of Concerns), and we may  modify this trail to also do that.


- 导入Mockito

    TODO:导入mockito.jar并链接到它。


- 测试1

    为了测试接口的行为,我们需要

    FacadeTDDWithMockito.java    
/**
 * This class belongs to the Source Code Trail documented at https://wiki.hybris.com/display/pm/Source+Code+Tutorial
 */
package de.hybris.platform.cuppytrail.mockito;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
 
import de.hybris.platform.cuppytrail.web.data.StadiumDataTO;
import de.hybris.platform.cuppytrail.web.facades.StadiumFacade;
 
import java.util.List;
 
import org.junit.Before;
import org.junit.Test;
 
 
public class FacadeTDDWithMockito
{
    private StadiumFacade stadiumFacade;
 
    @Test
    public void testFacade1()
    {
        final List<StadiumDataTO> stadiumDataTOs = stadiumFacade.getAllStadium();
        assertEquals(stadiumDataTOs.size(), 1);
        final StadiumDataTO stadiumDataTO = stadiumFacade.getStadiumDetails("Wembley");
        assertNotNull(stadiumDataTO);
    }
}

- 编译测试1

    - 创建StadiumFacade

    StadiumFacade.java    
package de.hybris.platform.cuppytrail.web.facades;
 
import de.hybris.platform.cuppytrail.web.data.StadiumDataTO;
 
import java.util.List;
 
 
/**
 * This interface belongs to the Source Code Trail documented at https://wiki.hybris.com/display/pm/Source+Code+Tutorial
 */
public interface StadiumFacade
{
    StadiumDataTO getStadiumDetails(String name);
 
    List<StadiumDataTO> getAllStadium();
}

    - 创建StadiumDataTO

    StadiumDataTO.java    
package de.hybris.platform.cuppytrail.web.data;
 
import java.util.List;
 
 
/**
 * This class belongs to the Source Code Trail documented at https://wiki.hybris.com/display/pm/Source+Code+Tutorial It
 * is the data transfer object for the Stadium Facade
 */
public class StadiumDataTO
{
    private final String capacity;
    private final String name;
    private List<MatchSummaryDataTO> matches;
 
    public StadiumDataTO(final String name, final Integer capacity)
    {
        this.name = name;
        this.matches = null;
        this.capacity = capacity == null ? "-" : capacity.toString();
    }
 
    public StadiumDataTO(final String name, final Integer capacity, final List<MatchSummaryDataTO> matches)
    {
        this(name, capacity);
        this.matches = matches;
    }
 
    public String getName()
    {
        return name;
    }
 
    public String getCapacity()
    {
        return capacity;
    }
 
    public List<MatchSummaryDataTO> getMatches()
    {
        return matches;
    }
}

    - 创建MatchSummaryDataTO

    MatchSummaryDataTO.java    
package de.hybris.platform.cuppytrail.web.data;
 
import java.text.DateFormat;
import java.util.Date;
 
 
/**
 * This class belongs to the Source Code Trail documented at https://wiki.hybris.com/display/pm/Source+Code+Tutorial It
 * is the data transfer object for the Stadium Facade
 */
public class MatchSummaryDataTO
{
    private final String guestTeam;
    private final String homeTeam;
    private String homeGoals;
    private String guestGoals;
    private final Date date;
 
    public MatchSummaryDataTO(final String homeTeam, final String guestTeam, final Date date)
    {
        this.guestTeam = guestTeam;
        this.homeTeam = homeTeam;
        this.date = date;
    }
 
    public String getGuestTeam()
    {
        return guestTeam;
    }
 
    public String getHomeTeam()
    {
        return homeTeam;
    }
 
    public void setHomeGoals(final String homeGoals)
    {
        this.homeGoals = homeGoals;
    }
 
    public void setGuestGoals(final String guestGoals)
    {
        this.guestGoals = guestGoals;
    }
 
    @Override
    public String toString()
    {
        final String formatedDate = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT).format(date);
 
        return homeTeam + ":( " + homeGoals + " ) " + guestTeam + " ( " + guestGoals + " ) " + formatedDate;
    }
}


- 通过测试1

    - 该测试将抛出一个空指针作为stadiumFacade目前空。因此,我们需要创建一个实现并将其指定给stadiumFacade。

    - 创建StadiumFacade的实现方式之一将会满足这个测试。

    StadiumFacadeImpl.java    
package de.hybris.platform.cuppytrail.web.facades.impl;
 
import de.hybris.platform.cuppytrail.web.data.StadiumDataTO;
import de.hybris.platform.cuppytrail.web.facades.StadiumFacade;
 
import java.util.ArrayList;
import java.util.List;
 
 
public class StadiumFacadeImpl implements StadiumFacade
{
 
    @Override
    public StadiumDataTO getStadiumDetails(final String name)
    {
        return new StadiumDataTO("Wembley", Integer.valueOf(12345));
    }
 
    @Override
    public List<StadiumDataTO> getAllStadium()
    {
        final List<StadiumDataTO> dataTOs = new ArrayList<StadiumDataTO>();
        dataTOs.add(new StadiumDataTO("Wembley", Integer.valueOf(12345)));
        return dataTOs;
    }
 
}

    - 加入此行至设定方法指定这个实施stadiumFacade

stadiumFacade = new StadiumFacadeImpl();

    - 该测试现在应该通过。


- 测试2

    - 一个最基本骨骼StadiumFacadeImpl足以满足测试1。

    - 在测试2中,我们将使用StadiumService扩展测试预期Facade。

    - FacadeTDDWithMockito替换

    FacadeTDDWithMockito.java    
/**
 * This class belongs to the Source Code Trail documented at https://wiki.hybris.com/display/pm/Source+Code+Tutorial
 */
package de.hybris.platform.cuppytrail.mockito;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
 
import de.hybris.platform.cuppytrail.model.StadiumModel;
import de.hybris.platform.cuppytrail.services.StadiumService;
import de.hybris.platform.cuppytrail.web.data.StadiumDataTO;
import de.hybris.platform.cuppytrail.web.facades.StadiumFacade;
import de.hybris.platform.cuppytrail.web.facades.impl.StadiumFacadeImpl;
 
import java.util.ArrayList;
import java.util.List;
 
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
 
 
public class FacadeTDDWithMockito
{
    private StadiumFacade stadiumFacade;
 
    @Mock
    private StadiumService stadiumService; // Service is mocked out by Mockito
 
    @Before
    public void setUp()
    {
        MockitoAnnotations.initMocks(this); // Tells Mockito to set up its annotated mocks
        stadiumFacade = new StadiumFacadeImpl();
        stadiumFacade.setStadiumService(stadiumService); // Inject the (mocked out) service into the StadiumFacade
 
        final List<StadiumModel> stadiumModels = new ArrayList<StadiumModel>();
        stadiumModels.add(newStadiumModel("Wembley", Integer.valueOf(12345)));
        stadiumModels.add(newStadiumModel("Allianz", Integer.valueOf(23456)));
 
        when(stadiumService.getAllStadium()).thenReturn(stadiumModels);
        when(stadiumService.getStadiumDetails("wembley")).thenReturn(newStadiumModel("Wembley", Integer.valueOf(12345)));
    }
 
    @Test
    public void testFacade1()
    {
        final List<StadiumDataTO> stadiumDataTOs = stadiumFacade.getAllStadium();
        assertEquals(stadiumDataTOs.size(), 2);
        final StadiumDataTO stadiumDataTO = stadiumFacade.getStadiumDetails("Wembley");
        assertNotNull(stadiumDataTO);
    }
 
    @Test
    public void testFacade2()
    {
        final List<StadiumModel> stadiumModels = stadiumFacade.getStadiumService().getAllStadium();
        assertEquals(stadiumModels.size(), 2);
        final StadiumModel stadiumModel = stadiumFacade.getStadiumService().getStadiumDetails("Wembley");
        assertNotNull(stadiumModel);
    }
 
    // Convenience method for creating a StadiumModel
    private StadiumModel newStadiumModel(final String name, final Integer capacity)
    {
        final StadiumModel model = new StadiumModel();
        model.setCode(name);
        model.setCapacity(capacity);
        return model;
    }
}


- 编译测试2

    - 创建StadiumService接口

    StadiumService.java    
ackage de.hybris.platform.cuppytrail.services;
 
import de.hybris.platform.cuppytrail.model.StadiumModel;
 
import java.util.List;
 
/**
 * This interface belongs to the Source Code Trail documented at https://wiki.hybris.com/display/pm/Source+Code+Tutorial
 * and describes the interface required to satisfy the StadiuServiceIntegrationTest
 */
public interface StadiumService
{
    List<StadiumModel> getAllStadium();
 
    StadiumModel getStadiumDetails(String code);
}

    - 以下内容添加到StadiumFacade

    StadiumFacade.java    
...
import de.hybris.platform.cuppytrail.services.StadiumService;
...
void setStadiumService(StadiumService stadiumService);
StadiumService getStadiumService();

    - 以下内容添加到StadiumFacadeImpl

    StadiumFacadeImpl.java    
...
import de.hybris.platform.cuppytrail.services.ItadiumService;
...
private StadiumService stadiumService;
...
@Override
public void setStadiumService(final StadiumService stadiumService)
{
    this.stadiumService = stadiumService;
}
 
@Override
public StadiumService getStadiumService()
{
    return stadiumService;
}


- 测试2通过

    - 该测试现在应该编译和运行,但将在断言“assertEquals(stadiumModels.size(),2);”失败。

        - 调整Facade来调用Service和Marshall的结果。

        - 确保Facade集结的结果变成预期StadiumDataTO。 


- 总结

    - 在这一步,你应该已经学会:

        - 如何开发一个Facade和DTO与TDD。

        - mock对象和 stubs ,以及它们如何相互配合之间的差异。

        - 阅读材料和cuppy源代码可供学习更多。

你可能感兴趣的:(11. Testing the Facades with Mockito)