代码整洁之道(Clean Code) 笔记(三)

第八章:边界

本章关于如何学习使用第三方组件

  1. 第三组件或框架追求普适性,而使用者则想要集中满足特定的需求
  2. 学习第三方组件首当其冲当然还是文档,其次重要的是它的单元测试,我甚至是把单元测试当作文档的一部分来看待。其实更重要的方法是学习性测试(learning tests), 通过测试来学习才是切实的体验,是一种精确的试验,我们甚至可以用自己的测试来验证第三方组件的新版本
  3. 边界上会发生有趣的事,这就要求我们对它清晰的分割和定义,以避免我们的代码过多地了解第三方组件中的特定信息,依靠你能控制的东西好过依靠你控制不了的东西,免得日后受它控制。简而言之就是尽可能隔离边界,在边界改动时只需要修改一下适配器,对的适配

第九章:单元测试

这一章我自认为是很重要的,特别是正在采用测试驱动开发/设计(TDD) 的程序员来讲。充分,良好的单元测试是保证我对生产代码大刀阔斧的关键,这也就是 TDD 的最后一个 D 又能理解了 Design 的原由

阅读全文 >>

JMockit 中捕获 mock 对象方法的调用参数

三个月前写过一篇 Mockito 中捕获 mock 对象方法的调用参数,一般项目中 Mockito 不决求助于 JMockit, 同样的在 JMockit 也需对捕获被 Mock 的方法调用参数。当我们用 new Expectations(){{}} 打桩并在后面断言了返回值,那就无需捕获参数来断言,匹配到了方法调用即证明传入的参数也是对的,如下面的代码所示

public class UserServiceTest {

    @Mocked
    private UserDao userDao;

    @Test
    public void couldCallUserDaoToAddUser() {
        new Expectations(){{
           userDao.findById(123);
           result = "Yanbin";
        }};

        UserService userService = new UserService(userDao);
        String user = userService.findBy(123);

        assertThat(user).isEqualTo("Yanbin");  //这里断言成功也就证明了 userDao.findById(123) 方法被调用,参数必须是 123
    }
}

但如果是未打桩的方法,或打桩是用的模糊参数(withInstanceOf(String.class)), 或是无返回值的方法就要事后对是否调用了某个方法以及传入什么参数的情况进行断言。 阅读全文 >>

使用 Awaitility 测试异步代码

对于同步方法的测试很简单,调用完后可立马检查执行状态; 而异步方法,由于我们无法确切的知道何时结束,因此以往的办法是用 Thread.sleep(500) 来预估一个执行时间。然后通常我们估计的要长于实际的时间,这就很浪费,况且偶然的超过预估的等待时间也并不意味着代码有问题。还有 sleep 方法还抛出一个检测异常 InterruptedException, 一般会要对 Thread.sleep(500) 作下简单包装。

于是今天要介绍的 Awaitility 就应运而生了,专门针对异步方法的测试。它的官方文档在 https://github.com/awaitility/awaitility/wiki/Usage。本文主要关注在 Java 8 环境下用 Lambda 的代码书写方式。Awaitlity 实际运行是以某种轮询的方式来检查是否达到某个运行状态,可设定最多,最少等待时间,或永久等待,或自定义轮询策略,之后就开始进行需要的断言,所以它可以尽可能的节省测试异步方法所需的时长。而不像 Thread.sleep(500) 一路等到黑,并且没有回头路。

通常我会在项目中给 JUnit 配上三个最佳伴侣,它们是(按 mvn dependency:tree 中的显示方式):

  1. org.awaitility:awaitility:2.0.0:test
  2. org.assertj:assertj-core: version: 3.8.0:test
  3. org.mockito:mockito-core:2.7.22:test

当然如果项目中没有异步调用自然是不需要 Awaitility, 在我的项目中是基本不可能的。以上三种都追求 DSL,以流畅的方式进行愉快的测试。

现在来尝试下 Awaitility 的几种基本的用法,先假定有下面的代码 UserService 阅读全文 >>

代码整洁之道(Clean Code) 笔记(二)

第四章:注释

  1. 别给糟糕的代码加注释 -- 重新写吧
  2. 什么也不会比乱七八糟的注释更有本事搞乱一个模块。什么也不会比陈旧,提供错误信息的注释更有破坏性
  3. 若编程语方足够有表达力,或者我们长于用这些语方来表达意图,就不那么需要注释 -- 也许根本不需要
  4. 注释的恰当用法是弥补我们在用代码表达意图时遭遇的失败。注释总是一种失败
  5. 不准确的注释要比没注释坏得多。它们满口胡言。代码,只有代码能忠实的告诉你它做的事
  6. 好注释不多,如法律信息,警示等。我都不觉得 TODO 是多好的东西
  7. 大多数注释都是坏注释,甚至一些 IDE 自动生成的代码也加一堆的废话注释,分散阅读注意力。我也写过那种日志式注释,这本是版本控制系统干的事。
  8. 很奇怪 IntelliJ IDEA 还在自动为新建的类加下创建者,代码是团队共有,时间长了根本就与作者无半毛关系。我经常碰到这种写有作者的代码哭笑不得,直接把原作者改了或删了也不太好,还不如把文件删了,新建一个没有作者注释的类
  9. 直接把代码注释掉是种讨厌的做法,自己可能不会再启用,其他人以为很重要更不敢删。不用了最好果断删掉,版本控制系统会帮你记住历史。
  10. 个人认为,注释会胡说八道,产品代码和测试代码不会撒谎。有功夫写注释还不如写好测试用例,测试需要维护,它本身就是最真实的文档。

阅读全文 >>

使用 Mockito 修改私有属性

修改私有属性来 Mock 可能不是一种很好的测试方式, 因为属性名是动态的,但有时不得已而为了,例如下面的代码:

public class UserService {
    private ExternalApi external = ExternalApi.default();
    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public User findUserById(int id) {
        return userDao.findById(external.convertId(id));
}

测试时欲隔离对 ExternalApi 的外部依赖, 当然可以把它也作为构造函数的一个参数,这样创建 UserService 实例时就可以 Mock external 属性。不过 external 经常是不变的,所以作为方法参数的必要性也不大。这就希望能在构造出 UserService 之后对 external 私有属性进行 Mock 处理。

在 Mockito 1.x 和 2.x 下要使用不同的方式,分别使用到 Whitebox 和 FieldSetter 类,它们都来自于  mockito.internal.util.reflection 包,可见 Mockito 打心底不推荐直接使用它们,但谁叫它们是 public 的呢。还有一种方式是使用 PowerMock + Mockito, 这是后话。 阅读全文 >>