一文彻底理解Java单元测试

Junit4

说起Java中的单元测试,我觉得大家首先想到的应该是Junit,比如下面这样的一个类和方法:

public class Calculator {
  public int evaluate(String expression) {
    int sum = 0;
    for (String summand: expression.split("\\+"))
      sum += Integer.valueOf(summand);
    return sum;
  }
}
复制代码

我们可以编写一个测试类 CalculatorTest.java,来对方法Calculator.evaluate((String expression)进行单元测试:

public class CalculatorTest {
  @Test
  public void evaluatesExpression() {
    Calculator calculator = new Calculator();
    int sum = calculator.evaluate("1+2+3");
    Assert.assertEquals(6, sum);
  }
}
复制代码

代码如上,可以看到,使用一个注解 @Test和断言语句Assert.assertEquals(6, sum);,便完成了一个单元测试的编写。

Test runners

一些常见的IDE都提供了对Junit的支持,可以直接在界面进行测试,无需我们自己去控制台run这些程序。
Junit默认的runner是:BlockJUnit4ClassRunner,可以使用@RunWith注解来指定runner,比如后续我们需要用到的SpringJUnit4ClassRunner

Assertions

上述例子中,要验证Calculator.evaluate的正确性,我们使用到了断言语句 Assert.assertEquals(6, sum);,在平常的单元测试中,我们都需要编写大量的断言语句。对于所有的基本类型,Junit都提供了断言方法,就像下面这样:

public class AssertTests {
  @Test
  public void testAssertNull() {
    assertNull("should be null", null);
  }

  @Test
  public void testAssertTrue() {
    assertTrue("failure - should be true", true);
  }
}
复制代码

Spring与Junit

在spring中,Spring TestContext Framework提供了对Junit的支持,通过使用@RunWith(SpringJUnit4ClassRunner.class)@RunWith(SpringRunner.class)注解,我们便能实现一个标准的Junit测试,并且能获取到Spring TestContext framework的支持,比如获取spring context、事务管理、依赖注入等等。

@ContextConfiguration

@ContextConfiguration用来加载和配置一个ApplicationContext,可以用指定xml的方式,也可以指定一个config class:

@ContextConfiguration("/test-config.xml") 
public class XmlApplicationContextTests {
    // class body...
}
@ContextConfiguration(classes = TestConfig.class) 
public class ConfigClassApplicationContextTests {
    // class body...
}
复制代码

即使用@RunWith(SpringRunner.class)@ContextConfiguration,就可以起一个spring的环境并进行测试了,如下:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CalculatorTest.TestConfig.class)
public class CalculatorTest {

    @Autowired
    private ApplicationContext applicationContext;

    @Test
    public void application() {
        Assert.assertNotNull(applicationContext);
        Assert.assertNotNull(applicationContext.getBean("testBean"));
    }

    @Configuration
    public static class TestConfig {
        @Bean
        public Object testBean() {
            return new Object();
        }
    }
}
复制代码

Mock

在真实的测试场景中,一个类往往会引用一个至多个bean,这些bean往往又会引用其他的bean,如果想在配置文件或类中配置完所有的bean,发现会越写越多,最后可能需要一份全量的bean配置了。

public abstract class AbstractSplitPkgStrategy extends AbstractCostSplitStrategy {

    @Resource
    private ISelfScItemAtomService              selfScItemAtomService;

    @Resource
    private GoodsCenterService                  goodsCenterService;

    @Resource
    private CostItemSplitMapper                 costItemSplitMapper;

    @Resource
    private SplitPkgByPcsMetaGenerateStrategy   pcsStrategy;

    @Resource
    private LstPackageReadService               lstPackageReadService;

    @Resource
    private TairManager                         commonTairManager;

    //方法...
}
复制代码

比如上面是我们要进行测试的一个类,当我尝试在xml或者config中配置它引用的bean的时候,我发现这些bean又引入了其他bean,然后得需要一层一层往下写配置。

Mockito

针对上述问题,我们可以使用Mockito来mock掉我们需要的bean。mock的配置也有多种方式,如下是两种配置方式:
使用xml进行配置:

//xml配置
<bean id="calculator" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="com.zuqiang.junit.Calculator"/>
</bean>
//test
@RunWith(SpringRunner.class)
@ContextConfiguration("/context.xml")
public class CalculatorTest {

    @Autowired
    private Calculator Calculator;

    @Test
    public void application() {
        Assert.assertNotNull(Calculator);
    }
}

复制代码

使用config class:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CalculatorTest.MockConfig.class)
public class CalculatorTest {

    @Autowired
    private Calculator calculator;

    @Test
    public void application() {
        Assert.assertNotNull(calculator);
    }

    @Configuration
    public static class MockConfig{

        @Bean
        public Calculator calculator() {
            return Mockito.mock(Calculator.class);
        }
    }
}
复制代码

mock的使用:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = CalculatorTest.MockConfig.class)
public class CalculatorTest {

    @Autowired
    private Calculator calculator;

    @Before
    public void before() {
        Mockito.when(calculator.evaluate(Mockito.anyString())).thenReturn(1);
    }

    @Test
    public void application() {
        Assert.assertEquals(1,calculator.evaluate("1+2"));
        Assert.assertEquals(1,calculator.evaluate("2+2"));
    }

    @Configuration
    public static class MockConfig {

        @Bean
        public Calculator calculator() {
            return Mockito.mock(Calculator.class);
        }
    }
}
复制代码

断言

在上面,我们已经看到了Junit提供的断言方法,对于一些简单的场景,我觉得没有什么问题。但是在一些复杂的场景下,可能会导致代码晦涩难懂。所以最后给大家介绍一个更好用的Java断言框架— AssertJ
比如下面这个比较简单的test:

    @Test
    public void test() throws Exception {
        //Arrange
        List<String> strings = Lists.newArrayList("a","b","c");
        //Act
        String removedString = strings.remove(0);
        //Assert
        assertThat(strings, containsInAnyOrder("b", "c"));
    }
复制代码

用AssertJ之后的写法像下面这样:

    @Test
    public void test() throws Exception {
        //Arrange
        List<String> strings = Lists.newArrayList("a","b","c");
        //Act
        String removedString = strings.remove(0);
        //Assert
        assertThat(strings).contains("b", "c");复制代码



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值