背景

在公司的项目开发中,有时候需要用到其他同学封装的lib,由于结果比较特殊需要调用这个方法才才可以,不能直接Mock掉这个lib (由于核心是调用lib,mock后就只剩下组装参数了),这样意义就不大了。
举例如下: 核心就在于 write 的过程了,如果 mock 之后,就没有测试的必要了,验证不了正确性。

  1. public class GlobalLogUtil {
  2. public static final String AK_GLOBAL_TAG = "ak_global_tag";
  3. public static final String MODULE_CODE = "module.product";
  4. public static final String PAGE_CODE = "page.product.manager";
  5. public static final String BASIC = "basic";
  6. public static final String PC = "PC";
  7. public static final String EDIT = "action.edit";
  8. @Resource
  9. public GlobalLogWriter logWriter;
  10. @Async("asyncExecutor")
  11. public void insertEditLog(LogBean logBean) {
  12. LogTemplateRequest request = new LogTemplateRequest();
  13. request.setTemplateCode("template.product.local.edit");
  14. request.setAction(EDIT);
  15. base(logBean.companyId, logBean.userId, request, logBean.relationId);
  16. request.setTemplateType(LogTemplateType.DYNAMIC.getCode());
  17. request.setUserName(logBean.userName);
  18. request.setTerminalType(PC);
  19. request.setTemplateValueList(this.buildList(logBean));
  20. logWriter.write(request);
  21. }
  22. private void base(Long companyId, Long userId, LogTemplateRequest request, String relationId) {
  23. request.setCompanyId(companyId);
  24. request.setBusiness(BASIC);
  25. request.setBusinessCode(AK_GLOBAL_TAG);
  26. request.setModuleCode(MODULE_CODE);
  27. request.setPageCode(PAGE_CODE);
  28. request.setBusinessId(relationId);
  29. request.setUserId(userId);
  30. request.setDatetime(DateUtil.format(new Date(), "yyyy-MM-dd HH:mm:ss.SSS"));
  31. }
  32. private List<TemplateValue> buildList(LogBean logBean) {
  33. List<TemplateValue> templateValueList = new ArrayList<>();
  34. templateValueList.add(this.buildNameValue(logBean));
  35. final DynamicLogBody first = this.buildListValue("新增标签", logBean.addList);
  36. final DynamicLogBody second = this.buildListValue("移除标签", logBean.deleteList);
  37. final List<DynamicLogBody> dynamicLogBodyList = Lists.newArrayList(first, second);
  38. //插入操作日志
  39. TemplateValue v2 = new TemplateValue();
  40. v2.setType(LogValueType.DYNAMIC.getCode());
  41. v2.setDynamicLogBodyList(dynamicLogBodyList);
  42. templateValueList.add(v2);
  43. return templateValueList;
  44. }
  45. private TemplateValue buildNameValue(LogBean logBean) {
  46. TemplateValue value = new TemplateValue();
  47. value.setType(LogValueType.ORDINARY.getCode());
  48. value.setValue(logBean.productName);
  49. return value;
  50. }
  51. private DynamicLogBody buildListValue(String key, final List<String> tagList) {
  52. StringBuilder stringBuilder = new StringBuilder();
  53. if (CollUtil.isNotEmpty(tagList)) {
  54. stringBuilder.append(tagList.get(0));
  55. if (tagList.size() != 1) {
  56. stringBuilder.append("】");
  57. }
  58. for (int i = 1; i < tagList.size() - 1; i++) {
  59. final String s = tagList.get(i);
  60. stringBuilder.append("【").append(s).append("】");
  61. }
  62. if (tagList.size() > 1) {
  63. stringBuilder.append("【").append(tagList.get(tagList.size() - 1));
  64. }
  65. }
  66. List<DynamicLogValue> dynamicLogValueList = new ArrayList<>();
  67. DynamicLogValue dynamicLogValue = new DynamicLogValue();
  68. dynamicLogValue.setValue(stringBuilder.toString());
  69. dynamicLogValue.setValueType(0);
  70. dynamicLogValueList.add(dynamicLogValue);
  71. DynamicLogBody dynamicLogBody = new DynamicLogBody();
  72. dynamicLogBody.setKey(key);
  73. dynamicLogBody.setValueList(dynamicLogValueList);
  74. return dynamicLogBody;
  75. }
  76. @Getter
  77. @Setter
  78. @ToString
  79. @Builder
  80. @AllArgsConstructor
  81. @NoArgsConstructor
  82. public static class LogBean {
  83. public String productName;
  84. public String relationId;
  85. public Long companyId;
  86. public Long userId;
  87. public String userName;
  88. public List<String> addList;
  89. public List<String> deleteList;
  90. }
  91. }

然后write的内部代码逻辑是这样的

  1. @Configuration
  2. public class MessageUtil implements MessageSourceAware {
  3. private static MessageSource messageSource;
  4. public void setMessageSource(MessageSource messageSource) {
  5. MessageUtil.messageSource = messageSource;
  6. }
  7. public static String getMessage(String message, Object[] args) {
  8. Locale locale = LocaleContextHolder.getLocale();
  9. return messageSource.getMessage(message, args, message, locale);
  10. }
  11. }

这里其实就是利用的 Spring#MessageSource , 但是在单元测试的时候因为没有被 Spring 接管,所以必然会出现空指针。

为了解决这个问题,我提前将默认的messageSource设置进去,这样即使没有被Spring接管,他也不会出现空指针,从而达到跑通单元测试的效果。

尽管这次通过这种提前到设置可以跑通,但是如果下次我们遇到的 **ResourceBundleMessageSource** 类不是public的呢,如果下次遇到的内部类怎么办。

  1. class GlobalLogUtilTest {
  2. @Spy
  3. private GlobalLogWriter globalLogWriter;
  4. @InjectMocks
  5. GlobalLogUtil globalLogUtil;
  6. @BeforeEach
  7. void setUp() {
  8. MockitoAnnotations.openMocks(this);
  9. }
  10. @Test
  11. void testInsertEditLog() {
  12. //
  13. ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
  14. messageSource.setDefaultEncoding("UTF-8");
  15. messageSource.addBasenames("i18n.messages");
  16. MessageUtil messageUtil = new MessageUtil();
  17. messageUtil.setMessageSource(messageSource);
  18. {
  19. final GlobalLogUtil.LogBean logBean = GlobalLogUtil.LogBean.builder()
  20. .productName("笑嘻嘻sku")
  21. .relationId("relationId")
  22. .companyId(911L)
  23. .userId(911L)
  24. .userName("陈顺")
  25. .addList(Lists.newArrayList("第三个"))
  26. .deleteList(Lists.newArrayList("删除第一个", "删除第三个"))
  27. .build();
  28. globalLogUtil.insertEditLog(logBean);
  29. }
  30. }
  31. }

总结

其实我真实想要表达的是,如果对于整体的逻辑过程没有一个庖丁解牛般的认识,那么总会遇到各种各样的错误,遇到错误的时候,解决方案一定要灵活,黑猫白猫,抓到老鼠就是好猫。

今天只是一个静态字段没有设置的空指针可以提前设置上,后天如果对象不是内部类,是运行时生成的,那么该怎么去处理呢。再推而广之,今天碰到的是这样的问题,明天后天碰到其他的问题呢,解决问题要头脑灵活,要天马行空(但是要有效)。但是灵活,天马行空又是对日常问题的结果积累,思考总结。

虽然写完了,但感觉没有把我想表达的含义表述出来,也许后边我自己再来回顾也不能理解自己为什么要这么写。