我正在尝试进行单元测试的场景示例:
方法有三个参数:Company name
, Plane Number 1
, Plane Number 2
public Double getDistanceBetweenPlanes(Sting company, String plane1, String plane2)
{
-Apply some validation logic to see that plane numbers and company realy exists
-Use complex geLocation and setelate data to get plane location
return plane1.geoLocation() - plane2.geoLocation() ;
}
现在,我想对它进行单元测试。
我没有真正的航班号,我没有实时连接到卫星。
我仍然希望能够以某种方式模拟整个事情,以便我可以测试这个API。对如何重构它有什么建议吗?是否需要为测试而创建新的公共方法?
通常在重构未经测试的代码时,我指的是我在实践TDD和BDD时会做的事情。
所以首先我要写一个简单的合约,一行=一个需求(现有代码)。然后,在编写测试代码时,我可能会注意到我不想测试的东西。在这种情况下,DI将帮助您删除与另一个类具有不同职责的代码。在进行对象设计时,关注具有不同关注点的对象之间的行为和交互是很重要的。Mockito的别名类BDDMockito可以帮助您使用这种方法。
在你的情况下,这里有一个你可以做的例子。
public class PlaneLocator {
private CompanyProvider companyProvider;
private PlaneProvider companyProvider;
private LocationUtil locationUtil;
// Constructor injection or property injection
public Double getDistanceBetweenPlanes(String companyId, String plane1Id, String plane2Id) {
Company company = companyProvider.get(company);
Plane plane1 = planeProvider.get(plane1Id);
Plane plane1 = planeProvider.get(plane1Id);
return locationUtil.distance(plane1.geoLocation(), plane2.geoLocation());
}
}
一个简单的JUnit测试可能看起来像这样(使用mockito 1.9.0的特性):
@RunWith(MockitoJUnitRunner.class)
public class PlaneLocatorTest {
@Mock CompanyProvider mockedCompanyProvider;
@Mock PlaneProvider mockedPlaneProvider;
@Mock LocationUtil locationUtil;
@InjectMocks SatelitePlaneLocator testedPlaneLocator;
@Test public void should_use_LocationUtil_to_get_distance_between_plane_location() {
// given
given(companyProvider.get(anyString())).willReturn(new Company());
given(planeProvider.get(anyString()))
.willReturn(Plane.builder().withLocation(new Location("A")))
.willReturn(Plane.builder().withLocation(new Location("B")));
// when
testedPlaneLocator.getDistanceBetweenPlanes("AF", "1251", "721");
// then
verify(locationUtil).distance(isA(Location.class), isA(Location.class));
}
// other more specific test on PlaneLocator
}
并且您将注入以下依赖项,每个都有自己的单元测试描述类的行为,考虑到输入和合作者:
public class DefaultCompanyProvider implements CompanyProvider {
public Company get(String companyId) {
companyIdValidator.validate(companyId);
// retrieve / create the company
return company;
}
}
public class SatellitePlaneProvider implements PlaneProvider {
public Plane get(Plane planeId) {
planeIdValidator.validate(planeId);
// retrieve / create the plane with costly satellite data (using a SatelliteService for example)
return plane;
}
}
使用这种方式,你可以重构你的代码与一个低得多的耦合。更好的关注点分离将允许更好地理解代码库、更好的可维护性和更容易的发展。
我还想引导你进一步阅读这篇博客文章转换优先前提来自著名的叔叔Bob Martin http://cleancoder.posterous.com/the-transformation-priority-premise
您可以使用jmock或easy mock之类的mock。
使用jmock,你可以这样写:
@Test
public testMethod(){
Mockery context = new Mockery();
plane1 = context.mock(Plane.class);
plane2 = context.mock(Plane.class);
context.expectations(new Expectations(){{
oneOf(plane1).geoLocation(); will(returnValue(integerNumber1));
oneOf(plane2).geoLocation(); will(returnValue(integerNumber2));
}});
assertEquals(
instanceUnderTest.method(plane1,plane2),
integerNumber1-integerNumber2
)
context.assertIsSatisfied()
}
最后一个方法将确保调用在期望上设置的方法。