java - 如何在尝试实现可测试代码的同时防止过度工程?

标签 java unit-testing testing

在下面的示例中,我想测试给定的源,是否构建了正确的 JSON 并将其映射到返回的对象中。起初,代码在其中创建了新的对象,如下所示:

@Override
public Map<String, Object> getAttributes( Source source, Response response )
{
    Objects.requireNonNull( response, "response can not be null" );

    final Map<String, Object> attributes = new HashMap<>( );

    final JSONArray users = new JSONArray( response.getEntityContentAsString( ) );
    final Set<String> mappedUsers = new HashSet<>( );
    for ( int i = 0; i < users.length( ); i++ )
    {
        mappedUsers.add( users.getJSONObject( i ).getString( "name" ) );
    }
    attributes.put( "mappedUsers", mappedUsers );

    return attributes;
}

但它有问题,首先,我不想使用 PowerMock 或其他反射实用程序来模拟新对象的创建。但是要测试这段代码;

  • 我必须在 response.getEntityContentAsString( ) 中返回正确的 JSON,因为我没有模拟 JSONArray,它应该创建正确的对象。这意味着每次我只想测试此代码行为时,我都必须修改此“虚拟”json。我必须在对象内添加“名称”属性,或者我必须使其长度适合循环。

为了防止这种情况,我想介绍在工厂内创建新对象。现在:

@Override
public Map<String, Object> getAttributes( Source source, Response response )
{
    Objects.requireNonNull( response, "response can not be null" );

    final Map<String, Object> attributes = new HashMap<>( );

    final JSONArray users = jsonArrayFactory.create( response.getEntityContentAsString( ) );
    final Set<String> mappedUsers = new HashSet<>( );
    for ( int i = 0; i < users.length( ); i++ )
    {
        mappedUsers.add( users.getJSONObject( i ).getString( "name" ) );
    }
    attributes.put( "mappedUsers", mappedUsers );

    return attributes;
}

在我的测试中,我可以模拟它而不是处理与 JSONArray 类一起正常工作的自定义 JSON。此外,我不必处理 JSONArray 的实现,因为我对函数的库细节不感兴趣。但是现在它似乎过度设计了,因为有很多这样的情况; JSONArray、JSONObject、JSONString等直接在创建的项目中。现在团队觉得他们必须创建所有这些工厂 JSONArrayFactory、JSONObjectFactory 等。

在这个例子中你会做什么?也许我们应该改变我们测试功能的方式?您如何处理新对象的创建并防止第 3 方的实现细节?

给定代码的测试:

@Test
public void getAttributes_givenResponse_shouldReturnAttributes( )
{
    final Response response = mock( Response.class );
    final JSONArray users = mock( JSONArray.class );
    final JSONObject user = mock( JSONObject.class );
    users.put( user );
    final String sampleContentEntity = "";
    final Integer sampleusersLength = 1;

    final String simpleName = "name";
    final Map<String, Object> expectedAttributes = new HashMap<>( );
    final Set<String> mappedUsers = new HashSet<>( );
    mappedUsers.add( simpleName );
    expectedAttributes.put( "mappedUsers", mappedUsers );

    when( response.getEntityContentAsString( ) ).thenReturn( sampleContentEntity );
    when( jsonArrayFactory.create( eq( sampleContentEntity ) ) ).thenReturn( users );
    when( users.length( ) ).thenReturn( sampleusersLength );
    when( users.getJSONObject( anyInt( ) ) ).thenReturn( user );
    when( user.getString( eq( "name" ) ) ).thenReturn( simpleName );

    final Map<String, Object> attributes =
        basicuserModule.getMappedAttributes( mock( Source.class ), response );

    assertThat( attributes ).isEqualTo( expectedAttributes );

    verify( response ).getEntityContentAsString( );
    verify( jsonArrayFactory ).create( eq( sampleContentEntity ) );
    verify( users ).getJSONObject( anyInt( ) );
}

最佳答案

它总是一种妥协。

但是这种折衷是双向的:如果你对所有东西都使用工厂,那么是的,你将能够模拟出几乎所有东西,并且你不会在经过测试的方法中有任何单一的"new",但是,您的测试将看起来像一长串模拟,并且很难阅读/理解/维护测试,而且它,IMO,测试的强制性要求。

还有一点需要考虑,黑盒测试比白盒测试要好得多。 在您的情况下,您不会返回 JSONArray users,您只需将其创建为内部变量来执行您的内部计算。

现在,理想情况下,测试应该检查给定输入参数列表,该方法是否返回预期值,仅此而已,测试不应该摆弄诸如“如果我想让它通过,我必须创建以这种特殊方式在这里创造内部值(value),然后创造另一个类似的值(value)”。这一切都使得测试不明确且非常脆弱。

所以这里有一些“经验法则”:

  • 总是喜欢黑盒测试。不要检查方法内部做了什么,最好在编写测试时甚至不要查看被测类的实现。

  • 始终尝试编写在给定参数集的情况下实际返回某些内容的方法 这将使测试更容易阅读和理解

  • 仅 Mock/Stub 交互 - 类需要的真正依赖项。通常这些并不多,而且它们出现在非常具体的点上。不要模拟内部变量的创建、就地完成的静态计算的结果或返回值。

例子:

// mocking example:
class SomeService {
    private SomeDAO dao; // this is a real dependency, mock it in test
}

// don't mock
Math.max(a,b)

// don't mock
LocalDateTime.of(...)

// don't mock
public int f() {
  ...
  List<Integer> internalVariable = new ArrayList<>(..)
}

关于java - 如何在尝试实现可测试代码的同时防止过度工程?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57552303/

相关文章:

ruby-on-rails - 如何计算平均请求持续时间?

java - spring security ldap隐藏密码属性

java - 如何使用 hibernate util 优化 dao?

c# - 如何使用 InMemory 数据库检查在单元测试中正确添加的记录

unit-testing - 在生产代码中提取类后是否应该创建新的测试类?

performance - yslow 只是说 28 个组件未添加到 CDN 我如何获得完整的组件详细信息

unit-testing - 在单元测试中使用随机数与硬编码值

java - 为什么我的哈希集如此耗费内存?

java - MainActivity 到 Fragment,然后从该 Fragment 返回到 MainActivity

java - 请确认/评论我在 EclEmma Eclipse 插件中看到的分支覆盖率问题