
本文为JUnit单元测试相关知识,下边将对JUnit单元测试概念,JUnit优点,JUnit安装与使用,JUnit运行流程与常用注解,JUnit测试套件使用及参数化设置,JUnit断言等进行详尽介绍~
📌博主主页:´Code_Wang的主页
👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
JUnit单元测试

单元测试又称模块测试,属于白盒测试,是开发者编写的一小段代码,用于检验被测代码的一个很小的、很明确的功能是否正确。通常而言,一个单元测试是用于判断某个特定条件(或者场景)下某个特定函数的行为。
另外JUnit是在极限编程和重构(refactor)中被极力推荐使用的工具,因为在实现自动单元测试的情况下可以大大的提高开发的效率,但是实际上编写测试代码也是需要耗费很多的时间和精力的,那么使用这个东西好处到底在哪里呢?
极限编程:
要求在编写代码之前先写测试,这样可以强制你在写代码之前好好的思考代码(方法)的功能和逻辑,否则编写的代码很不稳定,那么你需要同时维护测试代码和实际代码,这个工作量就会大大增加。因此在极限编程中,基本过程是这样的:构思-> 编写测试代码-> 编写代码-> 测试,而且编写测试和编写代码都是增量式的,写一点测一点,在编写以后的代码中如果发现问题可以较快的追踪到问题的原因,减小回归错误的纠错难度。
重构:
其好处和极限编程中是类似的,因为重构也是要求改一点测一点,减少回归错误造成的时间消耗。
其他情况:
我们在开发的时候使用JUnit写一些适当的测试也是有必要的,因为一般我们也是需要编写测试的代码的,可能原来不是使用的JUnit,如果使用JUnit,而且针对接口(方法)编写测试代码会减少以后的维护工作,例如以后对方法内部的修改(这个就是相当于重构的工作了)。另外就是因为JUnit有断言功能,如果测试结果不通过会告诉我们哪个测试不通过,为什么,而如果是像以前的一般做法是写一些测试代码看其输出结果,然后再由自己来判断结果是否正确,使用JUnit的好处就是这个结果是否正确的判断是它来完成的,我们只需要看看它告诉我们结果是否正确就可以了,在一般情况下会大大提高效率。
断言概述:
IDEA中JUnit安装步骤如下:
(1)新建一个项目工程,点击 文件File - 新建New - 项目Project,我这里项目名使用 JunitProj,点击完成

(2)完成项目的创建后,点击 文件File-设置Settings-Plugins 在搜索栏搜索 JUnit,此时出现了几个Plugins,选择 JUnit。Install JetBrains plugin…和Browser repositories两种方法,前者直接点击下载就好;解决IDEA 的 plugins 搜不到任何的插件问题:https://www.jb51.net/article/185940.htm

(3)安装完成之后,需要重启IDEA;

(4)当你下载好Junit4插件后,打开 文件File-设置Settings,如图注明修改配置

(5)在 JUnit4 模块里找到此代码 将test去掉

以前测试自己写的代码都是新建一个main方法,然后sysout输出控制台观察结果,繁琐又麻烦!现在让我们开始JUnit单元测试之旅!
(1)创建简单业务类
编写一个简单的计算类:Calculate类
package demo.util;
/**
* 实现加减乘除的简单计算类
*/
public class Calculate {
public int add(int a, int b) {
return a + b;
}
public int subtract(int a, int b) {
return a - b;
}
public int multiply(int a, int b) {
return a * b;
}
public int divide(int a, int b) {
return a / b;
}
}
(2)创建测试类

第一种直接点击被测试类Calculator 使用 Ctrl+Shift+T

第二种方法 鼠标右键点击类名 使用 goto-Test即可实现


(3)修改生成的测试类代码
上面步骤生成的测试类只包含测试方法的模板,并没有具体的测试细节,修改成如下:(其中有比较多的重复代码,暂时不管;并将减法的测试设为有问题)
package demo.util;
import org.junit.Assert;
import org.junit.Test;
class CalculateTest {
Calculate Calculate;
@Test
public void testAdd() {
calculate = new Calculate();
int result = calculate.add(2, 3);
Assert.assertEquals("加法有问题", 5, result);
/*
* "加法有问题":期望值和实际值不一致时,显示的信息
* 5 :期望值
* result :实际值
* Assert 断言 assertEquals相等断言
*/
}
@Test
public void testSubtract() {
calculate = new Calculate();
int result = calculate.subtract(12, 2);
Assert.assertEquals("减法有问题", 10000, result); //故意设置减法期望值为10000
}
@Test
public void testMultiply() {
calculate = new Calculate();
int result = calculate.multiply(2, 3);
Assert.assertEquals("乘法有问题", 6, result);
}
@Test
public void testDivide() {
calculate = new Calculate();
int result = calculate.divide(6, 3);
Assert.assertEquals("除法有问题", 2, result);
}
}
(4)运行结果
我们可以通过Run ->Edit Configuration或工具栏上的标签来调整我们测试运行配置:






总共有4个测试方法,运行了4个方法;其中failed有1个,即有一个方法的输出结果跟我们的预期不一样。
(1)JUnit使用的最佳实践
(2)测试失败的两种情况
注意: 测试用例是用来达到测试想要的预期结果,而不能测试出程序的逻辑错误。
比如:你需要写一个计算长方形面积的方法,而你错误地认为周长的公式就是计算面积的。所以在测试方法中,就算结果达到了你的预期,但这显然不是正确的计算面积方法。
package demo.util;
import org.junit.Assert;
import org.junit.Test;
public class ErrorAndFailureTest {
@Test
public void testAdd() {
int result = new Calculate().add(3, 3);
Assert.assertEquals("加法有问题", 5, result); // 预期值与程序输出不一样
}
@Test
public void testDivide() {
int result = new Calculate().divide(6, 0); // 除法中,除数为0
Assert.assertEquals("除法有问题", 3, result);
}
}
testAdd()方法是failure(失败/故障)错误

testDivide()方法是error错误

说明:
(1)JUnit的运行流程
右键被测试类,新建一个测试类。弹出框中,首先改变测试类所在的代码目录,然后勾选4个方法:

package demo.util;
import org.junit.*;
public class CalculateTest {
@BeforeClass
public static void setUpBeforeClass() throws Exception {
System.out.println("this is setUpBeforeClass()...");
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
System.out.println("this is tearDownAfterClass()...");
}
@Before
public void setUp() throws Exception {
System.out.println("this is setUp() @Before");
}
@After
public void tearDown() throws Exception {
System.out.println("this is tearDown() @After");
}
@Test
public void add() {
System.out.println("this is add()");
}
@Test
public void subtract() {
System.out.println("this is subtract()");
}
@Test
public void multiply() {
System.out.println("this is multiply()");
}
@Test
public void divide() {
System.out.println("this is divide()");
}
}
控制台输出如下:
this is setUpBeforeClass()...
this is setUp() @Before
this is subtract()
this is tearDown() @After
this is setUp() @Before
this is divide()
this is tearDown() @After
this is setUp() @Before
this is add()
this is tearDown() @After
this is setUp() @Before
this is multiply()
this is tearDown() @After
this is tearDownAfterClass()...
@BeforeClass修饰的方法会在所有方法被调用前被执行,而且该方法是静态的,所以当测试类被加载后接着就会运行它,而且在内存中它只会存在一份实例,它比较适合加载配置文件,进行初始化等等;
@AfterClass所修饰的方法会在所有方法被调用后被执行,通常用来对资源的清理,如关闭数据库的连接;
@Before和@After会在每个测试方法的前后各执行一次。
(2)JUnit常用注解
JUnit4和JUnit5对比:
| 特性 | Junit 4 | Junit 5 |
|---|---|---|
| 在当前类的所有测试方法之前执行。注解在静态方法上。此方法可以包含一些初始化代码。 | @BeforeClass | @BeforeAll |
| 在当前类中的所有测试方法之后执行。注解在静态方法上。此方法可以包含一些清理代码。 | @AfterClass | @AfterAll |
| 在每个测试方法之前执行。注解在非静态方法上。可以重新初始化测试方法所需要使用的类的某些属性。 | @Before | @BeforeEach |
| 在每个测试方法之后执行。注解在非静态方法上。可以回滚测试方法引起的数据库修改。 | @After | @AfterEach |
@Test、@Ignore的测试
package demo.util;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
public class AnotationTest {
@Test(expected = ArithmeticException.class)
public void testDivide() {
Assert.assertEquals("除法有问题", 3, new Calculate().divide(6, 0)); // 将除数设置为0
}
@Test(timeout = 2000)
public void testWhile() {
while (true) {
System.out.println("run forever..."); // 一个死循环
}
}
@Test(timeout = 3000)
public void testReadFile() {
try {
Thread.sleep(2000); // 模拟读文件操作
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Ignore("...")
@Test
public void testIgnore() {
System.out.println("会运行吗?");
}
}

说明:
@RunWith: 当类被@RunWith注解修饰,或者类继承了一个被该注解修饰的类,JUnit将会使用这个注解所指明的运行器(runner)来运行测试,而不是JUnit默认的运行器。
(1)JUnit测试套件
如果在测试类不增加的情况下,如何运行所有的单元测试代码类?一个个测试类的执行吗?显然繁琐且费劲。
将要运行的测试类集成在我们的测试套件中,比如一个系统功能对应一个测试套件,一个测试套件中包含多个测试类,每次测试系统功能时,只要执行一次测试套件就可以了。
新建3个测试任务类:
package demo.util;
import org.junit.Test;
public class TaskTest1 {
@Test
public void test() {
System.out.println("this is TaskTest1...");
}
}
package demo.util;
import org.junit.Test;
public class TaskTest2 {
@Test
public void test() {
System.out.println("this is TaskTest2...");
}
}
package demo.util;
import org.junit.Test;
public class TaskTest3 {
@Test
public void test() {
System.out.println("this is TaskTest3...");
}
}
新建一个套件类,包含以上三个任务类:
package demo.util;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({TaskTest1.class,TaskTest2.class,TaskTest3.class})
public class SuiteTest {
/*
* 1.测试套件就是组织测试类一起运行的
*
* 写一个作为测试套件的入口类,这个类里不包含其他的方法
* 更改测试运行器Suite.class
* 将要测试的类作为数组传入到Suite.SuiteClasses({})
*/
}
运行结果:

1、使用@RunWith注解,修改测试运行器。例如@RunWith(Suite.class),这个类就成为测试套件的入口类。
2、@Suite.SuiteClasses()中放入测试套件的测试类,以数组的形式{class1,class2,…}作为参数
(2)JUnit参数化设置
如果测试代码大同小异,代码结构都是相同的,不同的只是测试的数据和预期值,那么有没有更好的办法将相同的代码结构提取出来,提高代码的重用度呢?
解决:进行参数化测试。
步骤:
代码如下:
package demo.util;
import org.junit.Assert;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class) // 1.更改默认的测试运行器为RunWith(Parameterized.class)
public class ParameterTest {
// 2.声明变量存放预期值和测试数据
int expected = 0;
int input1 = 0;
int input2 = 0;
// 3.声明一个返回值 为Collection的公共静态方法,并使用@Parameters进行修饰
@Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][] { { 3, 1, 2 }, { 4, 2, 2 } });
}
// 4.为测试类声明一个带有参数的公共构造函数,并在其中为之声明变量赋值
public ParameterTest(int expected, int input1, int input2) {
this.expected = expected;
this.input1 = input1;
this.input2 = input2;
}
// 5.运行测试方法,即可完成对多组数据的测试
@Test
public void testAdd() {
Assert.assertEquals(expected, new Calculate().add(input1, input2));
}
}
运行结果:

断言是编程术语,表示为一些布尔表达式,程序员相信在程序中的某个特定点该表达式值为真,可以在任何时候启用和禁用断言验证,因此可以在测试时启用断言而在部署时禁用断言。同样,程序投入运行后,最终用户在遇到问题时可以重新启用断言。
使用断言可以创建更稳定、品质更好且 不易于出错的代码。当需要在一个值为FALSE时中断当前操作的话,可以使用断言。单元测试必须使用断言(Junit/JunitX)。
断言More:https://www.cnblogs.com/qiumingcheng/p/9506201.html
这个类提供了很多有用的断言方法来编写测试用例。只有失败的断言才会被记录。Assert 类中的一些有用的方法列式如下:
| Method | Description |
|---|---|
| assertNull(java.lang.Object object) | 检查对象是否为空 |
| assertNotNull(java.lang.Object object) | 检查对象是否不为空 |
| assertEquals(long expected, long actual) | 检查long类型的值是否相等 |
| assertEquals(double expected, double actual, double delta) | 检查指定精度的double值是否相等 |
| assertFalse(boolean condition) | 检查条件是否为假 |
| assertTrue(boolean condition) | 检查条件是否为真 |
| assertSame(java.lang.Object expected, java.lang.Object actual) | 检查两个对象引用是否引用同一对象(即对象是否相等) |
| assertNotSame(java.lang.Object unexpected, java.lang.Object actual) | 检查两个对象引用是否不引用同一对象(即对象不等) |
package demo.util;
import org.junit.Test;
import static org.junit.Assert.*;
public class TestAssertions {
@Test
public void testAssertions() {
//test data
String str1 = new String ("abc");
String str2 = new String ("abc");
String str3 = null;
String str4 = "abc";
String str5 = "abc";
int val1 = 5;
int val2 = 6;
String[] expectedArray = {"one", "two", "three"};
String[] resultArray = {"one", "two", "three"};
//Check that two objects are equal
assertEquals(str1, str2);
//Check that a condition is true
assertTrue (val1 < val2);
//Check that a condition is false
assertFalse(val1 > val2);
//Check that an object isn't null
assertNotNull(str1);
//Check that an object is null
assertNull(str3);
//Check if two object references point to the same object
assertSame(str4,str5);
//Check if two object references not point to the same object
assertNotSame(str1,str3);
//Check whether two arrays are equal to each other.
assertArrayEquals(expectedArray, resultArray);
}
}

👉Java全栈学习路线可参考:【Java全栈学习路线】最全的Java学习路线及知识清单,Java自学方向指引,内含最全Java全栈学习技术清单~
👉算法刷题路线可参考:算法刷题路线总结与相关资料分享,内含最详尽的算法刷题路线指南及相关资料分享~
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere
Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/
我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel
只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您
我有:When/^(?:|I)follow"([^"]*)"(?:within"([^"]*)")?$/do|link,selector|with_scope(selector)doclick_link(link)endend我打电话的地方:Background:GivenIamanexistingadminuserWhenIfollow"CLIENTS"我的HTML是这样的:CLIENTS我一直收到这个错误:.F-.F--U-----U(::)failedsteps(::)nolinkwithtitle,idortext'CLIENTS'found(Capybara::Element