单元测试的关键是,断言。

Assert.assertEquals等各种各样的断言语句。

没有断言的“单元测试”,可能都是“伪单元测试”。

不知道输入,不知道输出,也是写不好单元测试的。

尽管你自称已经了解了函数的作用。

好处

   重复测试:前提是方法的输入参数和函数的作用没有发生大的变化。单元测试代码,写好一次就行了。

 下次,函数内部逻辑进行了修改,只要不影响输出,或者说不影响以前的 输入-输出 集合,那么之前写的测试代码就可以服用。

 

   多场景测试:通过构造不用的输入,得到不同的输出,再和预期值进行比较。测试很多种场景。

 

  分工测试:一般来说是当事人自己写。但是“你写我测”这种方式也是可以的,尤其是针对工具库。输入定下来了,函数的作用根据方法名也知道了,输出也就可以明确了。

 

 质量的保证:不写测试,根本不能保证质量。尤其是针对基础库,不可能随便写几个main函数,看看输出就给外界使用。

通过严格的单元测试,覆盖多种场景流程,简单点就是 代码分支覆盖率 要足够高。

 

坏处

    写起来,看起来很累。

   一些复杂的业务场景,输入条件难以构造,输出也不太好确定,不太方便测试。

 

什么场景适用

    工具类:Apache Commons之类的工具库,大多是static方法,依赖少,很方便测试。

    框架:MybatisPlus等开源框架,都自带基本的单元测试

    不经常变的业务:业务代码,简单一点的,或者不经常变化的,可以写测试用例。

  输入复杂,场景难以完整构造,就没必要了。

 

 

怎样写

     构造数据、执行、断言、回滚

 

普通项目

    

public static String hideMobile(String mobile) {
		if (StringUtils.isBlank(mobile) || mobile.length() != 11) {
			return mobile;
		}
		String header = mobile.substring(0, 3);
		String end = mobile.substring(mobile.length() - 4, mobile.length());
		String hideMobile = header + "****" + end;
		return hideMobile;
	}

 

   单元测试

       

import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;

import com.common.kit.JtnKit;

public class JtnKitTest {

	@Test
	public void testHideMoble() {
		String mobile = "15727305904";
		String mobileAfterHide = JtnKit.hideMobile(mobile);
		Assert.assertEquals("157****5904", mobileAfterHide);
		
		String mobile2 = "15827105904";
		String mobileAfterHide2 = JtnKit.hideMobile(mobile2);
		Assert.assertEquals("158****5904", mobileAfterHide2);
		
		String mobile3 = "1572730590";
		String mobileAfterHide3 = JtnKit.hideMobile(mobile3);
		Assert.assertEquals(mobile3, mobileAfterHide3);
	}

	@BeforeClass
	public static void oneTimeSetUp() {
		// one-time initialization code
		System.out.println("@BeforeClass - oneTimeSetUp");
	}

	@AfterClass
	public static void oneTimeTearDown() {
		// one-time cleanup code
		System.out.println("@AfterClass - oneTimeTearDown");
	}

	@Before
	public void setUp() {
		System.out.println("@Before - setUp");
	}

	@After
	public void tearDown() {
		System.out.println("@After - tearDown");
	}

}

 核心是@test标记的方法。

这个例子简单,没啥需要构造的输入输出数据,直接在 方法里 构造了。

setUp等方法都是可选的。

 

单元测试,核心是Assert断言,不需要用肉眼去看控制台输出。

而是看JUnit方法执行成功的个数。

写单元测试有啥用?怎样写好单元测试_ide

 

Web项目

   Dao和Service,SpringBoot,Spring项目。

   这里的add方法,算作准确数据。(有争议的,add方法本身还没经过测试。最最严格的单元测试,我也不太知道怎么写)

@Rollback
@Transactional

2个注解,开启事务,最后回滚。不对数据库有影响。

注解放在类上,所有方法都自动回滚。

  

import java.util.List;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;



import lombok.extern.slf4j.Slf4j;

// 连接,测试专用的数据库
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@Rollback
@Transactional
public class JtnPostServiceTest {

	@Autowired
	private JtnPostService jtnPostService;

	@Test
	public void testJtnPostCrud() {
		// 添加
		JtnPostCreate jtnPostCreate = new JtnPostCreate();
		String title = "title";
		String content = "content";
		jtnPostCreate.setTitle(title);
		jtnPostCreate.setContent(content);
		// 添加成功返回true,id应该大于0
		RpcResult<Integer> addRpcResult = jtnPostService.add(jtnPostCreate);
		Integer id = addRpcResult.getData();
		Assert.assertTrue("id > 0", id > 0);
		// 查询,title和content保持一致
		RpcResult<JtnPostVo> detailRpcResult = jtnPostService.getDetailById(id);
		JtnPostVo jtnPostVo = detailRpcResult.getData();
		Assert.assertEquals(title, jtnPostVo.getTitle());
		Assert.assertEquals(content, jtnPostVo.getContent());
		// 更新
		String titleUpdate = "titleUpdate";
		String contentUpdate = "contentUpdate";
		JtnPostUpdate jtnPostUpdate = new JtnPostUpdate();
		jtnPostUpdate.setId(id);
		jtnPostUpdate.setTitle(titleUpdate);
		jtnPostUpdate.setContent(contentUpdate);
		RpcResult<Boolean> updateRpcResult = jtnPostService.updateById(jtnPostUpdate);
		boolean updateSucceed = updateRpcResult.getData();
		Assert.assertTrue("updateSucceed shoud be true", updateSucceed);
		RpcResult<JtnPostVo> detailRpcResultAfterUpdate = jtnPostService.getDetailById(id);
		JtnPostVo jtnPostVoAfterUpdate=detailRpcResultAfterUpdate.getData();
		Assert.assertEquals(titleUpdate, jtnPostVoAfterUpdate.getTitle());
		Assert.assertEquals(contentUpdate, jtnPostVoAfterUpdate.getContent());
		// 删除成功
		RpcResult<Boolean> removeRpcResult = jtnPostService.removeById(id);
		boolean removeSucceed = removeRpcResult.getData();
		Assert.assertTrue("removeSucceed shoud be true", removeSucceed);
	}

	@Test
	public void testJtnPostQuery() {
		// 添加
		Integer appId = 12;
		JtnPostCreate jtnPostCreate = new JtnPostCreate();
		jtnPostCreate.setAppId(appId);
		String title = "title";
		String content = "content";
		jtnPostCreate.setTitle(title);
		jtnPostCreate.setContent(content);
		jtnPostService.add(jtnPostCreate);
		// 列表
		JtnPostQuery jtnPostQuery = new JtnPostQuery();
		jtnPostQuery.setAppId(appId);
		RpcResult<List<JtnPostVo>> listRpcResult = jtnPostService.list(jtnPostQuery);
		List<JtnPostVo> jtnPostVo = listRpcResult.getData();
		Assert.assertNotNull(jtnPostVo);
		Assert.assertEquals(1, jtnPostVo.size());
		// 分页
		jtnPostQuery.setCurrent(1);
		jtnPostQuery.setSize(10);
		RpcResult<Page<JtnPostVo>> listPageRpcResult = jtnPostService.listPage(jtnPostQuery);
		Page<JtnPostVo> page = listPageRpcResult.getData();
		Assert.assertEquals(1, page.getTotal());
		Assert.assertEquals(1, page.getRows().size());
	}

	

}

 

   Controller

     上面的例子中,直接注入 Controller实例,应该也是可以的。(没有尝试)     

 

  以上是用了SpringBoot的Spring Web项目,如果没有用SpringBoot,也是有单独的扩展工具的。

  具体忘了,嘿嘿。

 


JUnit5新语法

   有兴趣的自行了解。

   比如,支持参数化。

  

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

// TODO 升级Eclipse才能用 JUnit5
public class ValueSourcesExampleTest {

	@Test
	@ParameterizedTest
	@ValueSource(ints = { 2, 4, 8 })
	void testNumberShouldBeEven(int num) {
		Assertions.assertEquals(0, num % 2);
	}

	@ParameterizedTest
	@ValueSource(strings = { "Radar", "Rotor", "Tenet", "Madam", "Racecar" })
	void testStringShouldBePalindrome(String word) {
		Assertions.assertEquals(isPalindrome(word), true);
	}

	@ParameterizedTest
	@ValueSource(doubles = { 2.D, 4.D, 8.D })
	void testDoubleNumberBeEven(double num) {
		Assertions.assertEquals(0, num % 2);
	}

	//回文数
	boolean isPalindrome(String word) {
		return word.toLowerCase().equals(new StringBuffer(word.toLowerCase()).reverse().toString());
	}
}

Maven配置

<!-- junit4 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
		<!-- junit5 -->
		 <dependency>
		        <groupId>org.junit.jupiter</groupId>
		        <artifactId>junit-jupiter-engine</artifactId>
		        <version>5.2.0</version>
		        <scope>test</scope>
		    </dependency>
		    <dependency>
		        <groupId>org.junit.jupiter</groupId>
		        <artifactId>junit-jupiter-params</artifactId>
		        <version>5.2.0</version>
		        <scope>test</scope>
		    </dependency>
		    <dependency>
		        <groupId>org.junit.platform</groupId>
		        <artifactId>junit-platform-launcher</artifactId>
		        <version>1.2.0</version>
		          <scope>test</scope>
		    </dependency>