Unit Test Summary1

4 篇文章 0 订阅

测试用例需要根据具体功能进行编写,需要将关注的功能点都测试到。测试的基本策略是,输入一个满足某个业务场景的数据,看得到的输出是否是期望的值。当预期没有达到时,我们修改现有的代码来达到预期。不断重复这个过程,尽最大可能覆盖可以考虑到的功能点。从而为提交的代码提供最基本的验证。

最近的单元测试编写,一开始我采用的是@SpringBootTest。但是由于我测试的类所在的package下面有很多其他的类,这些类的依赖比较复杂,当我采用@Autowired注入我测试的类时,需要去处理很多与该类无关的依赖信息。然后,我做了一个折衷:被测试的类采用new构建,它的依赖采用@MockBean注入。为了将这些采用@MockBean的依赖加载到我的测试类里,我将测试类里面的对应的依赖采用构造注入,我修改了被测试类的源码,如下:

class MyService {
    //@Autowired //最初我用的是@Autowired, 因为我觉得这样比较简洁
    private Class1 class1;
    //@Autowired
    private Class2 class2;
    
    public MyService(Class1 class1, Class2 class2){
        this.class1 = class1;
        this.class2 = class2;
    }
}

单元测试用例里就用这个构造器来初始化这个类,如下:

@RunWith(SpringRunner.class)
@SpringBootTest
class MyServiceTest {
    @MockBean
    private Class1 class1;
    @MockBean
    private Class2 class2;
    private MyService service;
    
    @Before
    public void setUp(){
        service = new MyService(class1, class2);
    }
}

因为我的服务类里面有调用第三方的rest服务,所以我采用了WireMockServer来mock对rest服务的调用。测试用例通过以上的方式运行起来了,代码覆盖率还ok。但是,有一些功能点没有测试到。代码里面有的功能依赖了配置参数,而这些参数是通过@Value的方式注入的,要让这些配置生效,就必须采用Spring的方式注入服务类,而注入服务类的话,需要解决很多与当前功能无关的依赖,这不是我想处理的。而且目前采用SpringBootTest做单元测试,每次启动都会加载很多依赖,我觉得这是不必要的。

最后,我放弃了已经写好的单元测试,而采用纯Mockito的方式重写单元测试。我采用的方式是:每个类的单元测试只关注当前类本身,所有的依赖都采用@Mock的方式注入被测试类。而在之前的测试用例中,我写了很多对rest服务调用的假设(采用WireMock的方式,现在我觉得与rest服务的交互的测试,应该用rest服务的代理类来测试),我觉得是没有必要的,只需要假设一个期望的返回结果就可以了。因为对于当前的测试类,需要测试的只是该类包含的我们关注的业务或功能逻辑。明白了这一点,我很快重写好了单元测试。

@RunWith(MockitoJunitRunner.class)
class MyServiceTest {
    @Mock
    private Class1 class1;
    @Mock
    private Class2 class2;
    @InjectMocks
    private MyService service;
    
    @Test
    public void testFeature1_shouldxxxxWhenxxx(){...}
    @Test
    public void testFeature2_shouldxxxxWhenxxx(){...}
}

现在,因为无需加载Spring的依赖,用例的执行快了一些。

但是,还是之前的问题,采用@Value注入的参数为null,有一些功能点没测试到。如下:

@Value("${display.conent1}")
private String content1;
@Value("${display.content2}")
private String content2;

void setDisplayContent(Display display, Criteria criteria){
    if(criteria.flagVal == 1)
        display.setContent(content1);
    else if(criteria.flagVal == 2)
        display.setContent(content2);
}

最后,我采用反射的方式来注入这些参数(问题解决了),如下:

@Before
public void setUp(){
    try{
        Field content1 = service.getClass().getDeclaredField("content1");
        content1.setAccessible(true);
        content1.set(service, "xxxxx1");
        Field content2 = service.getClass().getDeclaredField("content2");
        content2.setAccessible(true);
        content2.set(service, "xxxxx2");
    } catch(Exception ex){//...}
}

我的总结是:

1. 单元测试只需要测试当前类负责的功能,依赖都采用mock的方式;

2. 用最少的依赖来完成单元测试的编写;

3. 每个类尽量做到职责单一。即便不是单一的功能,但至少从业务或功能的角度来看,它负责处理的必须是同一类工作,可能是几个过程一起来达到一个目的。通过将各个职责用单独的类封装起来之后,我们mock的时候会更加方便。将同一个或同一种职责封装在一个类里面,可以减少各个类之间不必要的依赖,因为该类必须的过程已经包含在它自己的上下文中,它的功能的各个部分已经自包含,从而它依赖或调用的必然是在它的职责之外的一些功能组件。因此,我们需要合理的划分功能,以让它们之间的交互最少,从而可以得到一个简洁稳定,易于维护的系统。当然,我们不会一开始就得到,但在不断的分析和修改之后,各个功能之间的界限会日渐清晰。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值