单元测试(junit+dubbo+mockito)

关于单元测试请先回忆下面几个问题:

1)单元测试是否依赖网络?如果依赖网络,当没有网的时候怎么办?

2)单元测试是否支持多次可重复执行?

3)dubbo接口怎么单元测试?

4)如何计算单元测试对代码的覆盖率?

在回答上面几个问题前请先看下面介绍:

1、准备测试环境

1)引入依赖包:

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-all</artifactId>
            <version>1.10.19</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.10.RELEASE</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>dubbo</artifactId>
            <version>2.8.4</version>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <version>1.4.197</version>
            <scope>test</scope>
        </dependency>

2)服务启动时加载sql脚本配置

	<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
		<jdbc:script location="classpath:data/init-table.sql" />
		<jdbc:script location="classpath:data/import-data.sql" />
	</jdbc:initialize-database>

3)sql脚本

init-table.sql


SET MODE MYSQL;

drop table if exists `user_1`;
create table `user_1` (
  `id`     int(10) not null primary key AUTO_INCREMENT ,
  `name`   varchar(20) not null default '' ,
  `status` int(11) not null DEFAULT 0
);
import-data.sql
SET MODE MYSQL;

insert into `user_1`(`id`,`name`,`status`)values(100,'test1',0);

4)准备单元测试基类,所有测试类继承该基类。

@RunWith(SpringJUnit4ClassRunner.class)  //使用junit4进行测试
@ContextConfiguration({"classpath:spring/spring-config.xml"}) //加载配置文件
public class CommonTest {

    @Test
    public void test(){

    }
}

spring-config.xml对应web.xml中的配置

	<context-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>
			classpath*:/spring/spring-config.xml;
		</param-value>
	</context-param>

2、使用Junit进行常规单元测试

需要测试的service,包含增、删、改、查等接口。

@Service
public class UserService {

    public static final String ERROR_MSG = "this is test exception!";

    @Resource
    private UserDao userDao;

    public int saveUser(User user) {
        return userDao.saveUser(user);
    }

    public User queryUserById(int id) {
        return userDao.queryUserById(id);
    }

    public int updateUserById(User user) {
        Preconditions.checkNotNull(user, "用户信息不能为空!");
        return userDao.updateUserById(user);
    }

    public int deleteUserById(int id) {
        Preconditions.checkArgument(id > 0, "id(%s)必须大于0!", id);
        return userDao.deleteUserById(id);
    }

    public void testException() {
        throw new IllegalStateException(ERROR_MSG);
    }
}

单元测试类(继承上面的基类CommonTest.java)注入Service使用@Resource或@Autowired

//继承单元测试基类CommonTest.java
public class UserServiceTest extends CommonTest {

    @Resource //注入Service使用@Resource或@Autowired
    private UserService userService;

    /**
     * 创建测试模型
     */
    private User createUser() {
        Random random = new Random(1000000);
        User user = new User();
        user.setStatus(0);
        user.setName("new name_" + random.nextInt());
        return user;
    }

    @Test
    public void saveUser() {
        User user = createUser();
        // 保存数据
        userService.saveUser(user);
        Assert.assertTrue(user.getId() > 0);

        // 校验保存的数据与查询出来的数据是否一致
        User temp = userService.queryUserById(user.getId());
        Assert.assertNotNull(temp);
        Assert.assertNotNull(temp.getName());
        Assert.assertEquals(user.getName(), temp.getName());
        Assert.assertEquals(user.getStatus(), temp.getStatus());
    }

    @Test
    public void queryUserById() {
        // 检查初始化脚本加载数据是否正确
        User user = userService.queryUserById(100);
        Assert.assertNotNull(user);
        Assert.assertNotNull(user.getName());
        Assert.assertEquals("test1", user.getName());
    }

    @Test
    public void updateUserById() {
        User user = createUser();
        // 保存数据
        userService.saveUser(user);
        int id = user.getId();
        Assert.assertTrue(id > 0);

        // 校验保存的数据与查询出来的数据是否一致
        User temp = userService.queryUserById(id);
        Assert.assertNotNull(temp);
        Assert.assertNotNull(temp.getName());
        Assert.assertEquals(user.getName(), temp.getName());
        Assert.assertEquals(user.getStatus(), temp.getStatus());
        // 修改信息+更新数据
        user.setStatus(1000);
        user.setName("new-name" + new Random(1000000).nextInt());
        int updateResult = userService.updateUserById(user);
        Assert.assertEquals(1, updateResult);

        // 校验更新数据是否成功
        temp = userService.queryUserById(id);
        Assert.assertNotNull(temp);
        Assert.assertNotNull(temp.getName());
        Assert.assertEquals(user.getName(), temp.getName());
        Assert.assertEquals(user.getStatus(), temp.getStatus());
    }

    @Test
    public void deleteUserById() {
        // 检验数据库是否存在数据
        User user = userService.queryUserById(100);
        Assert.assertNotNull(user);
        Assert.assertNotNull(user.getName());
        Assert.assertEquals("test1", user.getName());

        // 校验删除
        int result = userService.deleteUserById(100);
        Assert.assertEquals(1, result);
    }
}

增、删、改、查主流程测试要点:

查询:初始化数据库表结构时导入数据,校验查询功能是否正常

新增:新增后需要再从数据查询,确保保存的数据与保存后查询的数据一致

修改:

1)先保存数据;

2)再根据保存的数据返回的主键查询数据,确认可在成功;

3)然后修改数据,从数据库获取数据,与修改的数据是否一致

删除:先校验数据是否存在,如果存在,再校验删除功能是否正常

3、Mockito工具介绍:

public class MockitoTest extends CommonTest {

    @InjectMocks    // mock注入说明:会注入变量
    @Resource
    private UserService userService;

    @Mock           // mock一个实例,注入到上面声明的 UserService 中
    @Resource
    private UserDao userDao;

    @Rule           // 对异常代码进行mock声明
    public ExpectedException thrown = ExpectedException.none();

    @Before
    public void setUp() {
        // mock注解声明及初始化
        MockitoAnnotations.initMocks(this);

        // 针对 @Mock 类中的方法进行定制:当调用该接口时返回固定值
        when(userDao.saveUser(any(User.class))).thenReturn(1314);
        when(userDao.updateUserById(any(User.class))).thenReturn(111);
    }

    @Test
    public void testMock() {
        // 调用前面指定的mock接口
        int result = userService.saveUser(new User());
        // 检验返回的固定值
        Assert.assertEquals(1314, result);

        // 调用前面指定的mock接口
        result = userService.updateUserById(new User());
        // 检验返回的固定值
        Assert.assertEquals(111, result);
    }

    @Test
    public void testThrown() {
        // 检验异常类
        thrown.expect(IllegalStateException.class);
        // 检验异常信息
        thrown.expectMessage(UserService.ERROR_MSG);
        // 开始执行方法
        userService.testException();
    }
}

要点介绍:

1)mock service时使用 @InjectMocks

2)mock上面servicek中的变量时,使用 @Mock

3)mock异常时使用 (需要校验异常类及异常信息)

@Rule           // 对异常代码进行mock声明
public ExpectedException thrown = ExpectedException.none();

4)@Before中初始化:MockitoAnnotations.initMocks(this);

5)mock类中的方法时使用when(接口名(参数名)).thenRerutn(返回值),样例如下:

when(userDao.saveUser(any(User.class))).thenReturn(1314);

4、dubbo接口mock介绍

1)在test/resource/*中配置dubbo

<dubbo:application name="dubbo-application" owner="yaohua" organization="xx"/>
<dubbo:consumer timeout="60000" retries="0"/>
<!-- mock专用 -->
<dubbo:reference interface="com.example.yaohua.service.DubboService" mock="com.example.yaohua.dubbo.DubboServiceImpl"/>

配置中对应的实现类

@Service
public class DubboServiceImpl implements DubboService {
    @Override
    public String sayHello(int id) {
        if(id == 1){
            return "123456";
        }else {
            return "000000";
        }
    }
}

dubbo单元测试类(继承基类CommonTest.java)

public class DubboTest extends CommonTest {

    @Resource
    private DubboService dubboService;

    @Test
    public void testDubbo(){
        String hello = dubboService.sayHello(1);
        Assert.assertNotNull(hello);
        Assert.assertEquals(hello,"123456");

        hello = dubboService.sayHello(123);
        Assert.assertNotNull(hello);
        Assert.assertEquals(hello,"000000");
    }
}

要点介绍:

1)需要配置dubbo配置信息,覆盖正常配置。

2)可实现并自定义实现类

3)正常调用dubbo接口时,返回的结果是上面自定义实现类中返回值

 

总结:

1)单元测试不依赖网络:dubbo方法、http调用接口使用Mock;jdbc使用内存数据库(H2)

2)单元测试需要支持多次可重复执行:服务每次启动时都会加载sql脚本(重新初始化表结构及导入原始数据),保证数据在每次执行都是同样的状态

3)dubbo接口mock测试:使用本地伪装

4)如何计算单元测试对代码的覆盖率:使用sonar,详细介绍待续

本文代码见:https://gitee.com/liuyaohua/spring-simple/tree/master/dubbo-test

本文已同步更新到公众号,沟通交流请关注公众号。

 

  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Spring Boot是一个用于构建Java应用程序的开源框架,它提供了一种简化了配置的方式来快速构建应用程序。JUnit是一个用于编写和运行单元测试的开源测试框架,而Mockito是一个用于创建和管理模拟对象的Java库。 下面是一个使用Spring Boot、JUnitMockito进行单元测试的示例: 假设我们有一个UserService类,它依赖于一个UserRepository接口来访问数据库并进行一些操作。我们想要对UserService的方法进行单元测试。 首先,我们需要创建一个测试类,命名为UserServiceTest。在测试类中,我们将使用JUnit的注解来标记测试方法,并使用Mockito来创建模拟对象。示例代码如下: ```java @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { @InjectMocks private UserService userService; @Mock private UserRepository userRepository; @Test public void testGetUserById() { // 配置模拟对象的行为 User user = new User("1", "John"); when(userRepository.findById("1")).thenReturn(user); // 调用被测试的方法 User result = userService.getUserById("1"); // 验证结果 assertEquals("John", result.getName()); } } ``` 在上面的示例中,我们使用了@RunWith注解来指定使用MockitoJUnitRunner运行测试,这样就能自动创建和管理模拟对象。使用@InjectMocks注解将被测试的对象自动注入到测试类中,使用@Mock注解创建模拟对象。 在testGetUserById方法中,我们首先使用when方法配置userRepository模拟对象的行为,表示当传入参数为"1"时,返回一个指定的User对象。 然后,我们通过调用userService的getUserById方法来测试该方法的逻辑。最后,使用assertEquals断言来验证结果是否符合预期。 以上就是一个使用Spring Boot、JUnitMockito进行单元测试的示例。通过使用Mockito创建模拟对象,我们可以更容易地测试各个方法的逻辑,而不依赖于实际的数据库。这样可以提高测试效率并确保代码的质量。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值