手机用户请
横屏
获取最佳阅读体验,REFERENCES
中是本文参考的链接,如需要链接和更多资源,可以关注其他博客发布地址。
平台 | 地址 |
---|---|
CSDN | https://blog.csdn.net/sinat_28690417 |
简书 | https://www.jianshu.com/u/3032cc862300 |
个人博客 | https://yiyuery.club |
JUnit
从零开始认识单元测试软件测试通常分为两个主要过程——验证 & 认证。
在软件测试中,缺陷和错误之间有区别,我们应该清楚地区分,以避免误解问题。
.
单元测试
这是在开发人员级别使用的最基本的测试,测试人员专注于单元代码的单个部分,而它已经从任何外部交互或依赖于任何模块之前被隔离。这个测试要求开发人员检查他们编写的最小代码单元,并证明单元可以独立工作。
如果你听说过测试驱动开发
(TDD:Test-Driven Development),单元测试就不陌生。单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。比如对函数abs()
,我们可以编写出以下几个测试用例:
把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。单元测试通过后有什么意义呢?
如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有bug,要么测试条件 输入不正确,总之,需要修复使单元测试能够通过。
如果我们对abs()
函数代码做了修改,只需要再跑一遍单元测试,如果通过,说明我们的修改不会对abs()
函数原有
的行为造成影响,如果测试不通过,说明我们的修改与原有行为不一致,要么修改代码,要么修改测试。
这种以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的 时候,可以极大程度地保证该模块行为仍然是正确的。
From:
廖雪峰
单元测试可以由两种方式完成:
JUnit
是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
JUnit 促进了先测试后编码
的理念,强调建立测试数据的一段代码,可以先测试,然后再应用。这个方法就好比“测试一点,编码一点,测试一点,编码一点……”,增加了程序员的产量和程序的稳定性,可以减少程序员的压力和花费在排错上的时间。
特点:
JUnit是一款优秀的开源Java单元测试框架,也是目前使用率最高最流行的测试框架,开发工具Eclipse和IDEA对JUnit都有很好的支持,JUnit主要用于以下测试场景。
JUnit GitHub地址:https://github.com/junit-team
testImplementation 'org.springframework.boot:spring-boot-starter-test'
.
.
.
.
测试样例定义了运行多重测试的固定格式
/*测试入口*/
public class Ex3Test {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(JunitEx3Test.class,JunitEx3_2Test.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
public class JunitEx3_2Test extends TestCase {
@Test
public void testXX2() {
System.out.println("No of Test2 Case:" + this.countTestCases());
System.out.println("Original Test Name:" + this.getName());
this.setName("JunitEx3_2Test Name");
System.out.println("Update Test Name:" + this.getName());
}
}
public class JunitEx3Test extends TestCase {
/**
* 为被run(TestResult result) 执行的测试案例计数
* 注意前缀必须为test***
*/
@Test
public void testX1() {
System.out.println("No of Test Case:" + this.countTestCases());
System.out.println("Original Test Name:" + this.getName());
this.setName("testX1 Name");
System.out.println("Update Test Name:" + this.getName());
}
@Test
public void testX2() {
System.out.println("No of Test Case:" + this.countTestCases());
System.out.println("Original Test Name:" + this.getName());
this.setName("testX2 Name");
System.out.println("Update Test Name:" + this.getName());
}
}
TestResult 类收集所有执行测试案例的结果。它是收集参数层面的一个实例。这个实验框架区分失败和错误。失败是可以预料的并且可以通过假设来检查。错误是不可预料的问题就像 ArrayIndexOutOfBoundsException。
TestResult
类的一些重要方法列式如下:
/*测试入口*/
public class Ex4Test {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(JEx4_1Test.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
public class JEx4_1Test extends TestResult {
@Test
public void testX1() throws IllegalArgumentException {
throw new IllegalArgumentException("testX1 failed");
}
@Test
public void testX2() throws IllegalArgumentException {
throw new IllegalArgumentException("testX2 failed");
}
@Test
public void testX3() {
System.out.println("testX3 success...");
}
}
TestSuite 类是测试的组成部分。它运行了很多的测试案例
/*测试入口*/
public class Ex5Test {
public static void main(String[] args) {
TestSuite suite = new TestSuite(TestJunit1.class, TestJunit2.class);
TestResult result = new TestResult();
suite.run(result);
System.out.println("Number of test cases = " + result.runCount());
}
}
public class TestJunit1 extends TestCase {
String message = "Robert";
MessageUtil messageUtil = new MessageUtil(message);
@Test
public void testMessage() {
System.out.println("TestJunit1 testMessage()");
assertEquals(message, messageUtil.printMessage());
}
}
public class TestJunit2 extends TestCase {
String message = "Robert";
MessageUtil messageUtil = new MessageUtil(message);
@Test
public void testMessage() {
System.out.println("TestJunit2 testMessage()");
message = "Hi!" + "Robert";
assertEquals(message,messageUtil.printMessage());
}
}
IntellJ IDEA 支持快速生成测试用例
Ctrl + Shift + T
.
.
测试结果的表达式
断言测试也就是期望值测试,是单元测试的核心,也就是决定测试结果的表达式
Assert对象中的断言方法:
Assert.assertEquals 对比两个值相等
Assert.assertNotEquals 对比两个值不相等
Assert.assertSame 对比两个对象的引用相等
Assert.assertArrayEquals 对比两个数组相等
Assert.assertTrue 验证返回是否为真
Assert.assertFlase 验证返回是否为假
Assert.assertNull 验证null
Assert.assertNotNull 验证非null
public class Ex1Test {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(Ex1Test.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
/**
* 简单输出测试 Person(name=xxx)
*/
@Test
public void test1() {
Person person = Person.builder().name("xxx").build();
System.out.println(person.toString());
}
/**
* 利用 Assert 断言输出结果
* org.junit.Assert
*/
@Test
public void test2() {
Person person = Person.builder().name("xxx").build();
System.out.println(person.toString());
Assert.assertEquals(person.getName(), "xxx");
Assert.assertSame(person.getName(), "xxx");
Assert.assertNotSame(person.getName(), "xx2");
Assert.assertFalse(person.getName().endsWith("xx2"));
Assert.assertTrue(person.getName().endsWith("xx"));
Assert.assertNull(null);
Assert.assertNotNull(person);
}
/**
* 测试 @Before
* Person(name=before....1)
* Person(name=xxx3)
*/
@Test
public void test3() {
Person person = Person.builder().name("xxx3").build();
System.out.println(person.toString());
}
@Before
public void before1() {
Person person = Person.builder().name("before....1").build();
System.out.println(person.toString());
}
/**
* 测试 @After
* Person(name=before....1)
* Person(name=xxx4)
* Person(name=after...1)
*/
@Test
public void test4() {
Person person = Person.builder().name("xxx4").build();
System.out.println(person.toString());
}
@After
public void after1() {
Person person = Person.builder().name("after...1").build();
System.out.println(person.toString());
}
/**
* 测试@AfterClass
* Person(name=before....1)
* Person(name=xxx5)
* Person(name=after...1)
* Person(name=after...2)
*/
@Test
public void test5() {
Person person = Person.builder().name("xxx5").build();
System.out.println(person.toString());
}
/**
* java.lang.Exception: Method after2() should be static
* 在所有方法执行之后执行
*/
@AfterClass
public static void after2() {
Person person = Person.builder().name("after...2").build();
System.out.println(person.toString());
}
/**
* 测试 @BeforeClass
* Person(name=before...2)
* Person(name=before....1)
* Person(name=xxx6)
* Person(name=after...1)
* Person(name=after...2)
*/
@Test
public void test6() {
Person person = Person.builder().name("xxx6").build();
System.out.println(person.toString());
}
/**
* java.lang.Exception: Method before2() should be static
* 在所有方法执行之前执行
*/
@BeforeClass
public static void before2() {
Person person = Person.builder().name("before...2").build();
System.out.println(person.toString());
}
/**
* hamcrest-core-1.3.jar
* Hamcrest是一款用以编写matcher对象的框架,以类库的形式发布。一个matcher对象就是一个明确定义的匹配规则.
* > 匹配规则:Matchers
*/
@Test
@Ignore
public void test7() {
MatcherAssert.assertThat(Long.valueOf(1), instanceOf(Integer.class));
//字符串匹配符
String n = "Magci";
//containsString:字符串变量中包含指定字符串时,测试通过
MatcherAssert.assertThat(n, Matchers.containsString("ci"));
//startsWith:字符串变量以指定字符串开头时,测试通过
MatcherAssert.assertThat(n, Matchers.startsWith("Ma"));
//endsWith:字符串变量以指定字符串结尾时,测试通过
MatcherAssert.assertThat(n, Matchers.endsWith("i"));
//euqalTo:字符串变量等于指定字符串时,测试通过
MatcherAssert.assertThat(n, Matchers.equalTo("Magci"));
//equalToIgnoringCase:字符串变量在忽略大小写的情况下等于指定字符串时,测试通过
MatcherAssert.assertThat(n, Matchers.equalToIgnoringCase("magci"));
//equalToIgnoringWhiteSpace:字符串变量在忽略头尾任意空格的情况下等于指定字符串时,测试通过
MatcherAssert.assertThat(n, Matchers.equalToIgnoringWhiteSpace(" Magci "));
//...so on
}
@Test
public void testX() {
}
/**
* fail({{message}}) 直接中止方法运行
*/
@Test
@Ignore
public void test8() {
Assert.fail("directlly stop and output fail log!");
}
}
测试套件意味着捆绑几个单元测试用例并且一起执行他们。在 JUnit 中,@RunWith 和 @Suite 注释用来运行套件测试。这个教程将向您展示一个例子,其中含有两个测试样例 TestJunit1 & TestJunit2 类,我们将使用测试套件一起运行他们。
.
/*测试入口*/
public class Ex2Test{
public static void main(String[] args) {
Result result = JUnitCore.runClasses(TestSuite.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
public class MessageUtil {
String message;
public String printMessage() {
return message;
}
public MessageUtil(String message) {
this.message = message;
}
}
public class TestJunit1 {
String message = "Robert";
MessageUtil messageUtil = new MessageUtil(message);
@Test
public void testMessage() {
System.out.println("TestJunit1 testMessage()");
assertEquals(message, messageUtil.printMessage());
}
}
public class TestJunit2 {
String message = "Robert";
MessageUtil messageUtil = new MessageUtil(message);
@Test
public void testMessage() {
System.out.println("TestJunit2 testMessage()");
message = "Hi!" + "Robert";
assertEquals(message,messageUtil.printMessage());
}
}
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestJunit1.class ,TestJunit2.class
})
public class TestSuite {
}
Junit 提供了一个暂停的方便选项。如果一个测试用例比起指定的毫秒数花费了更多的时间,那么 Junit 将自动将它标记为失败。timeout 参数和 @Test 注释一起使用。现在让我们看看活动中的 @test(timeout)。
.
public class Ex6Test {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(JEx6_1Test.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
public class JEx6_1Test {
@Test
public void testX1(){
System.out.println("x1");
}
@Test(timeout = 10)
public void testX2(){
for(int i =0;i<100000000;i++){
Math.random();
}
System.out.println("x2");
}
@Test
public void testX3(){
System.out.println("x3");
}
}
Junit 用代码处理提供了一个追踪异常的选项。你可以测试代码是否它抛出了想要得到的异常。expected 参数和 @Test 注释一起使用。现在让我们看看活动中的 @Test(expected)。
.
/*测试入口*/
public class Ex7Test {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(JEx7_1Test.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
public class JEx7_1Test {
String message = "Robert";
MessageUtil messageUtil = new MessageUtil(message);
@Test
public void testX1(){
System.out.println("x1");
}
//@Test(expected = ArithmeticException.class)
@Test(expected = IllegalArgumentException.class)
public void testX2(){
messageUtil.printMessage();
}
@Test
public void testX3(){
System.out.println("x3");
}
}
class MessageUtil {
private String message;
//Constructor
//@param message to be printed
public MessageUtil(String message){
this.message = message;
}
// prints the message
public void printMessage(){
System.out.println(message);
int a =0;
int b = 1/a;
}
// add "Hi!" to the message
public String salutationMessage(){
message = "Hi!" + message;
System.out.println(message);
return message;
}
}
Junit 4 引入了一个新的功能参数化测试。参数化测试允许开发人员使用不同的值反复运行同一个测试。你将遵循 5 个步骤来创建参数化测试。
一旦每一行数据出现测试用例将被调用。
/*测试入口*/
public class Ex8Test {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(JEx8_1Test.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
@RunWith(Parameterized.class)
public class JEx8_1Test {
private Integer inputNumber;
private Boolean expectedResult;
private PrimeNumberChecker primeNumberChecker;
@Before
public void initialize() {
primeNumberChecker = new PrimeNumberChecker();
}
public JEx8_1Test(Integer inputNumber,
Boolean expectedResult) {
this.inputNumber = inputNumber;
this.expectedResult = expectedResult;
}
@Parameterized.Parameters
public static Collection primeNumbers() {
return Arrays.asList(new Object[][] {
{ 2, true },
{ 6, false },
{ 19, true },
{ 22, false },
{ 23, true }
});
}
@Test
public void testPrimeNumberChecker() {
System.out.println("Parameterized Number is : " + inputNumber);
assertEquals(expectedResult,
primeNumberChecker.validate(inputNumber));
}
}
/**
* 质数
*/
class PrimeNumberChecker {
public Boolean validate(final Integer primeNumber) {
for (int i = 2; i < (primeNumber / 2); i++) {
if (primeNumber % i == 0) {
return false;
}
}
return true;
}
}
.
.
public class Ex1Test extends BaseBootJunitTest {
@Resource
private IPersonService personService;
/**
* 初始数据插入
*/
@Test
public void testX1() {
personService.saveWithName("p_test_1111");
}
/**
* 方法内部事务间隔离
*/
@Test
public void testX2() {
//第一行数据插入成功
personService.saveWithName(CapsuleStringUtil.randomStr("p_test_"));
//第二行数据插入失败
personService.saveNoTransactional("p_test_1111");
}
/**
* 针对Test方法加入事务控制
* 避免测试数据污染数据库
*/
@Test
@Transactional(rollbackFor = Exception.class)
public void testX3() {
//第一行数据插入失败
personService.saveWithName(CapsuleStringUtil.randomStr("p_test_"));
//第二行数据插入失败
personService.saveNoTransactional("p_test_1111");
}
}
.
.
public class Ex2Test extends BaseBootJunitTest {
@Resource
private TestRestTemplate restTemplate;
@Test
public void testHello() {
String resp = restTemplate.getForObject("/hello", String.class);
System.out.println(resp);
Assert.assertEquals("Hello Junit!", resp);
}
}
.
.
Hamcrest匹配器的用法
.
官网地址 http://hamcrest.org/JavaHamcrest/tutorial
.
.
.
/*测试入口*/
public class Ex3Test {
public static void main(String[] args) {
Result result = JUnitCore.runClasses(JEx3_1Test.class);
for (Failure failure : result.getFailures()) {
System.out.println(failure.toString());
}
System.out.println(result.wasSuccessful());
}
}
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class JEx3_1Test extends BaseBootJunitTest {
@Resource
private IPersonService personService;
private String pName;
/**
* 每个测试方法都是个测试用例,独立执行 @beforeInit 注解标注的方法
*/
@Before
public void before() {
System.out.println("[Before]-----------------------------------");
pName = "p_test_auto_1";
}
@After
public void after() {
System.out.println("[After]-----------------------------------");
}
/**
* 正常过程:人员添加
*/
@Test
public void testX() {
Person person = personService.saveWithName(pName);
log("添加后人员信息返回结果", person);
Person dbPerson = personService.findByPersonName(pName,true);
Assert.assertNotNull(dbPerson);
}
/**
* 正常过程:人员修改
*/
@Test
public void testX2() {
Person oldPerson = personService.findByPersonName(pName,true);
oldPerson.setName(pName + "_modify");
Person newPerson = personService.saveWithResult(oldPerson);
log("修改后人员信息", newPerson);
Assert.assertNotNull(newPerson);
Assert.assertEquals(oldPerson.getName(), newPerson.getName());
}
/**
* 正常过程:人员删除
*/
@Test
public void testX3() {
Person dbPerson = personService.findByPersonName(pName+"_modify",true);
personService.delete(dbPerson.getId());
log("删除前人员信息", dbPerson);
Person personDel = personService.findByPersonName(dbPerson.getName(), false);
log("删除后搜索结果", personDel);
}
}
本文从软件测试为讨论的切入点,介绍了单元测试在软件测试中的重要性和对应角色。接下来,以JUnit测试框架展开,就环境搭建、测试类型、JUnit 核心API、JUnit各种测试方法分别进行了介绍,并提供了代码示例。最后,结合人员的增删改操作,编写了对应的自动化测试用例。
.