重构和测试

发布于 2024-12-11 20:44:41 字数 560 浏览 0 评论 0原文

我尝试以某种方式进行单元测试的场景示例:

一个方法获取三个参数:公司名称飞机编号 1飞机编号 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。 关于如何重构它有什么建议吗?是否有任何新的公共方法只是为了测试而创建?

An example of scenario I'm trying to unit test somehow:

A method gets three parameters: 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() ;
}

Now, I want to unit test this.

I do not have real plane numbers, I do not have live connection to the satellite.

Still I would like to be able to mock the whole thing somehow so that I can test this API.
Any suggestion on how to refactor it? Any new public method to be created just for testing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

夏日浅笑〃 2024-12-18 20:44:41

通常在重构未测试的代码时,我指的是练习 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 叔叔 撰写的这篇博客文章 TDD 中的转型优先前提 http://cleancoder.posterous.com/the-transformation-priority-premise

Usually when refactoring non-tested code, I'm referring to what I would do when practicing TDD and BDD.

So first I would write a simple contract, one line = one requirement (of the existing code).
Then while coding the test, I would probably notice things I don't want to test. In this case, DI will help you remove the code that has different responsibility to another class. When working in a object design it is important to focus on behavior and interaction between objects with different concerns. Mockito, with is alias class BDDMockito, can help you favor this approach.

In your case, here's an example of what you can do.

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());
    }
}

A simple JUnit test might look like (using features from 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
}

And you will have the following dependencies injected, each having his own unit test describing how the class behaves, considering the input and the collaborators :

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;
    }
}

Using this way you can refactor your code with a much lower coupling. The better separation of concerns will allow a better understanding of the code base, a better maintainability, and easier evolution.

I also would like to redirect you to further reading on this blog post The Transformation Priority Premise in TDD from the famous Uncle Bob Martin http://cleancoder.posterous.com/the-transformation-priority-premise

酷炫老祖宗 2024-12-18 20:44:41

您可以使用 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()
}  

最后一个方法将确保调用根据期望设置的方法。如果没有,则会引发异常并且测试失败。

You may use mocks like jmock or easy mock.

With jmock you'll write something like this:

@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()
}  

The last method will assure the methods setted on expectations are called. If not an exception is raised and the test fails.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文