Рефакторинг и тестирование
Пример сценария, который я пытаюсь как-то тестировать:
Метод получает три параметра: 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. Любое предложение о том, как его реорганизовать? Любой новый публичный метод, который будет создан только для тестирования?
2 ответа
Обычно при рефакторинге непроверенного кода я имею в виду то, что я буду делать, практикуя 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;
}
}
Используя этот способ, вы можете реорганизовать свой код с гораздо более низкой связью. Лучшее разделение задач позволит лучше понять базу кода, улучшить сопровождение и упростить эволюцию.
Я также хотел бы перенаправить вас к дальнейшему чтению этого поста в блоге "Приоритетное преобразование в TDD" от известного дяди Боба Мартина http://cleancoder.posterous.com/the-transformation-priority-premise
Вы можете использовать макеты, такие как jmock или easy 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()
}
Последний метод обеспечит вызов методов, заданных на основе ожиданий. Если не возникнет исключение, и тест не пройден.