实现可测试代码时如何避免过度工程?附JSON处理示例
如何平衡JSON类实例化的可测试性与避免过度工程?
这种场景我太熟悉了——想让代码可测试,但又不想为了每个第三方库类都套一层工厂,搞得代码臃肿不堪。咱们一步步拆解问题,看看有哪些更灵活的方案:
1. 先反思:是否真的需要Mock JSON库的所有细节?
你之前的痛点是不想写真实JSON来测试逻辑,但其实可以换个思路:测试时用极简的合法JSON字符串,而非Mock整个JSONArray/JSONObject。这样既不用引入额外的工厂类,测试也更贴近真实业务场景。
比如你的测试可以改成这样:
@Test public void getAttributes_givenResponse_shouldReturnAttributes() { final Response response = mock(Response.class); // 用极简的合法JSON,完全不需要Mock任何JSON类 when(response.getEntityContentAsString()).thenReturn("[{\"name\":\"test\"}]"); final Map<String, Object> attributes = basicuserModule.getMappedAttributes(mock(Source.class), response); Set<String> expectedUsers = new HashSet<>(); expectedUsers.add("test"); assertThat(attributes.get("mappedUsers")).isEqualTo(expectedUsers); }
这种方式的好处:
- 代码保持简洁,没有额外的工厂类开销
- 测试逻辑和真实业务逻辑完全对齐,避免Mock带来的“测试过了但实际跑不起来”的问题
- 不用关心JSON库的实现细节,专注验证业务结果
2. 如果确实需要Mock,用适配器而非零散工厂
如果你的业务里经常需要对JSON做复杂操作,或者担心未来更换JSON库的成本,那可以考虑封装JSON操作的适配器,而非给每个JSON类单独写工厂。
比如先定义一个业务聚焦的接口:
public interface UserJsonProcessor { Set<String> extractUserNamesFromJson(String jsonArrayStr); // 可以添加其他业务相关的JSON操作方法 }
然后用第三方JSON库实现这个接口:
public class DefaultUserJsonProcessor implements UserJsonProcessor { @Override public Set<String> extractUserNamesFromJson(String jsonArrayStr) { JSONArray users = new JSONArray(jsonArrayStr); Set<String> names = new HashSet<>(); for (int i = 0; i < users.length(); i++) { names.add(users.getJSONObject(i).getString("name")); } return names; } }
业务代码就可以简化成:
@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<>(); Set<String> mappedUsers = userJsonProcessor.extractUserNamesFromJson(response.getEntityContentAsString()); attributes.put("mappedUsers", mappedUsers); return attributes; }
测试时只需要Mock这个适配器接口:
@Test public void getAttributes_givenResponse_shouldReturnAttributes() { final Response response = mock(Response.class); UserJsonProcessor mockProcessor = mock(UserJsonProcessor.class); Set<String> testNames = new HashSet<>(); testNames.add("test"); when(mockProcessor.extractUserNamesFromJson(anyString())).thenReturn(testNames); // 注入mockProcessor到basicuserModule final Map<String, Object> attributes = basicuserModule.getMappedAttributes(mock(Source.class), response); assertThat(attributes.get("mappedUsers")).isEqualTo(testNames); }
这种方式的优势:
- 只封装业务需要的操作,不会产生大量零散的工厂类
- 彻底隔离第三方JSON库的依赖,以后换库只需要修改适配器实现
- 测试完全聚焦业务逻辑,不用纠结JSON解析的底层细节
3. 什么时候适合用单一工厂类?
如果你的项目里大量直接实例化JSON类,而且确实需要灵活切换JSON库(比如从org.json换到Jackson),那可以考虑写一个统一的JsonFactory,而非每个JSON类单独建工厂:
public interface JsonFactory { JSONArray createArray(String json); JSONObject createObject(String json); // 其他通用创建方法 }
这样既满足了可测试性,又不会产生过多冗余类,便于统一管理。
4. 避免过度工程的核心原则
- 只封装频繁变化或存在替换风险的依赖:如果项目一直用同一个JSON库,且很少修改JSON解析逻辑,没必要为了测试强行加工厂
- 测试优先但不牺牲代码简洁性:测试的目的是保障质量,不是为了Mock而Mock。能用真实极简数据测试的,就不要Mock第三方库
- 和团队统一规范:和团队沟通清楚,不是所有第三方类都需要工厂,只针对那些确实影响测试或可能变更的依赖做封装
内容的提问来源于stack exchange,提问作者cmlonder
相关产品推荐
相关产品推荐

