在下面的示例中,我想测试给定的源,是否构建了正确的 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/