单元测试介绍

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

简介

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

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

单元测试在计算机编程中单元测试(Unit Testing)又称为模块测试,是针对代码模块(软件设计的最小单位)来进行正确性检验的测试工作。在过程化编程可能是整个模块、但更多是函数或过程等;对于面向对象编程通常是整个接口,比如类,也可能是方法。使用代码来测试代码,关注状态和行为测试。单元测试所测试的百分比通常被称为测试覆盖率。

单元测试基于测试用例进行组织。理想的情况下,测试用例互相独立,为此使用桩、mockfake、服务虚拟化和测试框架等,通常由开发或者白盒测试人员书写。对于数据库和缓存等操作,传统建议用mock的方式进行单元测试。但是依赖较多的情况下mock比较耗时耗力,为此尽量采用本地构建实际数据、缓存等中间件的方式,尽量少使用mock,对于网络依赖,则尽量采用服务虚拟化。

测试代码与实际代码分开。
 

 

单元测试主要针对各种输入组合进行测试,常用的方法有等价类划分和边界值等。同时特别关注控制流和数据流,通过关注ifforswitchwhile等分支条件,尽量提高覆盖率。另外单元测试要格外关注异常处理逻辑。

单元测试重视覆盖率,但是不为了追求覆盖率而不顾实际情况。通常一般的公司在覆盖率50%左右能达到比较好的性价比。追求90%以上的覆盖率通常比较有难度,即便是google之类的领导性的公司,代码覆盖率也只达到了80%多。

单元测试能提前发现一些集成、系统测试阶段的问题,但是不能替代后续测试。

单元测试在代码高内聚松耦合及公共库提取完整等情况比较容易进行测试

单元测试通常与小提交、每次提交触发测试等持续集成配合使用。

单元测试自动生成用例是较前沿的技术,相关商业工具有:AgitarOne、Jtest 、SilverMark Test Mentor
开源工具有:CodePlex AnalytiX、EvoSuite、Randoop、Palus、Daikon, Eclat, Javalanche, Java PathFinder, JCrasher, jWalk, Korat, RecGen和ReCrash等。当然有不少公司直接用python控制各种语言生成单元测试用例。

单元调用系统公共方法到产生结果之间的部分。结果可以是:返回值(非void)、系统的状态或行为改变、不受控制的第三方系统调用。单元测试的范围可以小到一个方法,大到多个类。测试单元不是越小越好,把工作单元缩到最小,最后会不得不伪造一堆东西,这些东西并不是使用公共API的真实最终结果,而是生成结果过程中的一些中间状态。近年来,单元测试有测试功能、性能、安全等趋向,比如安卓中就有本地单元测试和基于仿真的单元测试。

参考资料:https://en.wikipedia.org/wiki/Unit_testing

单元测试的组织:AAA(arrange, act, and assert) 可能还有After。

 

 

哪些项目最适合单元测试?
* 平台类库性质的项目
* 规模大的,需要长时间维护的
* 正确性要求极高的项目

哪些代码需要测试?
* UI层
* UI Business
* 业务层
* 数据层

一般只需要测公开方法

 

单元测试的前提
* 程序层次结构清楚
* 层间解耦
* 外部依赖小

 

最最糟糕的被测方法
* 业务代码混在UI层方法中
* 数据库操作混在业务代码中
* 外部依赖很多

 

UI层的测试
* 不需要具体的业务实现
* 可以看到在不同业务下的UI表现
* 关键字: 静态Mock

业务层的测试
* 不需要具体的数据层
* 不需要UI层来验证
* 关键字: 动态Mock

业务层的测试,为了去除依赖,mock是必须的。
但在使用每一个Mock前,请想想,是否可以通过重构,摆脱这个依赖。

先定好接口,然后对于具体实现,就可以使用TDD


自动化
关键字:人工干预(比如需要网络连接、数据库等),通过Mock,去除干预。
关键字:DailyBuild 。

好的测试:可重复、独立的
 

 

单元测试框架

开源的以xunit为主。商业工具有Typemock Isolator.NET/Isolator++, TBrun, JustMock, Parasoft Development Testing (Jtest, Parasoft C/C++test, dotTEST), Testwell CTA++ and VectorCAST/C++等。

Python: unittest、pytest(推荐)、nose
Java: Junit(推荐)、TestNG、Spock。可以理解Junit是TestNG的子集,但是Junit使用更广泛。Java的主流测试框架是JUnit和TestNG。前者用户最多,对mock框架兼容好。后者可读性更好。

单元测试的好处

不写单元测试的借口:
* 编写单元测试太花时间了
* 运行测试的时间太长了
* 测试代码并不是我的工作
* 代码太复杂了,依赖太多,不能做单元测试

* 没有单元测试,这些问题照样能发现。


1, 尽早发现bug,降低bug修复成本。在比较理想的情况下,单元测试能发现占比40%左右的BUG。
2, 验证功能符合预期
3, 文档化
4, 确认代码修改没有影响其他功能。
5, 理解当前系统行为
6, 验证第三方代码。
7, 简化集成(自底向上)
8,TDD

9,增强自信,减少不必须要的时间浪费。

10,及早发现架构等设计方面的问题。

 

什么是好的单元测试?

单元测试常见的问题:

•测试毫无意义
•测试不稳定
•测试需要很长的时间来执行
•测试没有充分覆盖的代码
•测试耦合太紧,修改麻烦
•测试准备工作复杂。

 

• [F]ast
测试执行控制在3分钟以内。尽量减少依赖。
• [I]solated
• [R]epeatable
比如当前时间。必要时可以使用Sandbox。
• [S]elf-validating
 print、System.out.println(),测试框架可以减少代码、减少不必要的输出。要能自验证、自安排、自动化。通常与buildbot、jenkins、TeamCity之类的集成系统,有提交的时候触发构建与测试,甚至持续交付。

当然还有简单Simple的意思。能简单地书写用例、定位问题。
• [T]imely
及时更新测试代码,省得后期对代码生疏了更耗时。必要时有评审也自动系统监控。后期补单元测试的价值这没有前期大。

 

单元测试的内容

Right-BICEP

Right 结果正确
B 边界条件
I 反向关系
C 通过其他方法交叉检查结果
E 错误条件
P 性能

边界值:

•虚假或不一致的输入值,如文件名
“* W:!点¯x\&GI / W $→> $ G / H#@ WQ。
•格式错误,如电子邮件地址缺少顶级域名( fred@foobar. )
•计算中可能导致数值溢出。
•空或没有值,如0,0.0,"”或null。
•值远远超过合理的期望,比如年龄180岁。
•列表中有不该有的重复名单。
•对有序列表进行排序。
•乱序

涉及Conformance、Ordering、Range、Reference、Existence、Cardinality、Time(相对,比如、绝对时间、并发)。

比如索引:

•起始和结束索引具有相同的值
•起始值比结束值大
•指数为负
•结束值超过允许
•不能匹配具体项。

引用:

•跨范围引用了什么
•外部依赖
•是否需要对象在某一状态
•必须存在的任何其他条件

反向关系

比如用加法验证加法,用除法验证乘法。数据库的插入与查询、加密与解密等。百分比的总数必须等于1。

错误条件:
内存满、磁盘满、网线断、邮件进入黑名单、程序崩溃、时间错误、系统负载高、色彩位数低、分辨率低等。

性能:单元测试中计时。JUnitPerf  JMeter等。

 

以上部分主要来源junit8,需要有空时再次阅读。

 

简单的getter和setter之类代码通常不需要测试。JVM相关内容通常可以认为是可靠的。对已有代码的测试,通常从出错最多的地方开始。

 

测试驱动开发

要把测试当做设计工具,不仅仅是质量保证工具。

单元测试介绍_第1张图片

单元测试介绍_第2张图片

 

单元测试介绍_第3张图片

测试驱动开发概念参考:http://osherove.com/blog/2007/10/8/the-various-meanings-of-tdd.html

服务虚拟化

在软件工程中,服务虚拟化是在基于异构组件(如API驱动、基于云计算的应用及SOA等)的应用中仿真具体应用行为的方法。

使用场景:

  • 尚未完成

  • 仍在发展

  • 第三方或合伙人控制

  • 测试的时间和容量有限制

  • 很难规定或在测试环境中配置

  • 需要由不同的团队具有不同的测试数据设置和其他要求的同时访问

  • 负载和性能测试的成本很高

 

常见虚拟资产的创建的方法:
1,录制真实通信
2,日志
3,分析接口规格
4,自定义行为控制接口

 

在SOA等体系中,使用mock工作量很大,敏捷开发加快了迭代速度,为此mock较多的情况下,通常不如采用服务虚拟化。

简单的虚拟化通常通过配置hosts文件,把目标服务器指向自定义服务器。python的flask框架因为开发速度快,经常被采用。在TCP和UDP层,python socket经常使用。这块不仅是对单元,对接口甚至是系统等测试都意义重大。

常用的框架如下:

https://github.com/oarrabi/mockpy(貌似只支持MAC平台),以HTTP模拟为主。

阿里巴巴出品:https://github.com/thx/RAP  主要为帮助web工程师管理API。RAP通过GUI工具帮助WEB工程师更高效的管理接口文档,同时通过分析接口结构自动生成Mock数据、校验真实接口的正确性,使接口文档成为开发流程中的强依赖。有了结构化的API数据,RAP可以做的更多,而我们可以避免更多重复劳动。

wiremock: http://wiremock.org/ https://github.com/tomakehurst/wiremock (推荐)

 

  • HTTP响应打桩,可匹配URL,标题和正文内容

  • 请求验证

  • 在单元测试运行,作为一个独立的进程或为WAR应用

  • Java API,JSON文件和JSON加HTTP配置

  • 桩录制/回放

  • 故障注入

  • 每个请求条件代理

  • 浏览器代理支持请求检查和更换

  • 状态的行为模拟

  • 可配置的响应延迟 

 

junitperf用于单元性能测试。
Feed4junit用于提供数据驱动。

 

https://github.com/jamesdbloom/mockserver 在Java, JavaScript and Ruby中mockHTTP或HTTPS。MockServer内置代理内省流量并支持端口转发、web转发等。

 

https://github.com/splunk/eventgen 是Splunk的事件生成器,基于Python。

 

https://github.com/dreamhead/moco (比较推荐,支持HTTP和socket)

 

快速入门

 

@FunctionalInterface
public interface Scoreable {
   int getScore();
}
import java.util.*;

public class ScoreCollection {
   private List scores = new ArrayList<>();
   
   public void add(Scoreable scoreable) {
      scores.add(scoreable);
   }
   
   public int arithmeticMean() {
      int total = scores.stream().mapToInt(Scoreable::getScore).sum();
      return total / scores.size();
   }
}
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*; 
import org.junit.*;

public class ScoreCollectionTest {
   @Test
   public void answersArithmeticMeanOfTwoNumbers() {
      // Arrange
      ScoreCollection collection = new ScoreCollection();
      collection.add(() -> 5);
      collection.add(() -> 7);
      
      // Act
      int actualResult = collection.arithmeticMean();
      
      // Assert
      assertThat(actualResult, equalTo(6));
   }
}

参考资料:https://en.wikipedia.org/wiki/Service_virtualization

 

Java 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,创建方法参见下面的安装配置部分。

单元测试介绍_第4张图片

单元测试介绍_第5张图片

单元测试介绍_第6张图片

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.          

单元测试介绍_第7张图片

                         

 

在广泛使用的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> extends BaseMatcher> {
    
    private final Comparable 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> Matcher 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:


    junit
    junit
    4.12

 

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,如果光标在测试里面,只会执行当前测试。

 

 

只看失败的测试:

单元测试介绍_第8张图片

测试失败时才弹出:

单元测试介绍_第9张图片

 

拷贝错误信息

单元测试介绍_第10张图片

 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

单元测试介绍_第11张图片

这样就可以使用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 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 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

Java Junit断言


标准断言:

JUnit的Assert类提供一些静态方法,一般以assert开头,参数分别为错误信息,期望值,实际值。如果期望与实际不符,将抛出AssertionException异常。

以下给出这类方法的概览,[]中的参数为可选,类型为String。

 

语句 描述
assertTrue([message,] boolean condition) 确认布尔条件为真。
assertFalse([message,] boolean condition) 确认布尔条件为假
assertEquals([message,] expected, actual) 确认返回等于期望,数组等比较的是地址而非内容。
assertEquals([message,] expected, actual, tolerance) 测试float或double相等。   tolerance为容许的误差。
assertNull([message,] object) 对象为null
assertNotNull([message,] object) 对象非null
assertSame([message,] expected, actual) 变量指向同一对象.
assertNotSame([message,] expected, actual) 变量指向不同对象

assertEquals(double expected,double actual, double delta)可以解决double的精度误差,对于钱,建议使用BigDecimal类型。另外还有个特殊的fail(message)

 

Hamcrest对断言进行了扩展

assertThat扩展了断言,语法如下:

public static void assertThat(Object actual, Matcher matcher)。

导入import static org.hamcrest.CoreMatchers.*;

Matcher实现了org.hamcrest.Matcher接口,并且可以组合,比如:
•  assertThat(calculatedTax, is(not(thirtyPercent)) );
•  assertThat(phdStudentList, hasItem(DrJohn) );
•  assertThat(manchesterUnitedClub, both( is(EPL_Champion)).and(is(UEFA_Champions_League_Champion)) );

Matcher列表如下:
 allOf ,  anyOf ,  both ,  either ,  describedAs ,  everyItem ,  is ,  isA ,  anything ,
hasItem ,  hasItems ,  equalTo ,  any ,  instanceOf ,  not ,  nullValue ,  notNullValue ,
sameInstance ,  theInstance , startsWith ,  endsWith , and  containsString。

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;

import java.util.Arrays;
import java.util.List;

import org.junit.Test;

public class AssertThatTest {
    
    @Test
    public void test_matcher_behavior() throws Exception {
        
        int myAge = 30;
        
        //examine the exact match with equalTo and is
        assertThat(myAge, equalTo(30));
        assertThat(myAge, is(30));
        
        //examine partial match with not()
        assertThat(myAge, not(equalTo(33)));
        assertThat(myAge, is(not(33)));
    }
    
    @Test
    public void verify_multiple_values() throws Exception {
        
        double myMarks = 100.00;
        assertThat(myMarks, either(is(100.00)).or(is(90.9)));
        assertThat(myMarks, both(not(99.99)).and(not(60.00)));
        assertThat(myMarks, anyOf(is(100.00),is(1.00),is(55.00),is(88.00),is(67.8)));
        assertThat(myMarks, not(anyOf(is(0.00),is(200.00))));
        assertThat(myMarks, not(allOf(is(1.00),is(100.00),is(30.00))));
    }
    
    @Test
    public void verify_collection_values() throws Exception {
        
        List salary =Arrays.asList(50.0, 200.0, 500.0);
        assertThat(salary, hasItem(50.00));
        assertThat(salary, hasItems(50.00, 200.00));
        assertThat(salary, not(hasItem(1.00)));
    }
    
    @Test
    public void verify_Strings() throws Exception {
        
        String myName = "John Jr Dale";
        assertThat(myName, startsWith("John"));
        assertThat(myName, endsWith("Dale"));
        assertThat(myName, containsString("Jr"));
    }
}

参考资料

Pragmatic Unit Testing in Java 8 with JUnit -2015 英文 pdf

Pragmatic Unit Testing in Java with JUnit -2004 英文 pdf -- 中文翻译 《单元测试之道Java版》 扫描版

 

 

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

你可能感兴趣的:(单元测试介绍)