Spring MVC Integration Testing - REST API

本文是 Spring MVC Testing 集成测试系列的第4篇,原文链接:Integration Testing of Spring MVC Applications: REST API, Part OneIntegration Testing of Spring MVC Applications: REST API, Part Two

本文主要介绍如何为基于Spring MVC的REST-full的web应用程序添加集成测试。REST服务通过HTTP标准方法的语义(GET/POST/PUT/DELETE等)来隐喻常见的增删改查(CRUD)操作。

本文主要演示如何一步一步地为REST-full API服务添加集成测试用例,包括:

  • 获取Todo项列表接口的集成测试
  • 获取单个Todo项接口的集成测试
  • 删除单个Todo项接口的集成测试
  • 添加新Todo项接口的集成测试
  • 更新Todo项接口的集成测试

Spring MVC Integration Testing - Controllers

本文是Spring MVC Testing 集成测试系列的第2篇,原文链接:Integration Testing of Spring MVC Applications: Controllers

本文主要介绍如何为“标准”Controller编写集成测试。在这里“标准”的含义延续前一个序列 Spring MVC Testing 中的含义,表示不使用Ajax的请求或者处理Form结果的请求。

同样地,本文还是一步一步地为我们的TodoApplication编写集成测试。该程序提供Todo项的增删改查(CRUD)接口,本文主要关注其中的3个接口:获取Todo项列表;查看单个Todo项的详情;以及删除某个Todo项。

Spring MVC Integration Testing - Configuration

本文是 Spring MVC Testing 集成测试系列的第1篇,原文链接:Integration Testing of Spring MVC Applications: Configuration

没有人会否认集成测试的重要性,它是验证我们开发的组件能够正常协同工作的重要手段。不幸的是,对使用Spring MVC开发的web应用程序作集成测试有一点麻烦。

过去我们一直用 SeleniumJWebUnit 来对web应用接口作集成测试,然后效果不是很好。这种方法有以下三个缺点:

  • 对于开发中的web接口,编写和维护测试的工作量比较大
  • 对于使用Javascript,尤其是Ajax的web应用,可用性不高
  • 必须在web容器中启动运行,导致速度慢而且很没有效率

经常就是开发者在后续开发过程中觉得维护之前的集成测试用例太过耗时而且效果不大,所以废弃了这种形式的集成测试。幸运的是,我们找到了一种新型的集成测试框架Spring MVC Test可以用来简化测试工作。

本文主要介绍如何配置Spring MVC Test框架来进行web应用的测试。本系列使用的工具包括:

  • Spring Framework 3.2
  • JUnit 4.10
  • Maven 3.0.3

我们一起来开始进入Spring MVC Test的世界吧!

Spring MVC Unit Testing - REST API

本文是 Spring MVC Testing 单元测试系列的第3篇,原文链接:Unit Testing of Spring MVC Controllers: REST API

使用Spring MVC可以很方便第创建REST风格的接口,但是编写REST风格接口的单元测试并不是那么方便。幸运的是,Spring MVC Test极大地简化了我们为REST风格controller编写单元测试的工作。

本文将通过为Todo项的增删改查(CRUD)的REST风格接口操作编写单元测试的方式,一步一步地讲解如何使用Spring MVC Test来进行单元测试。OK,我们快点进入正文吧!

Spring MVC Unit Testing - Normal Controllers

本文是 Spring MVC Testing 单元测试系列的第2篇,原文链接:Unit Testing of Spring MVC Controllers: "Normal" Controllers

本系列的第1部分讲述了使用Spring MVC Test应如何进行单元测试的配置,现在可以开始实战一下如何对标准controller编写单元测试。

首先需要明确一下。

何为标准controller?

注意:原文标准是加了双引号的("normal")

我们称之为标准controller的Controller,是渲染view或者处理form提交请求的Controller。(与之相对的是Rest Controller)。

OK,现在我们进入正文。

Spring MVC Unit Testing - Configuration

本文是 Spring MVC Testing 单元测试系列的第1篇,原文链接:Unit Testing of Spring MVC Controllers: Configuration

一直以来,为Spring MVC的Controller写单元测试的工作既简单又问题多多。简单体现在单元测试可以很简单地写个测试用例调用一下目标Controller的方法;问题在于这种单元测试完全没有用(不是HTTP的请求),比如说,这种单元测试的方法没办法测试请求映射、参数验证和异常映射等。

幸运的是,从Spring 3.2开始,我们可以使用Spring MVC Test Framework这一强大的工具通过DispatcherServlet来仿照HTTP请求的方式来单元测试Controller的方法。

本文主要介绍如何配置Spring使得可以单元测试Spring MVC Controllers。

下面进入正题。

Spring MVC Testing: Content/目录

本系列翻译自Spring MVC Test Tutorial

Springframework自3.2版本以后,提供了Spring MVC Test Framework用于对Spring MVC项目进行测试。

本系列一共两个部分:单元测试和集成测试。

单元测试将一个一个的Spring MVC Controller作为一个单元,对每一个接口进行测试。Controller层对Service层的调用使用Mockito进行模拟。

集成测试对整个web服务进行测试,虽然测试的单位仍然是接口,但是测试结果更偏向于生产环境。为了保证测试的稳定性,使用了DBUnit来控制每一次测试的数据样本。

需要注意的是,虽然这个系列将Spring MVC Test Framework分为单元测试和集成测试两个部分,但是对于Spring本身来说,其内部实现都是一样的。单元测试和集成测试的区分,是从开发者的角度进行的区分。

Learning Java Concurrency - ReentrantReadWriteLock

ReentrantLock是互斥锁,对于要保护的资源,同一时间只能有一个线程进行访问。所谓的访问,就是读和写。但是在实际中,往往是读操作对互斥性的要求远远低于写操作。

考虑一个共享资源,比如一个List对象,可能会有多个线程对其进行读写。

下面是使用ReentrantLock实现的一个版本。

private static class ExclusiveLockStack {
    private final List<String> list = new ArrayList<String>();
    private final ReentrantLock lock = new ReentrantLock();

    public void push(final String val) {
        if (null == val)    return;

        lock.lock();
        try {
            list.add(val);
        } finally {
            lock.unlock();
        }
    }

    public String last() {
        lock.lock();
        String str = null;
        try {
            final int lastIdx = list.size() - 1;
            if (lastIdx >= 0) {
                str = list.get(lastIdx);
            }
        } finally {
            lock.unlock();
        }
        return str;
    }
}

下面是使用ReentrantReadWriteLock实现的一个版本。

ReadWrittatic class ReadWriteLockStack {
    private final List<String>           list  = new ArrayList<String>();
    private final ReentrantReadWriteLock lock  = new ReentrantReadWriteLock();
    private final Lock                   rLock = lock.readLock();
    private final Lock                   wLock = lock.writeLock();

    public void push(final String val) {
        if (null == val)    return;

        wLock.lock();
        try {
            list.add(val);
        } finally {
            wLock.unlock();
        }
    }

    public String last() {
        rLock.lock();
        String str = null;
        try {
            final int lastIdx = list.size() - 1;
            if (lastIdx >= 0) {
                str = list.get(lastIdx);
            }
        } finally {
            rLock.unlock();
        }
        return str;
    }
}

Learning Java Concurrency - ReentrantLock & Condition

ReentrantLocksynchronized的高阶版本,用来控制多线程同步。ReentrantLock是一种独占锁,同一时间只能有一个线程使用一把锁,其他请求加锁的线程都会被阻塞。除了控制多线程同步之外,ReentrantLock还提供了Condition用来进行多线程通讯。ConditionObject类的方法wait & notify的替代版本,可以用等待/通知模式来有效控制多线程对共享资源的访问。

仿synchronized,用ReentrantLock实现单例模式的代码如下:

private static class Singleton {
    private static volatile Singleton INSTANCE;
    private static ReentrantLock lock = new ReentrantLock();

    private Singleton() {}

    public static Singleton instance() {
        Singleton var = INSTANCE;
        if (null == var) {
            lock.lock();
            try {
                var = INSTANCE;
                if (null == var) {
                    INSTANCE = var = new Singleton();
                }
            } finally {
                lock.unlock();
            }
        }

        return var;
    }
}

仿wait & notify,用Condition来实现父子通知汇款的代码如下:

private static class DepositAccount {
    private int money;

    private final ReentrantLock lock = new ReentrantLock();
    private final Condition cond = lock.newCondition();

    public DepositAccount() {
        this.money = 0;
    }

    public void withdraw(final int val) {
        lock.lock();
        try {
            while (money < val) {
                try {
                    cond.await(); // 钱不够,等一会儿
                } catch (InterruptedException e) {
                    // do nothing here
                }
            }

            money -= val;
        } finally {
            lock.unlock();
        }
    }

    public void deposite(final int val) {
        lock.lock();
        try {
            money += val;

            cond.signalAll(); // 存完钱周知一下
        } finally {
            lock.unlock();
        }
    }
}

Learning Java Concurrency - wait & notify

在synchronized关键字之外,Java提供了另外的waitnotify函数族用于支援多线程通信,使用上类似于JUC的Condition类。

wait()notify()notifyAll()是Object类的方法,与synchronized配套使用。

public class Object {
    ...

    public final native void wait(long timeout) throws InterruptedException;
    public final void wait(long timeout, int nanos) throws InterruptedException;
    public final void wait() throws InterruptedException;

    public final native void notify();
    public final native void notifyAll();

    ...
}

wait一共有三个函数。调用wait的线程必须已经持有了同一个对象的同步器(使用synchronized)。调用wait的线程会进入等待状态,直到另外的线程调用了同一个对象的notify函数,或者指定的等待时间过期,或者被中断(引发InterruptedException)。调用wait函数之后,当前线程会放弃已经持有的同步器。

notify一共是有两个函数。notify()函数会唤醒当前的由于执行wait而进入等待的某个线程;注意,被唤醒的线程是不可预料的,也就是说不同的JVM实现可以用不同的规则算法来决定被唤醒的是哪一个线程。notifyAll()函数会唤醒所有的等待线程,但是只会有一个线程最终进入执行。

Learning Java Concurrency - synchronized

synchronized,同步控制器,是Java原生提供的多线程同步控制的工具,是Java语法的一部分。

synchronized在语义上等同于一个独占锁。synchronized可以用来修饰一个方法,标识该方法是可同步的;也可以用来修饰语句块,标识该语句块是同步的。在代码经过编译之后,JVM会在方法或者语句块的前后插入monitorentermonitorexit的虚拟机指令,这两条指令又会隐式地调用lock原语。

synchronized可以使用在普通方法里,也可以使用在静态方法里。

class Synchronized {

    public synchronized void method1(final String val) {
        System.out.println("1: Begin add " + val);
        System.out.println("1: Finish add " + val);
    }
    
    public synchronized static void method2(final String val) {
        System.out.println("2: Begin add " + val);
        System.out.println("2: Finish add " + val);
    }

    public void method3(final String val) {
        synchronized(this) {
            System.out.println("3: Begin add " + val);
            System.out.println("3: Finish add " + val);
        }
    }

    public static void method4(final String val) {
        synchronized(Synchronized.class) {
            System.out.println("4: Begin add " + val);
            System.out.println("4: Finish add " + val);
        }
    }
}

Learning Java Concurrency - Content/目录

Java Concurrency是学习Java的过程中绕不过去的一道坎。既然绕不过去,只能一点一点去学习它,然后了解它。本系列是在学习Java Concurrency过程中的记录,鉴于网络上关于原理和实现代码的文章一大堆,所以本系列会尽量以实际应用为主。毕竟原理了解的再深,还是要落地为代码。

本系列主要参考《深入浅出Java Concurrency》系列。