使用Junit进行单元测试教程

软件测试的目的

什么是软件测试?

软件测试是    执行的软件以验证代码状态(state testing)或事件序列(behavior testing)符合预期    。

软件测试的作用

软件单元测试帮助开发人员验证程序的部分逻辑是正确的。    

测试术语

被测代码(或应用)

被测试的代码通常称被测代码。如果您正在测试的应用程序,则为被测试应用程序。

测试夹具(fixture)

测试夹具是代码的固定状态,通常为测试输入,也可以为预置条件。    

单元测试

单元测试用代码测试代码,关注状态和行为测试。单元测试所测试的百分比通常被称为测试覆盖率。
单元测试通常针对一小段代码,例如方法或类。通过测试实现或mock排除外部依赖。
单元测试通常不适合测试复杂用户界面或组件集成。

集成测试

集成测试关注测试组件的行为或一组组件之间的集成。功能测试有时等同于集成测试。通常需要把user story转化为test suite。

性能测试

性能测试使用基准重复测试软件组件。其目的是为了确保测试的代码即使是在高负载下也运行速度够快。

行为与状态测试

行为测试(也叫交互作用测试)不验证的方法调用的结果,但检查方法是否为用正确的输入参数调用。状态测试验证的结果。

如果您正在测试的算法和系统的功能,你想测试在大多数情况下的状态,而不是交互。典型的测试装置使用嘲笑或相关类的存根,以抽象与这些其他类路程,测试状态中的对象,被测试的相互作用。

测试组织

测试代码与实际代码分开。简单的getter和setter之类代码通常不需要测试。JVM相关内容通常可以认为是可靠的。对已有代码的测试,通常从出错最多的地方开始。Java的主流测试框架是JUnit和TestNG。前者用户最多,对mock框架兼容好。后者可读性更好。

Junit快速入门


JUnit的4.x版使用注解来指定测试。JUnit的主页:http://junit.org/,代码托管:https://github.com/junit-team/junit。JUnit测试是类中用于测试的方法。使用注解@org.junit.Test。方法中使用的断言方法(JUnit或其他断言框架提供),检查代码的执行的实际结果与预期。

下面的代码演示JUnit测试。

创建java工程first,并在src目录下创建test目录。

创建类MyClass:

package first;

public class MyClass {
    
    public int multiply(int x, int y) {
        
        // the following is just an example
        if (x > 999) {
          throw new IllegalArgumentException("X should be less than 1000");
        }
        return x / y;
  }
}

创建测试类MyClassTest,创建方法参见下面的安装配置部分。

111957_sctK_1433482.jpg

Selecting the methods to test

Eclipse prompt for adding JUnit to the project classpath

package first;

import static org.junit.Assert.assertEquals;
import org.junit.Test;

public class MyClassTest {
  
  @Test(expected = IllegalArgumentException.class)
  public void testExceptionIsThrown() {
    MyClass tester = new MyClass();
    tester.multiply(1000, 5);
  }
  
  @Test
  public void testMultiply() {
    MyClass tester = new MyClass();
    assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));
  }
}

选中测试类, 右键选择Run-As → JUnit Test.          

Result of running a unit test

                         


在广泛使用的Junit命名方法是类名称下测试和“测试”加Test后缀。should常用于测试方法名,如ordersShouldBeCreated,menuShouldGetActive,以增强可读性。Maven通过surfire插件自动生成Tests后缀的测试类。


多个测试类可以组合成test suite。运行test suite将在该suite按照指定的顺序执行所有测试类。下面演示包含两个测试类 (MyClassTest和MySecondClassTest) 的测试集,通过@Suite.SuiteClasses statement可以增加测试类。

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class)
@SuiteClasses({ MyClassTest.class, MySecondClassTest.class })
public class AllTests {

}

test suite中也可以包含test suite。

通过标准的Java代码可以在Eclipse之外运行JUnit测试。Apache Maven的或Gradle框架之类的构建框架通常与持续集成服务器(如Hudson或Jenkins)组合定期自动执行测试。
org.junit.runner.JUnitCore类的runClasses()方法允许运行测试类,返回为org.junit.runner.Result对象,包含测试结果信息。

下面类演示如何运行MyClassTest。这个类将执行测试类,输出潜在错误到控制台。

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class MyTestRunner {

  public static void main(String[] args) {
    Result result = JUnitCore.runClasses(MyClassTest.class);
    for (Failure failure : result.getFailures()) {
      System.out.println(failure.toString());
    }
  }
}

以上类可以像Java类一样从命令行运行,你只需要添加JUnit Jar到classpath即可。

Junit基础

注解

Junit4.*使用注解(annotation)标识测试方法和配置测试,以下是常用相关annotation的简要说明。特别重要的注解如下:

 

注解描述
@Test  
public void method()
标识方法为测试方法
@Test  (expected = Exception.class)标识抛出指定的异常
@Test(timeout=100)超时,单位ms
@Before 
public void method()
每个测试执行之前的准备工作。用来准备测试环境,如读取输入数据,初始化类等。
@After  
public void method()
每个测试执行之后的清理工作。如删除临时文件,恢复初始设置,释放内存等。
@BeforeClass 
public static void method()
每个测试集执行之前的准备工作。用来执行费时任务,如连接数据库。必须是Static方法。
@AfterClass 
public static void method()
每个测试集执行之后的清理工作。用来清理,如断开数据库连接,必须是Static方法。
@Ignore or @Ignore("Why disabled")忽略指定测试方法,用于测试类尚未准备好等情况,使用时最好标明忽略的原因。

 @RunWith会替代默认的org.junit.runner.Runner类,比如:

@RunWith(Suite.class)
public class MySuite {
}

Mock也需要使用注解。

测试用例组织

import org.junit.runner.RunWith;
import org.junit.runners.Suite;


@RunWith(Suite.class)
@Suite.SuiteClasses({ AssertTest.class, TestExecutionOrder.class, Assumption.class })
public class TestSuite {
}

断言


JUnit会假定所有测试方法按任意顺序执行,也就是说,一个测试不应该依赖其它测试。

Junit 4.11允许你用Annoation以字母顺序对测试方法进行排序,使用方法为用@FixMethodOrder(MethodSorters.NAME_ASCENDING)。默认使用固定但不可预期的顺序,对应参数为`MethodSorters.DEFAULT`,也可以使用`MethodSorters.JVM`,代表JVM的默认方式,每次运行顺序会不同。

自定义的实例:
package org.hamcrest.examples.tutorial;

import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

public class IsNotANumber extends TypeSafeMatcher<Double> {

 
@Override
 
public boolean matchesSafely(Double number) {
   
return number.isNaN();
 
}

 
public void describeTo(Description description) {
    description
.appendText("not a number");
 
}

 
@Factory
 
public static <T> Matcher<Double> notANumber() {
   
return new IsNotANumber();
 
}

}


使用:

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;

import static org.hamcrest.examples.tutorial.IsNotANumber.notANumber;

import junit.framework.TestCase;

public class NumberTest extends TestCase {

 
public void testSquareRootOfMinusOneIsNotANumber() {
    assertThat
(Math.sqrt(-1), is(notANumber()));
 
}
}

切记,要静态导入notANumber, 参考资料:https://code.google.com/p/hamcrest/wiki/Tutorial。

稍微复杂点的实例:

package com.example;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;

public class LessThanOrEqual<T extends Comparable<T>> extends BaseMatcher<Comparable<T>> {
    
    private final Comparable<T> expValue;
    
    public LessThanOrEqual(T expValue) {
        this.expValue= expValue;
    }
    
    @Override
    public void describeTo(Description desc) {
        desc.appendText(" less than or equal(<=)"
        +expValue);
    }
    
    @Override
    public boolean matches(Object t) {
        int compareTo = expValue.compareTo((T)t);
        return compareTo > -1;
    }
    
    @Factory
    public static<T extends Comparable<T>> Matcher<T> lessThanOrEqual(T t) {
        return new LessThanOrEqual(t);
    }
}

使用:

    @Test
    public void lessthanOrEquals_matcher() throws Exception
    {
        int actualGoalScored = 2;
        int expGoalScored= 4;
        assertThat(actualGoalScored, lessThanOrEqual(expGoalScored));
        expGoalScored =2;
        assertThat(actualGoalScored, lessThanOrEqual(expGoalScored ));
        
        double actualDouble = 3.14;
        double expDouble = 9.00;
        assertThat(actualDouble, lessThanOrEqual(expDouble));
        String authorName = "Sujoy";
        String expAuthName = "Zachary";
        assertThat(authorName, lessThanOrEqual(expAuthName));
    }

安装配置

在Gradle编译时使用Junit:

apply plugin: 'java'
dependencies {
  testCompile 'junit:junit:4.12'
}

Maven:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>


Eclipse 等IDE自带Junit。


Eclipse对Junit的支持

Eclipse有创建JUnit测试的向导。例如,要为现有类创建JUnit测试或测试类,在类单击鼠标右键,New → JUnit Test Case。File → New → Other... → Java→ JUnit也可打开类似窗口。


执行:在类单击鼠标右键Run-as →JUnit Test,执行类中所有测试。Alt+Shift+X, ,T,如果光标在测试里面,只会执行当前测试。


 

只看失败的测试:

测试失败时才弹出:

 

拷贝错误信息

Copy failed tests into clipboard

 JUnit的静态导入
静态导入允许在类中定义的public static字段和方法不指定类就可以使用。

// without static imports you have to write the following statement
Assert.assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));

// alternatively define assertEquals as static import
import static org.junit.Assert.assertEquals;

// more code
// use assertEquals directly because of the static import
assertEquals("10 x 5 must be 50", 50, tester.multiply(10, 5));

Eclipse中可以配置自动静态导入:Window → Preferences and select Java → Editor → Content Assist → Favorites.

  • org.junit.Assert

  • org.hamcrest.CoreMatchers

  • org.hamcrest.Matchers

Adding static imports to the preferences

这样就可以使用Content Assist(快捷方式Ctrl+Space)添加方法导入。

异常测试

注解:@Test (expected = Exception.class)可以测试单个异常。

测试多个异常的方法:

try {
   mustThrowException(); 
   fail();
} catch (Exception e) {
   // expected
   // could also check for message of exception, etc.
}

插件测试

JUnit的插件测试为插件书写单元测试。这些测试运行特殊的测试执行器,在单独的虚拟机中生成Eclipse实例。


高级用法

参数化(数据驱动)

使用注解@RunWith(Parameterized.class)即可。

测试类必须包含@Parameters注解的静态方法返回数组的集合,用于作为测试方法的参数。public域使用@parameter注解可以取得测试注入测试值。

package first;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

import java.util.Arrays;
import java.util.Collection;
import static org.junit.Assert.assertEquals;
import static org.junit.runners.Parameterized.*;

@RunWith(Parameterized.class)
public class ParameterizedTestFields {

    // fields used together with @Parameter must be public
    @Parameter
    public int m1;
    @Parameter (value = 1)
    public int m2;
    
    // creates the test data
    @Parameters
    public static Collection<Object[]> data() {
        Object[][] data = new Object[][] { { 1 , 2 }, { 5, 3 }, { 121, 4 } };
        return Arrays.asList(data);
    }
    
    @Test
    public void testMultiplyException() {
        MyClass tester = new MyClass();
        assertEquals("Result", m1 * m2, tester.multiply(m1, m2));
    }
    
    // class to be tested
    class MyClass {
        public int multiply(int i, int j) {
            return i *j;
        }
    }
}


使用构造方法也可以实现类似的效果:

package first;

import static org.junit.Assert.assertEquals;

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)
public class ParameterizedTestUsingConstructor {

    private int m1;
    private int m2;
    
    public ParameterizedTestUsingConstructor(int p1, int p2) {
        m1 = p1;
        m2 = p2;
    }
    
    // creates the test data
    @Parameters
    public static Collection<Object[]> data() {
        Object[][] data = new Object[][] { { 1 , 2 }, { 5, 3 }, { 121, 4 } };
        return Arrays.asList(data);
    }
    
    @Test
    public void testMultiplyException() {
        MyClass tester = new MyClass();
        assertEquals("Result", m1 * m2, tester.multiply(m1, m2));
    }
    
    // class to be tested
    class MyClass {
        public int multiply(int i, int j) {
            return i *j;
        }
    }
}


Rule

Rule可以灵活的增加或者重定义测试类每个测试方法的行为。通过@Rule注解可以创建和配置在测试方法使用的对象。比如灵活地指定异常:

package first;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

public class RuleExceptionTesterExample {

  @Rule
  public ExpectedException exception = ExpectedException.none();
  
  @Test
  public void throwsIllegalArgumentExceptionIfIconIsNull() {
    exception.expect(IllegalArgumentException.class);
    exception.expectMessage("Negative value not allowed");
    ClassToBeTested t = new ClassToBeTested();
    t.methodToBeTest(-1);
  }
}


注意上述代码只做演示,不能实际执行。

JUnit的已经提供了规则的几个有用的实现。例如, TemporaryFolder类在测试执行完毕后会删除文件。

package first;

import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.IOException;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

public class RuleTester {

  @Rule
  public TemporaryFolder folder = new TemporaryFolder();
  
  @Test
  public void testUsingTempFolder() throws IOException {
    File createdFolder = folder.newFolder("newfolder");
    File createdFile = folder.newFile("myfilefile.txt");
    assertTrue(createdFile.exists());
  }
}


实现TestRule接口可自定义Rule。这个接口的apply(Statement, Description)方法返回Statement实例。Statement即JUnit运行时中的测试,Statement#evaluate()运行它们。Description 描述了单个测试。它通过反射阅读测试信息。


下例子添加日志语句到Android应用。

import android.util.Log;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class MyCustomRule implements TestRule {

    private Statement base;
    private Description description;
    
    @Override
    public Statement apply(Statement base, Description description) {
        this.base = base;
        this.description = description;
        return new MyStatement(base);
    }
    
    public class MyStatement extends Statement {
    
        private final Statement base;
        public MyStatement(Statement base) {
            this.base = base;
        }
        
        @Override
        public void evaluate() throws Throwable {
            Log.w("MyCustomRule",description.getMethodName() + "Started");
            try {
                base.evaluate();
            } finally {
                Log.w("MyCustomRule",description.getMethodName() + "Finished");
            }
        }
    }
}


使用Rule:

@Rule
public MyCustomRule myRule = new MyCustomRule();


更多关于Rule的资料:https://github.com/junit-team/junit/wiki/Rules

分类

public interface FastTests { /* category marker */
}

public interface SlowTests { /* category marker */
}

public class A {
  @Test
  public void a() {
    fail();
  }
  
  @Category(SlowTests.class)
  @Test
  public void b() {
  }
}

@Category({ SlowTests.class, FastTests.class })
public class B {
  @Test
  public void c() {
  }
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses({ A.class, B.class })
// Note that Categories is a kind of Suite
public class SlowTestSuite {
  // Will run A.b and B.c, but not A.a
}

@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@ExcludeCategory(FastTests.class)
@SuiteClasses({ A.class, B.class })
// Note that Categories is a kind of Suite
public class SlowTestSuite {
  // Will run A.b, but not A.a or B.c
}

  本部分参考资料:https://github.com/junit-team/junit/blob/master/doc/ReleaseNotes4.8.md


参考资料

python开发自动化测试群291184506 PythonJava单元白盒测试群144081101

Unit Testing with JUnit - Tutorial


转载于:https://my.oschina.net/u/1433482/blog/601266

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值