3.java程序员必知必会类库之junit

这篇具有很好参考价值的文章主要介绍了3.java程序员必知必会类库之junit。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

单元测试技术的使用,是区分一个一般的开发者和好的开发者的重要指标。程序员经常有各种借口不写单元测试,但最常见的借口就是缺乏经验和知识。常见的单测框架有 JUnit , Mockito 和PowerMock 。本文就Junit展开介绍。

1.介绍

JUnit 是一个 Java 编程语言的单元测试框架。JUnit 促进了“先测试后编码”的理念,强调建立测试数据的一段代码,可以先测试,然后再应用。增加了程序员的产量和程序的稳定性,可以减少程序员的压力和花费在排错上的时间。
因为junit不同版本使用上面有一些差异,下面用junit4做展示

2.使用

2.1 简单demo

下面通过一个简单的demo演示,方法是计算一个人的年龄,我们需要对这个方法进行单元测试,这里只是说明如何使用junit,不需要关注具体的年龄计算代码细节。

public class UserAgeTest {

    public long getAge(Date birthday) {

        Date startDate = birthday;
        Date endDate = new Date();

        LocalDate localStart = null;
        LocalDate localEnd = null;
        ZoneId zoneId = ZoneId.systemDefault();
        if (startDate == null && endDate == null) {
            return 0;
        } else {
            if (startDate != null) {
                localStart = startDate.toInstant().atZone(zoneId).toLocalDate();
            }
            if (endDate != null) {
                localEnd = endDate.toInstant().atZone(zoneId).toLocalDate();
            }
            Period period = Period.between(localStart, localEnd);
            long year = (long) period.getYears();
            return year;
        }
    }
}

针对上面方法,写的测试类如下:

public  class TestJunit {
    @Test
    public void testadd() throws Exception{
        UserAgeTest userAgeTest = new UserAgeTest();
        Date birthday = new SimpleDateFormat("yyyy-MM-dd").parse("2019-01-01");
        long age = userAgeTest.getAge(birthday);
        System.out.println(age);
    }
}

最终输出方法运行结果,这里可以看到用junit的第一个好处是,我们代码中不用写一大堆main方法,而且代码复用性高,因为一个类只能有一个main方法,如果要测试其他方法,需要重新写main方法,测试方法复用性不高

在我们使用单元测试时候,单元测试类需要满足如下条件,否则运行会报错(这里是基于junit4,junit5限制条件会不一样)

  1. 测试的类不能是抽象类,且必须只含有一个构造器
  2. 测试方法不能是抽象方法,而且不能有返回值
  3. 测试类和方法必须被public修饰

可以看到上面的测试代码满足这三个要求

2.2 其他注解

上面介绍了Test注解的使用,下面介绍其他注解:

注解名称 注解含义
Test 表示方法是测试方法。
Before 在每个测试用例之前执行
After 在每个测试用例之后执行
BeforeClass 初始化类级别的资源,与before功能类似,只会执行一次
AfterClass 初始化类级别的资源,与After功能类似,只会执行一次
Ignore 不会在测试期间运行,即某些没有实现的方法或者功能没有完善的方法
2.2.1 Test注解扩展
2.2.1.1 timeout

可以在注解上面添加超时时间,超出超时时间方法报错,超时值是以毫秒为单位指定的

    //方法执行超过200毫秒报错
    @Test(timeout = 200)
    public void testTimeout(){
         while (true){

         }
    }
//java.lang.Exception: test timed out after 200 milliseconds
2.2.1.2 expected

检查方法是否抛出特定的异常,可以在注解上添加期待异常,如果方法没有抛出指定异常,则会报错

@Test(expected = NullPointerException.class)
public void testExpect(){

}
//java.lang.AssertionError: Expected exception: java.lang.NullPointerException

抛出异常后,方法正常运行

@Test(expected = NullPointerException.class)
public void testExpect(){
    throw new NullPointerException();
}
2.2.2 Before和After

该注解添加的方法,会在所在测试类每个方法执行前后都运行

    @Before
    public void beforeRun(){
        System.out.println("每个test方法执行前运行");
    }

    @After
    public void afterRun(){
        System.out.println("每个方法执行后运行");
    }
    @Test
    public void testRun(){
        System.out.println("测试运行方法1");
    }
    @Test
    public void testRun2(){
        System.out.println("测试运行方法2");
    }

程序输出:

每个test方法执行前运行
测试运行方法2
每个方法执行后运行
2.2.3 BeforeClass 和 AfterClass

注意:

  1. beforeclass 和 AfterClass 修饰的方法必须是静态的,否则会报错。
  2. beforeclass ,afterclass, before,after 的执行顺序 可以留意一下
  3. beforeclass 是执行一次,before是每个方法前都会执行,类似数据库连接初始化之类的操作,不必每个方法之前前执行,可以使用beforeclass 提高执行效率
  4. afterclass 和beforeclass对应,一些释放资源的操作可以放到afterclass里面
    @Before
    public void beforeRun(){
        System.out.println("每个test方法执行前运行");
    }

    @BeforeClass
    public static void  beforeClass(){
        System.out.println("执行beforeclass方法");
    }

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

    @After
    public void afterRun(){
        System.out.println("每个方法执行后运行");
    }
    @Test
    public void testRun(){
        System.out.println("测试运行方法1");
    }
    @Test
    public void testRun2(){
        System.out.println("测试运行方法2");
    }

程序运行执行结果:
执行beforeclass方法
每个test方法执行前运行
测试运行方法1
每个方法执行后运行
每个test方法执行前运行
测试运行方法2
每个方法执行后运行
执行afterClass方法

2.3.4 Ignore

igore注解标注的方法,不会执行,主要用于某些待完善功能方法。还不能测试,为了不影响整体测试用例运行,可以增加这个注解

@Test
@Ignore
public void testRun2(){
   System.out.println("测试运行方法2");
}
2.3.5 Runwith

junit 官方文档对runwith 解释如下:

When a class is annotated with @RunWith or extends a class annotated with @RunWith, JUnit will invoke the class it references to run the tests in that class instead of the runner built into JUnit.

翻译过来,就是如果一个类或者他的父类用runwith注解了,那么Junit会调用runwith属性指定的类来运行测试用例,而不是用内置的运行器。

JUnit中有一个默认的Runner,它的名字叫BlockJunit4ClassRunner,但这是在JUnit4.4之后才引入的,对于4.4之前版本的JUnit,它的名字叫Junit4ClassRunner

下面就几个常见的运行器做个简单介绍

2.3.5.1 Parameterized

参数化测试

/**
 * <p>
 * The custom runner <code>Parameterized</code> implements parameterized tests.
 * When running a parameterized test class, instances are created for the
 * cross-product of the test methods and the test data elements.
 * </p>
 * 翻译:当使用这个执行器的时候,可以创建基于测试数据和测试方法的组合测试用例,比如有五组数据,两个测试方法,那么会创建10个测试用例用于测试
 * 
 * For example, to test a Fibonacci function, write:
 * 
 * <pre>
 * &#064;RunWith(Parameterized.class)
 * public class FibonacciTest {
 * 	&#064;Parameters
 * 	public static List&lt;Object[]&gt; data() {
 * 		return Arrays.asList(new Object[][] {
 * 				{ { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 },
 * 						{ 6, 8 } } });
 * 	}
 * 
 * 	private int fInput;
 * 
 * 	private int fExpected;
 * 
 * 	public FibonacciTest(int input, int expected) {
 * 		fInput= input;
 * 		fExpected= expected;
 * 	}
 * 
 * 	&#064;Test
 * 	public void test() {
 * 		assertEquals(fExpected, Fibonacci.compute(fInput));
 * 	}
 * }
 * </pre>
 * 
 * <p>
 * Each instance of <code>FibonacciTest</code> will be constructed using the
 * two-argument constructor and the data values in the
 * <code>&#064;Parameters</code> method.
 * </p>
 */

上面是Parameterized的源码注释,可以看到里面用了斐波那契数列作为例子,讲了参数化测试的使用方式和场景。这里再使用一个案例加深印象,demo是从其他博客拷贝过来的,文末有参考文献

public class PriceCalculator {
  private int quantity;
  private double unitPrice;
  private double discount;
  
  public PriceCalculator(int quantity, double price, double discount){
    this.quantity = quantity;
    this.unitPrice = price;
    this.discount = discount;
  }
  
  public double getPrice(){
    return this.quantity * this.unitPrice *  ( this.discount / 100 );
  }
}

@RunWith(Parameterized.class)
public class TestRunwith {


    private int quantity;
    private double price;
    private double discount;
    private double expected;
    private PriceCalculator priceCalculator;

    public TestRunwith(int qty, double price, double discount, double expected){
        this.quantity = qty;
        this.price = price;
        this.discount = discount;
        this.expected = expected;
    }

    //配置测试数据,启动会逐条遍历,将每条数据通过构造器设置到类的成员变量里面
    @Parameterized.Parameters()
    public static Collection<Object[]> generateData()
    {
        return Arrays.asList(new Object[][] {
                { 5, 10, 90, 45 },
                { 4, 5, 80, 16 } });
    }

    //在方法调用前,将测试数据灌入待测试方法里面
    @Before
    public void setUp() throws Exception {
        this.priceCalculator = new PriceCalculator(this.quantity, this.price, this.discount);
    }

    @Test
    public void testPrice(){
        assertEquals("price calculated for test data", this.expected,
                this.priceCalculator.getPrice(), 0);
    }

在IDEA的concole可以看到两条测试案例运行成功。
3.java程序员必知必会类库之junit
下面是paramters 类源码介绍

public class Parameterized extends Suite {
	/**
	 * Annotation for a method which provides parameters to be injected into the
	 * test class constructor by <code>Parameterized</code>
	 */
	@Retention(RetentionPolicy.RUNTIME)
	@Target(ElementType.METHOD)
	public static @interface Parameters {
	}
	
	//可以看到会维护一个内部类,继承BlockJUnit4ClassRunner,用于解析创建执行器
	private class TestClassRunnerForParameters extends
			BlockJUnit4ClassRunner {
			//第几个测试用例参数
		private final int fParameterSetNumber;
		//参数列表
		private final List<Object[]> fParameterList;

		TestClassRunnerForParameters(Class<?> type,
				List<Object[]> parameterList, int i) throws InitializationError {
			super(type);
			fParameterList= parameterList;
			fParameterSetNumber= i;
		}
		//创建测试用例,可以看到会用测试类的唯一构造方法
		@Override
		public Object createTest() throws Exception {
			return getTestClass().getOnlyConstructor().newInstance(
					computeParams());
		}
		//参数从参数列表获取
		private Object[] computeParams() throws Exception {
			try {
				return fParameterList.get(fParameterSetNumber);
			} catch (ClassCastException e) {
				throw new Exception(String.format(
						"%s.%s() must return a Collection of arrays.",
						getTestClass().getName(), getParametersMethod(
								getTestClass()).getName()));
			}
		}
		
		//第几个测试
		@Override
		protected String getName() {
			return String.format("[%s]", fParameterSetNumber);
		}
		
		//测试用例名称,方法名加第几次
		@Override
		protected String testName(final FrameworkMethod method) {
			return String.format("%s[%s]", method.getName(),
					fParameterSetNumber);
		}
		//校验测试类构造方法是否唯一,如果不唯一会报错,因为要用构造方法设置参数
		@Override
		protected void validateConstructor(List<Throwable> errors) {
			validateOnlyOneConstructor(errors);
		}

		@Override
		protected Statement classBlock(RunNotifier notifier) {
			return childrenInvoker(notifier);
		}
	}

	//执行器列表,其实就是根据参数列表,创建多个执行器,遍历执行,详见下文
	private final ArrayList<Runner> runners= new ArrayList<Runner>();

	/**
	 * Only called reflectively. Do not use programmatically.
	 */
	public Parameterized(Class<?> klass) throws Throwable {
		super(klass, Collections.<Runner>emptyList());
		List<Object[]> parametersList= getParametersList(getTestClass());
		for (int i= 0; i < parametersList.size(); i++)
			runners.add(new TestClassRunnerForParameters(getTestClass().getJavaClass(),
					parametersList, i));
	}
	
	//维护的执行器列表会交给父类遍历执行
	@Override
	protected List<Runner> getChildren() {
		return runners;
	}

	//这里可以看到,会根据测试类,找由Parameters注解标记的方法,然后反射调用获取方法返回结果
	@SuppressWarnings("unchecked")
	private List<Object[]> getParametersList(TestClass klass)
			throws Throwable {
		return (List<Object[]>) getParametersMethod(klass).invokeExplosively(
				null);
	}

	private FrameworkMethod getParametersMethod(TestClass testClass)
			throws Exception {
		List<FrameworkMethod> methods= testClass
				.getAnnotatedMethods(Parameters.class);
		//保证测试类parameters注解的方法,有静态的,公共的
		for (FrameworkMethod each : methods) {
			int modifiers= each.getMethod().getModifiers();
			if (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))
				return each;
		}
		throw new Exception("No public static parameters method on class "
				+ testClass.getName());
	}
}
2.3.5.2 Suite

测试套件,其作用是使用JUnit执行一个测试套件。Suite类是JUnit自带的,意为套件,顾名思义,就是一套东西。通过它,可以把多个相关的测试类看做一个测试套件一起测试,@RunWith指定了Suite类,说明这个TestSuite类是一个套件。通过@Suite.SuiteClasses指定了要执行的测试类(这些类中的所有用例都会执行)。需要注意的是,这个TestSuite类本身用例则不会执行了(如下面的testSuite()方法)。

//使用套件测试
@RunWith(Suite.class)
//括号中填写你需要执行的类
@Suite.SuiteClasses({TestJunit.class,TestRunwith.class})
public class TestSuite {
    @Test
    public void testSuite(){
        System.out.println("测试suite");
    }
}

3.java程序员必知必会类库之junit

2.3.5.3 Categories

顾名思义,执行一个“类别”。和Suite类似,只是Suite是执行指定类中的所有用例,而Categories执行的范围更小,是在Suite的基础上只执行指定的“类别”的用例。这就需要事先在各个测试用例上用@Category标注该用例属于那些“类别”,之后便可以通过类别来选择执行某些用例


public  class TestJunit {
    @Test
    @Category({A.class})
    public void testRun(){
        System.out.println("测试运行方法A");
    }
    @Test
    @Category({B.class})
    public void testRun2(){
        System.out.println("测试运行方法B");
    }

    @Test
    @Category({A.class,B.class})
    public void testRun3(){
        System.out.println("测试运行方法A和B");
    }
}

@RunWith(Categories.class)
@Categories.IncludeCategory(A.class)
@Categories.ExcludeCategory(B.class)
@Suite.SuiteClasses({TestJunit.class,TestRunwith.class})
public class TestCategories {

}

运行结果:
测试运行方法A

2.3.5.4 Theories

提供一组参数的排列组合值作为待测方法的输入参数。同时注意到在使用Theories这个Runner的时候,我们的待测方法可以拥有输入参数,而这在其它的Runner中的测试方法是不行的。


@RunWith(Theories.class)
public class TestTheories {
    
    @DataPoints
    public static String[] names = {"Tony", "Jim"};
    @DataPoints
    public static int[] ages = {10, 20};

//    @DataPoints
//    public static double[] wewes = {223.23, 2323.5656};

    @Theory
    public void testMethod(String name, int age){
        System.out.println(String.format("%s's age is %s", name, age));

    }
}

代码输出结果:

Tony’s age is 10
Tony’s age is 20
Jim’s age is 10
Jim’s age is 20

2.3.5.5 SpringJUnit4ClassRunner

SpringJUnit4ClassRunner,其实这个类继承与JUnit默认的运行器BlockJUnit4ClassRunner,继承的好处就是可以完全保留默认的功能,并且提供了一套支持Spring上下文的框架,正如官方文档所说:

SpringJUnit4ClassRunner is a custom extension of JUnit’s BlockJUnit4ClassRunner which provides functionality of the Spring TestContext Framework to standard JUnit tests by means of the TestContextManager and associated support classes and annotations.

2.3 断言Assert

为什么会用断言?
以上面2.1 的demo为例,根据输入日期,输出年龄。我们运行测试方法发现其通过了测试。虽然我们能通过右侧结果的展示观察到计算年龄的结果是否正确,但对于大量测试来说,我们关注的重点应该是整体测试集合是否过了测试。于是我们引入断言。
简单来说,给程序运行结果给一个预期值,如果运行结果不是预期值,则直接报错,不再往下执行测试案例。
断言在我们程序中的很多地方都会用到,不止junit测试。

 @Test
 public void testAssert(){

     int i=2;
     int j=2;
     Assert.assertEquals("结果和预期不符",4,i+j);
     Assert.assertEquals("结果和预期不符",3,i+j);

     Assert.assertSame("结果和预期不符",4,i+j);
     Assert.assertNotSame("结果和预期不符",4,i+j);

     Assert.assertNull("结果为null",null);
     Assert.assertNotNull("结果为null",new TestAssert());
     
 }

2.4 假设Assume

什么是假设

3 集成构建系统

pom添加如下插件,然后在maven test或者package的时候,默认会执行所有单元测试用例

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.22.2</version>
        </plugin>
    </plugins>
</build>

类似这样
3.java程序员必知必会类库之junit
在idea编辑器中:
3.java程序员必知必会类库之junit
有时候Maven项目下存在test在执行test时却提示[INFO] No tests to run.,可能受以下几种情况影响:

  1. Test类命名不规范:
    默认排除的测试类:Abstract*Test.java ,Abstract**TestCase.java
  2. 存放test的目录不规范,非src/test/java/目录
  3. 项目打包类型是 pom

4. 结合spring

4.1 添加依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>4.3.5.RELEASE</version>
</dependency>

4.2 测试类添加注解

//替换运行器
@RunWith(SpringJUnit4ClassRunner.class)
//告知运行器配置文件
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class SpringTest {
	//注入bean
    @Resource(name = "accountDao")
    private AccountDao accountDao;
    @Test
    public void test01() {
        System.out.println(accountDao);
    }
}

5. 结合springboot

5.1 添加依赖

 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-test</artifactId>
     <scope>test</scope>
 </dependency>

5.2 测试类添加注解,配置启动类

@RunWith(SpringRunner.class)
@SpringBootTest(classes = DeDataServiceApplication.class)
@PropertySource(value = {"classpath:application.yml"}, encoding = "UTF-8")
public class TestModel {

}

参考文献:
Parameterized注解使用文章来源地址https://www.toymoban.com/news/detail-413712.html

到了这里,关于3.java程序员必知必会类库之junit的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【APP自动化测试必知必会】Appium之微信小程序自动化测试

    H5 是指第 5 代 HTML ,也指用 H5 语言制作的一切数字产品。 所谓 HTML 是“超文本标记语言”的英文缩写。我们上网所看到网页,多数都是由 HTML 写成的。 “超文本”是指页面内可以包含图片、链接,甚至音乐、程序等非文字元素。而“标记”指的是 这些超文本必须由包含属性

    2024年02月09日
    浏览(102)
  • MySql必知必会

    Buffer Pool基本概念 Buffer Pool:缓冲池,简称BP。其作用是用来缓存表数据与索引数据,减少磁盘IO操作,提升效率。 Buffer Pool由 缓存数据页(Page) 和 对缓存数据页进行描述的 控制块 组成, 控制块中存储着对应缓存页的所属的 表空间、数据页的编号、以及对应缓存页在Buffer Poo

    2024年01月22日
    浏览(63)
  • 聊聊Flink必知必会(二)

    Flink是一个有状态的流处理框架,因此需要对状态做持久化,Flink定期保存状态数据到存储空间上,故障发生后从之前的备份中恢复,这个过程被称为Checkpoint机制。而Checkpoint为Flink提供了Exactly-Once的投递保障。 流处理是一个数据不断输入的过程,为了更好更方便的快照,需要

    2024年02月08日
    浏览(45)
  • ChatGPT入门必知必会

    更多文章欢迎关注公众号: stackoveriow 2023年是真正意义上的AI之年,因为ChatGPT 2007年,iPhone开启了智能手机时代, 2023年,我们迎来了人工智能时代,我们正处于历史的大转折点上,这也许是启蒙运动级别的思想和社会转折,工业革命级别的生产和生活转折 。继22年12月份从GP

    2023年04月18日
    浏览(103)
  • 聊聊Flink必知必会(六)

    Flink是一个分布式系统,需要有效地分配和管理计算资源才能执行流应用程序。它集成了所有常见的集群资源管理器,如Hadoop YARN和Kubernetes,但也可以设置为作为一个独立的集群运行,甚至作为一个库。 Flink运行时由两种类型的进程组成:一个JobManager和一个或多个taskmanager。

    2024年02月04日
    浏览(52)
  • 《SQL 必知必会》全解析

    不要哀求,学会争取。若是如此,终有所获。 原文:https://mp.weixin.qq.com/s/zbOqyAtsWsocarsFIGdGgw 你是否还在烦恼 SQL 该从何学起,或者学了 SQL 想找个地方练练手?好巧不巧,最近在工作之余登上牛客,发现了牛客不知道啥时候上线了SQL 必知必会的练习题。 《SQL 必知必会》作为麻

    2024年02月08日
    浏览(46)
  • 聊聊Flink必知必会(七)

    虽然数据流中的许多操作一次只查看一个单独的事件(例如事件解析器),但某些操作会记住多个事件的信息(例如窗口算子)。 这些操作称为有状态的(stateful)。 有状态操作的一些示例: 当应用程序搜索某些事件模式(event patterns)时,状态(state)将存储迄今为止遇到的事件序

    2024年02月04日
    浏览(47)
  • MySQL必知必会(初级篇)

    数据库 (DataBase,DB),是统一管理的、长期存储在计算机内的、有组织的相关数据的集合。特点是数据见联系密切、冗余度小、独立性高、易扩展,并且可以为各类用户共享。 MySQL :是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的

    2023年04月08日
    浏览(47)
  • 聊聊Flink必知必会(五)

    聊聊Flink的必知必会(三) 聊聊Flink必知必会(四) 从源码中,根据关键的代码,梳理一下Flink中的时间与窗口实现逻辑。 对数据流执行 keyBy() 操作后,再调用 window() 方法,就会返回 WindowedStream ,表示分区后又加窗的数据流。如果数据流没有经过分区,直接调用 window() 方法则会返

    2024年02月05日
    浏览(62)
  • 【数据库】索引必知必会

    数据库中索引(Index)是一种帮助快速查找数据的数据结构,可以把它理解为书的目录,通过索引能够快速找到数据所在位置。 使用索引可以加快数据查找的效率,这是创建索引的最主要原因。 场景的索引数据结构有:Hash表(通过hash算法快速定位数据,但不适合范围查询,

    2023年04月20日
    浏览(49)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包