《我要进大厂系列 五》-谈谈你常用的测试框架有哪些?

Posted 熊猫IT学院

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《我要进大厂系列 五》-谈谈你常用的测试框架有哪些?相关的知识,希望对你有一定的参考价值。

1.Junit的使用

1.1.Junit 是什么

JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中最为成功的一个JUnit有它自己的JUnit扩展生态圈。多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
注意:Junit 测试也是程序员测试,即所谓的白盒测试,它需要程序员知道被测试的代码如何完成功能,以及完成什么样的功能。

白盒测试:  Junit 测试也是程序员测试,即所谓的白盒测试 程序内部细节
黑盒测试:  只需要测试功能好不好用

1.2.Junit 能做什么

我们知道 Junit 是一个单元测试框架,那么使用 Junit 能让我们快速的完成单元测试。
通常我们写完代码想要测试这段代码的正确性,那么必须新建一个类,然后创建一个 main() 方法,然后编写测试代码。如果需要测试的代码很多呢?那么要么就会建很多main() 方法来测试,要么将其全部写在一个 main() 方法里面。这也会大大的增加测试的复杂度,降低程序员的测试积极性。而 Junit 能很好的解决这个问题,简化单元测试,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。

1.3.Junit 的用法

 首先下载 Junit jar 包

<!-- Junit -->
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.12</version>
</dependency>

 我们先看下面这个例子,看一下 Junit 的用法
①、编写代码(需要测试的类)

public class Calculator {

	/**
	 * 传入两个参数,求和
	 * @param a
	 * @param b
	 * @return
	 */
	public int add(int a, int b) {

		return a + b;
	}

	/**
	 * 传入两个参数,求差
	 * @param a
	 * @param b
	 * @return
	 */
	public int sub(int a, int b) {

		return a - b;
	}
}

②、编写测试类
 不用Junit

public class CalculatorTest {

	public static void main(String[] args) {
		Calculator c = new Calculator();
		// 测试 add()方法
		int result = c.add(1, 2);
		if (result == 3) {
			System.out.println("add()方法正确");
		}

		// 测试 sub()方法
		int result2 = c.sub(2, 1);
		if (result2 == 1) {
			System.out.println("sub()方法正确");
		}
	}
}

那么我们可以看到,不用 Junit 只能写在 main()方法中,通过运行结果来判断测试结果是否正确。这里需要测试的只有两个方法,如果有很多方法,那么测试代码就会变得很混乱。

 使用 Junit(看不懂 Assert.assertEquals()方法没关系,可以自己写 if()语句来判断)

public class CalculatorTest1 {

	@Test
	// 测试 add()方法
	public void testAdd() {
		Calculator c = new Calculator();
		int result = c.add(1, 2);
		Assert.assertEquals(result, 3);
	}

	@Test
	// 测试 sub()方法
	public void testSub() {
		Calculator c = new Calculator();
		int result = c.sub(2, 1);
		Assert.assertEquals(result, 1);
	}
}

那么由上面可以看到,使用 Junit 不需要创建 main() 方法,而且每个测试方法一一对应,逻辑特别清晰。可能有读者会问,这样写代码量也并不会减少啊,那么你接着往下看:

首先介绍 Junit 的几种类似于 @Test 的注解:
  1.@Test: 测试方法
   a)(expected=XXException.class)如果程序的异常和XXException.class一样,则测试通过
   b)(timeout=100)如果程序的执行能在100毫秒之内完成,则测试通过
  2.@Ignore: 被忽略的测试方法:加上之后,暂时不运行此段代码
  3.@Before: 每一个测试方法之前运行
  4.@After: 每一个测试方法之后运行
  5.@BeforeClass: 方法必须必须要是静态方法(static 声明),所有测试开始之前运行,注意区分before,是所有测试方法
6.@AfterClass: 方法必须要是静态方法(static 声明),所有测试结束之后运行,注意区分 @After

那么上面的例子,我们可以看到,每个 @Test 方法中都有 Calculator c = new Calculator();即类的实例化,那么我们可以将其放入到 @Before 中

public class JunitTest {

	public JunitTest() {
		System.out.println("构造函数");
	}

	@BeforeClass
	public static void beforeClass() {
		System.out.println("@BeforeClass");
	}

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

	@Test
	public void test() {
		System.out.println("@Test");
	}

	@Ignore
	public void ignore() {
		System.out.println("@Ignore");
	}

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

	@AfterClass
	public static void afterClass() {
		System.out.println("@AfterClass");
	}
}

注意:编写测试类的原则: 
   ①测试方法上必须使用@Test进行修饰
②测试方法必须使用public void 进行修饰,不能带任何的参数
③新建一个源代码目录来存放我们的测试代码,即将测试代码和项目业务代码分开
④测试类所在的包名应该和被测试类所在的包名保持一致
⑤测试单元中的每个方法必须可以独立测试,测试方法间不能有任何的依赖
⑥测试类使用Test作为类名的后缀(不是必须)
⑦测试方法使用test作为方法名的前缀(不是必须)

 测试类测试

public class TestCalture {
	//第一步:应该维护Calture的这个对象
	private Calture calture=null;
	//第二步:初始化对象以及数据
	
	@Before             //类的对象初始化的时候 进行调用 (这个使用的比较多)
	//@BeforeClass      //这个注解是在 类的对象初始化之前 进行调用了  也就是说 对象还没有存在就被调用了
	public void init(){
		calture=new Calture();
	}
	
	@Test
	public void testAdd() throws Exception {
		//下面就进行操作了
		int acResult=calture.add(1,1);
		//接下来进行断言
		assertNotNull(acResult);
		assertEquals(2,acResult);
		//假设要进行10次断言呢?
		//assert这个家伙就要写10次
	}
	
	@Test(expected=ArithmeticException.class,timeout=3000)  //当我们知道要产生异常的时候可以使用expected 来告诉虚拟机希望产生怎样一个异常
	public void testCf(){
		int acResult=calture.chu(1,0);
	}
	
	@After
	public void destory(){
		calture=null;
	}
}

1.4 单元测试小结

单元测试的用处到底是什么?
 对当前的这个模块的功能进行测试
 回归测试的时候检查当前开发的功能和前面已经完成的功能是否产生了影响

1.5 Unit4新断言-Hamcrest的常用方法

1.5.1.字符相关匹配符

1、equalTo:
assertThat(testedValue, equalTo(expectedValue));
断言被测的testedValue等于expectedValue,equalTo可以断言数值之间,字符串之间和对象之间是否相等,相当于Object的equals方法
2、equalToIgnoringCase:
assertThat(testedString, equalToIgnoringCase(expectedString));
断言被测的字符串testedString在忽略大小写的情况下等于expectedString
3、equalToIgnoringWhiteSpace:
assertThat(testedString, equalToIgnoringWhiteSpace(expectedString);
断言被测的字符串testedString在忽略头尾的任意个空格的情况下等于expectedString
(注意:字符串中的空格不能被忽略)
4、containsString:
assertThat(testedString, containsString(subString) );
断言被测的字符串testedString包含子字符串subString
5、endsWith:
assertThat(testedString, endsWith(suffix));
断言被测的字符串testedString以子字符串suffix结尾
6、startsWith:
assertThat(testedString, startsWith(prefix));
断言被测的字符串testedString以子字符串prefix开始

1.5.2.一般匹配符

1、nullValue():
assertThat(object,nullValue());
断言被测object的值为null

2、notNullValue():
assertThat(object,notNullValue());
断言被测object的值不为null

3、is:
assertThat(testedString, is(equalTo(expectedValue)));
断言被测的object等于后面给出匹配表达式
1)is匹配符简写应用之一:
assertThat(testedValue, is(expectedValue));
is(equalTo(x))的简写,断言testedValue等于expectedValue
2)is匹配符简写应用之二:
assertThat(testedObject, is(Cheddar.class));
is(instanceOf(SomeClass.class))的简写,断言testedObject为Cheddar的实例

4、not:
assertThat(testedString, not(expectedString));
与is匹配符正好相反,断言被测的object不等于后面给出的object

5、allOf:
assertThat(testedNumber, allOf( greaterThan(8), lessThan(16) ) );
断言符合所有条件,相当于“与”(&&)

6、anyOf:
assertThat(testedNumber, anyOf( greaterThan(16), lessThan(8) ) );
断言符合条件之一,相当于“或”(||)

1.5.3.数值相关匹配符

1、closeTo:
assertThat(testedDouble, closeTo( 20.0, 0.5 ));
断言被测的浮点型数testedDouble在20.0-0.5 ~ 20.0+0.5范围之内

2、greaterThan:
assertThat(testedNumber, greaterThan(16.0));
断言被测的数值testedNumber大于16.0

3、lessThan:
assertThat(testedNumber, lessThan (16.0));
断言被测的数值testedNumber小于16.0

4、greaterThanOrEqualTo:
assertThat(testedNumber, greaterThanOrEqualTo (16.0));
断言被测的数值testedNumber大于等于16.0

5、lessThanOrEqualTo:
assertThat(testedNumber, lessThanOrEqualTo (16.0));
断言被测的testedNumber小于等于16.0

1.5.4.集合相关匹配符

1、hasEntry:
assertThat(mapObject, hasEntry(“key”, “value” ) );
断言被测的Map对象mapObject含有一个键值为"key"对应元素值为"value"的Entry项

2、hasItem:
assertThat(iterableObject, hasItem (element));
表明被测的迭代对象iterableObject含有元素element项则测试通过

3、hasKey:
assertThat(mapObject, hasKey (“key”));
断言被测的Map对象mapObject含有键值“key”

4、hasValue:
assertThat(mapObject, hasValue(value));
断言被测的Map对象mapObject含有元素值value

import org.hamcrest.core.AllOf;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.IsNull;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.bruce.demo.Calculator;

/**
 * 计算器的这个测试类
 */
public class TestHancrestCalture {
	
	//第一步:应该维护Calture的这个对象
	private Calculator calture=null;
	//第二步:初始化对象以及数据
	
	@Before             //类的对象初始化的时候 进行调用 (这个使用的比较多)
	//@BeforeClass        //这个注解是在 类的对象初始化之前 进行调用了  也就是说 对象还没有存在就被调用了
	public void init(){
		calture=new Calculator();
	}
	
   
	@Test
	public void testAdd() throws Exception {
		//下面就进行操作了
		int acResult=calture.add(1,1);
		//这个就稍微复杂点 如果记不住 也没关系  你可以写多条
		assertThat(acResult,AllOf.allOf(IsNull.notNullValue(),IsEqual.equalTo(2)));
	}
	
	
	@After
	public void destory(){
		calture=null;
	}
}

1.6 Unit4新断言-Hamcrest的常用方法

现在有50个Service 50个DAO
现在我开发好了一个功能:按照刚才的说法要把所有的测试用例都运行一次 至少运行60次显然不靠谱
suite在这种情况下 就因运而生了
suite是用来干嘛的呢?
可以使用Suite这门技术一次性运行多个测试用例

 TestA

public class TestA {
     
	private Calculator calture=null;
	
	@Before
	public void init(){
		calture=new Calculator();
	}
	
	@Test
	public void add(){
		int acResult=calture.add(1,1);
		Assert.assertEquals(2, acResult);
	}
}

 TestB

public class TestB {
private Calculator calture=null;
	
	@Before
	public void init(){
		calture=new Calculator();
	}
	
	@Test(expected=ArithmeticException.class)
	public void testCf(){
		calture.chu(1,0);
	}
}

 TestAB

@RunWith(Suite.class)    //这个是告诉这个测试用例使用那个类来进行运行
@SuiteClasses({TestA.class,TestB.class})
public class TestAB {

}

2.2. Stub(桩)的使用

场景:现在要写Service层的测试用例,但是DAO并没有开发好,但是DAO的整个约束是有的 整个DAO的接口是有的,只是没有实现而已,但是你就是要写测试.
思想:就是自己编写一个接口的实现类,然后这个接口的实现类中访问数据库的时候,使用了Map来模拟整个数据的存储、在每一个调用的方法上直接,使用Map来进行操作的这种测试模式 就叫做stub.

 新建一个Dao层接口

public interface IUserDAO {
    /**
     * 通过id找用户
     * @Title: findUserById   
     * @Description: TODO
     * @param: @param uId
     * @param: @return      
     * @return: User      
     * @throws
     */
	public User findUserById(Serializable uId);
	
}

 新建一个Sevice接口

public class UserService {
   
	private IUserDAO userDAO=null;
	
	public void setUserDAO(IUserDAO userDAO) {
		this.userDAO = userDAO;
	}
	
	/**
	 * 现在要测试这个方法
	 * @Title: getUserById   
	 * @Description: TODO
	 * @param: @param uId
	 * @param: @return      
	 * @return: User      
	 * @throws
	 */
	public User getUserById(Serializable uId){
		return userDAO.findUserById(uId);
	}
	
}

 User实现类

public class User implements Serializable {

	private int uId;
	private String uName;
	private String uPwd;

	public int getuId() {
		return uId;
	}

	public void setuId(int uId) {
		this.uId = uId;
	}

	public String getuName() {
		return uName;
	}

	public void setuName(String uName) {
		this.uName = uName;
	}

	public String getuPwd() {
		return uPwd;
	}

	public void setuPwd(String uPwd) {
		this.uPwd = uPwd;
	}

	public User(int uId, String uName, String uPwd) {
		super();
		this.uId = uId;
		this.uName = uName;
		this.uPwd = uPwd;
	}

	public User() {
		super();
	}

	@Override
	public String toString() {
		return "User [uId=" + uId + ", uName=" + uName + ", uPwd=" + uPwd + "]";
	}
}

public class UserDAOStub implements IUserDAO {

	// 使用Map集合来模拟数据库
	private Map<Integer, User> maps = new HashMap<Integer, User>();
	
	{
		maps.put(1, new User(1, "zhangsan", "123"));
		maps.put(2, new User(2, "lisi", "110"));
		maps.put(3, new User(3, "wangwu", "119"));
	}

	public User findUserById(Serializable uId) {
		return maps.get(uId);
	}
}

 测试类

public class TestUserService {

	private UserService userService=null;
	private User exUser=null;
	private UserDAOStub userDAOStub=null;
	
	@Before
	public void init(){
		userService=new UserService();
		exUser=new User(1,"zhangsan","123");
		userDAOStub=new UserDAOStub();
		//在userSerVice中进行设置
		userService.setUserDAO(userDAOStub);
	}
	
	/**
	 * 用过id找用户
	 * @Title: testGetUserById   
	 * @Description: TODO
	 * @param: @throws Exception      
	 * @return: void      
	 * @throws
	 */
	@Test
	public void testGetUserById() throws Exception {
	   User acUser=userService.getUserById(1);	
	   //下面紧接着进行断言
	   Assert.assertEquals(exUser.getuId(),acUser.getuId());
	   Assert.assertEquals(exUser.getuName(),acUser.getuName());
	   Assert.assertEquals(exUser.getuPwd(),acUser.getuPwd());
	}
}

3.dbunit的使用

专门用来进行DAO层测试的、在分层测试中每一层都要使用不同的框架和技术来进行测试、是因为每一层在测试的时候的着重点不一样

 搭建Dao层环境

<dependencies>

		<!--导入我们的dbunit的包 -->
		<dependency>
			<groupId>org.dbunit</groupId>
			<artifactId>dbunit</artifactId>
			<version>2.5.3</version>
		</dependency>

		<!-- dbutils -->
		<dependency>
			<groupId>commons-dbutils</groupId>
			<artifactId>commons-dbutils</artifactId>
			<version>1.6</version>
		</dependency>

		<dependency>
			<groupId>com.mchange</groupId>
			<artifactId>c3p0</artifactId>
			<version>0.9.5.2</version>
		</dependency>

	</dependencies>

 实体类

public class User implements Serializable {

	private int uId;
	private String uName;
	private String uPwd;

	public int getuId() {
		return uId;
	}

	public void setuId(int uId) {
		this.uId = uId;
	}

	public String getuName() {
		return uName;
	}

	public void setuName(String uName) {
		this.uName = uName;
	}

	public String getuPwd() {
		return uPwd;
	}

	public void setuPwd(String uPwd) {
		this.uPwd = uPwd;
	}

	以上是关于《我要进大厂系列 五》-谈谈你常用的测试框架有哪些?的主要内容,如果未能解决你的问题,请参考以下文章

《我要进大厂系列 六》-谈谈你对ThreadLocal理解?

《我要进大厂系列 八》-谈谈你对Synchronized理解?

《我要进大厂系列 一》-说说你对volatile关键字理解

《我要进大厂系列 七》-谈谈你对强软弱虚四种引用理解?

《我要进大厂系列 九》-谈谈Spring循环依赖

《我要进大厂系列 二》-说说你对CAS理解