单元测试JUnit
说明
1.JUnit4 的执行的一般流程:
a)首先获得待测试类所对应的 Class对象。
b)然后通过该 Class对象获得当前类中所有 public 方法所对应的 Method 数组。
c)遍历该Method数组,取得每一个Method对象
d)调用每个Method 对象的isAnnotationPresent(Test.class)方法,判断该方法是否被 Test注解所修饰。
e)如果该方法返回true,那么调用method.invoke()方法去执行该方法,否则不执行。
2.测试类的私有方法时可以采取两种方式:
1)修改方法的访问修饰符,将 private 修改为 default 或 public(但不推荐采
取这种方式)。
2)使用反射在测试类中调用目标类的私有方法(推荐)
单元测试DeleteAll
package com.vvvv.junit;
import java.io.File;
import junit.framework.Assert;
import junit.framework.TestCase;
public class DeleteAllTest extends TestCase{
public void testDeleteAll(){
File file = null;
try{
file = new File("test.txt");
file.createNewFile();
DeleteAll.deleteAll(file);
}catch (Exception ex){
Assert.fail();
}
boolean isExist = file.exists();
Assert.assertFalse(isExist);
}
/**
* 构造的是一个目录结构,其结构表示如下
*
* d
* / \
* / \
* d1 d2
* / \
* / \
*s1 s2
*
*/
public void testDeleteAll2(){
File directory = null;
try{
directory = new File("dir");
directory.mkdir();
File file1 = new File(directory, "file1.txt");
File file2 = new File(directory, "file2.txt");
file1.createNewFile();
file2.createNewFile();
File d1 = new File(directory, "d1");
File d2 = new File(directory, "d2");
d1.mkdir();
d2.mkdir();
File subFile1 = new File(d1, "subFile1.txt");
File subFile2 = new File(d2, "subFile2.txt");
subFile1.createNewFile();
subFile2.createNewFile();
DeleteAll.deleteAll(directory);
}
catch(Exception ex){
Assert.fail();
}
Assert.assertNotNull(directory);
String[] names = directory.list();
Assert.assertEquals(0, names.length);
directory.delete();
}
}
基于注解修饰
3.在JUnit 4中, 通过@Before注解实现与JUnit 3.8中的setUp方法同样的功能,通过@After 注解实现与 JUnit 3.8 中的tearDown 方法同样的功能。
4.在JUnit 4 中,使用@BeforeClass与@AfterClass 注解修饰一个 public static void no-arg 的方法,这样被@BeforeClass 注解所修饰的方法会在所有测试方法执行前执行;被@AfterClass注解所修饰的方法会在所有测试方法执行之后执行。
@BeforeClass
public void globalInit(){
System.out.println("globalInit invoked!");
}
@AfterClass
public static void globalDestroy(){
System.out.println("globalDestroy invoked!");
}
5.@Ignore 注解可用于修饰测试类与测试方法,当修饰测试类时,表示忽略掉类中的所有测试方法;当修饰测试方法时,表示忽略掉该测试方法。
6.对测试设置期望抛出异常与超时限制
示例:
参数化测试
7.参数化测试(Parameters):当一个测试类使用参数化运行器运行时,需要在类的声明处加上@RunWith(Parameterized.class)注解,表示该类将不使用 JUnit内建的运行器运行,而使用参数化运行器运行;在参数化运行类中提供参数的方法上要使用@Parameters 注解来修饰,同时在测试类的构造方法中为各个参数赋值(构造方法是由 JUnit 调用的),最后编写测试类,它会根据参数的组数来运行测试多次。
package com.vvvv.junit4;
import static org.junit.Assert.assertEquals;
import java.util.Arrays;
import java.util.Collection;
import org.junit.Before;
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 ParametersTest{
private int expected;
private int input1;
private int input2;
private Calculator cal;
@Parameters
public static Collection prepareData(){
Object[][] object = { { 3, 1, 2 }, { -4, -1, -3 }, { 5, 2, 3 },
{ 4, -4, 8 } };
return Arrays.asList(object);
}
@Before
public void setUp(){
cal = new Calculator();
}
//构造方法
public ParametersTest(int expected, int input1, int input2){
this.expected = expected;
this.input1 = input1;
this.input2 = input2;
}
@Test
public void testAdd(){
assertEquals(this.expected, cal.add(this.input1, this.input2));
}
}
测试套件
TestSuite(测试套件):可以将多个测试组合到一起,同时执行多个测试,如下示例:
Junit 3的测试套件
package com.vvvv.junit;
import junit.extensions.RepeatedTest;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
public class TestAll extends TestCase{
public static Test suite(){
//测试套件
TestSuite suite = new TestSuite();
//执行几个测试类
suite.addTestSuite(CalculatorTest.class);
suite.addTestSuite(LargestTest.class);
suite.addTestSuite(DeleteAllTest.class);
suite.addTest(new RepeatedTest(new CalculatorTest("testSubtract"), 20));
return suite;
}
}
Junit 4测试套件
在Junit 4 中使用Suite运行器允许你手动构建一个包含多个测试类的套件,相当于在Junit 3中的static Test suite()方法,使用@Runwith(Suite.class)和@SuiteClasses(TestClass.class, ...)注解修饰即可,@Runwith(Suite.class)修饰指明测试的类用Suite套件运行器运行,@SuiteClass指明使用哪些测试套件。
package com.vvvv.junit4;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({ CalculatorTest.class, LargestTest.class,
ParametersTest.class })
public class TestAll{
}
在 JUnit 4 中,如果想要同时运行多个测试,需要使用两个注解:@RunWith(Suite.class)以及@Suite.SuiteClasses(),通过这两个注解分别指定使用 Suite 运行器来运行测试,以及指定了运行哪些测试类,其中的@SuiteClasses 中可以继续指定 Suite,这样 JUnit 会再去寻找里面的测试类,一直找到能够执行的 Test Case 并执行之。
package com.vvvv.junit4;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses(TestAll.class)
public class TestAll2{
}
在Junit的测试类中,每一个测试方法的运行都会重新执行setUp 与tearDown方法,例如以下测试类CalculatorTest中,如果有n个测试方法,则setUp与tearDown方法就会执行n次,结果会输出n个1。
public class CalculatorTest extends TestCase{
private Calculator cal;
private int count;
public CalculatorTest(String name){
super(name);
}
@Override
public void setUp() throws Exception{
System.out.println(++count);
cal = new Calculator();
}
@Override
public void tearDown() throws Exception{
}
public void testAdd(){
int result = cal.add(1, 2);
Assert.assertEquals(3, result);
}
……
public static void main(String[] args){
//以awt ui风格来运行CalculatorTest测试类
//junit.awtui.TestRunner.run(CalculatorTest.class);
//以swing ui风格来运行CalculatorTest测试类
junit.swingui.TestRunner.run(CalculatorTest.class);
}
}
模板方法模式(Template Method)
说明
定义
定义一个操作中的算法骨架,而将一些步骤延伸到子类中去,使得子类可以不改变一个算法的结构,即可重新定义该算法的某些特定步骤。这里需要复用的是算法的结构,也就是步骤,而步骤的实现可以在子类中完成。
使用场合
1)一次性实现一个算法的不变部分,并且将可变的行为留给子类来完成。
2)各子类公共的行为应该被提取出来并集中到一个公共父类中以避免代码的重复。首先识别现有代码的不同之处,并且把不同部分分离为新的操作,最后,用一个调用这些新的操作的模板方法来替换这些不同的代码。
3)控制子类的扩展
模板方法模式的组成
– 父类角色:提供模板。
– 子类角色:为模板提供实现。
模板方法模式在Junit中具体应用,可以在TestCase类中,runBare()方法,首先是执行setUp方法,然后执行runTest方法,最后执行tearDown方法。如下图:
模板方法模式示例
模板抽象类
package com.vvvv.pattern.templatemethod;
public abstract class AbstractClass{
public void template(){
this.method1();
this.method2();
this.method3();
}
public abstract void method1();
public abstract void method2();
public abstract void method3();
}
对模板的具体实现类
package com.vvvv.pattern.templatemethod;
public class ConcreteClass extends AbstractClass{
@Override
public void method1(){
System.out.println("step 1");
}
@Override
public void method2(){
System.out.println("step 2");
}
@Override
public void method3(){
System.out.println("step 3");
}
}
调用客户端
package com.vvvv.pattern.templatemethod;
public class Client{
public static void main(String[] args){
AbstractClass ac = new ConcreteClass();
ac.template();
}
}
适配器模式
说明
将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式的构成
目标抽象角色(Target)
– 定义客户要用的特定领域的接口
适配器(Adapter)
– 调用另一个接口,作为一个转换器
被适配角色(Adaptee)
– 定义一个接口,Adapter需要接入
客户端(Client)
– 协同对象符合Adapter适配器
适配器的分类
有两种类型的适配器模式
– 类适配器(采取继承的方式)
– 对象适配器(采取对象组合的方式)推荐使用这种方式
适配器模式示例(类适配器,通过继承)
抽象目标角色
package com.vvvv.pattern.adapter;
//目标抽象角色
public interface Target{
public void method1();
}
被适配角色
package com.vvvv.pattern.adapter;
//要被适配的角色
public class Adaptee{
//被适配的目标方法
public void method2(){
System.out.println("目标方法");
}
}
适配器
package com.vvvv.pattern.adapter;
//适配器角色(通过继承要被适配的类,实现该适配器对它的适配)
public class Adapter extends Adaptee implements Target{
//通过适配器角色的method1间接调用目标方法
@Override
public void method1(){
this.method2();
}
}
客户端
package com.vvvv.pattern.adapter;
//客户端
public class Client{
public static void main(String[] args){
//通过初始化一个适配器调用目标
Target target = new Adapter();
//调用目标方法
target.method1();
}
}
适配器模式在Junit当中的应用
在Junit 中,runTest方法即是适配器的方法,它适配我们具体的测试方法,即通过它,间接的调用了测试类中的测试方法;在runBare方法中,通过runTest方法将我们自己编写的testXXX方法进行了适配,使得JUnit 可以执行我们自己编写的 TestCase,runTest 方法的实现如下:
在runTest 方法中,首先获得我们自己编写的 testXXX 方法所对应的Method 对象(不带参数),然后检查该 Method 对象所对应的方法是否是 public 的,如果是则调用Method 对象的invoke 方法来去执行我们自己编写的 testXXX方法。
对象组合方式
抽象目标角色
package com.vvvv.pattern.adapter2;
//抽象目标角色
public interface Target{
public void method1();
}
被适配者
package com.vvvv.pattern.adapter2;
//适配器
public class Adapter implements Target{
private Adaptee adaptee;
public Adapter(Adaptee adaptee){
this.adaptee = adaptee;
}
@Override
public void method1(){
adaptee.method2();
}
}
适配器
package com.vvvv.pattern.adapter2;
//被适配者
public class Adaptee{
public void method2(){
System.out.println("执行方法");
}
}
客户端
package com.vvvv.pattern.adapter2;
//客户端
public class Client{
public static void main(String[] args){
Target target = new Adapter(new Adaptee());
target.method1();
}
}
缺省的适配器模式(AWT,Swing事件模型所采用的模式)
抽象目标角色
package com.vvvv.pattern.defaultadapter;
//抽象目标
public interface AbstractService{
public void service1();
public void service2();
public void service3();
}
适配器(缺省的适配器)
package com.vvvv.pattern.defaultadapter;
//适配器(缺省的适配器,都空实现)
public class ServiceAdapter implements AbstractService{
@Override
public void service1(){
}
@Override
public void service2{
}
@Override
public void service3(){
}
}
具体的实现类(具体的适配器)
package com.vvvv.pattern.defaultadapter;
//具体的实现类
public class ConcreteService extends ServiceAdapter{
@Override
public void service1(){
System.out.println("执行业务方法");
}
}
在awt中的应用(swing与awt事件模型所采用的模式)
package com.vvvv.pattern.defaultadapter;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
public class AwtApp{
public static void main(String[] args){
Frame frame = new Frame("title");
frame.addMouseMotionListener(new MouseMotionAdapter(){
@Override
public void mouseMoved(MouseEvent e){
System.out.println("x: " + e.getX() + " y: " + e.getY());
}
});
frame.addWindowListener(new WindowAdapter(){
@Override
public void windowClosing(WindowEvent e){
System.out.println("关闭窗口");
System.exit(0);
}
});
frame.setSize(new Dimension(50, 100));
frame.setVisible(true);
}
}
命令模式
说明
意图
将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤消的操作。
作用
命令模式的构成
1 客户角色:创建一个具体命令对象,并确定其接收者
2 命令角色:声明一个给所有具体命令类的抽象接口。这是一个抽象角色,通常由一个接口或抽象类实现
3 具体命令角色:定义一个接收者和行为之间的弱耦合,实现execute方法,负责调用接收者的相应操作
4 请求者角色:负责调用命令对象执行请求。
5 接收者角色:负责具体实施和执行一个请求
命令模式示例
抽象命令角色
package com.vvvv.pattern.command;
//命令对象
public interface Command{
public void execute();
}
命令接收者
package com.vvvv.pattern.command;
//接收者
public class Receiver{
public void doAction(){
System.out.println("执行命令操作!");
}
}
具体命令角色
package com.vvvv.pattern.command;
//具体的命令角色(关联到了接收者)
public class ConcreteCommand implements Command{
private Receiver receiver;
public ConcreteCommand(Receiver receiver){
this.receiver = receiver;
}
@Override
public void execute(){
receiver.doAction();
}
}
命令请求者
package com.vvvv.pattern.command;
//命令请求者(具体对命令的调用者)
public class Invoker{
private Command command;
public Invoker(Command command){
this.command = command;
}
//执行调用操作
public void doInvokerAction(){
command.execute();
}
}
客户端
package com.vvvv.pattern.command;
public class Client{
public static void main(String[] args){
//接收者
Receiver receiver = new Receiver();
//命令
Command command = new ConcreteCommand(receiver);
//请求者
Invoker invoker = new Invoker(command);
//请求者请求
invoker.doInvokerAction();
}
}
组合模式
说明
Component(抽象构件接口)
– 为组合的对象声明接口
– 在某些情况下实现从此接口派生出的所有类共有的默认行为
– 定义一个接口可以访问及管理它的多个子部件
Leaf(叶部件)
– 在组合中表示叶节点对象,叶节点没有子节点
– 定义组合中接口对象的行为
Composite(组合类)
– 定义有子节点(子部件)的部件的行为
– 存储子节点(子部件)
– 在Component接口中实现与子部件相关的操作
Client(客户端)
– 通过Component接口控制组合部件的对象
组合模式示例
抽象构件接口
package com.vvvv.pattern.composite;
//抽象构件接口
public interface Component{
public void doSomething();
}
组合类
package com.vvvv.pattern.composite;
import java.util.ArrayList;
import java.util.List;
//组合类(可以存放叶子与组合对象)
public class Composite implements Component{
private List<Component> list = new ArrayList<Component>();
public void add(Component component){
list.add(component);
}
public void remove(Component component){
list.remove(component);
}
public List<Component> getAll(){
return this.list;
}
@Override
public void doSomething(){
//遍历
for(Component component : list){
component.doSomething();
}
}
}
叶子结点
package com.vvvv.pattern.composite;
//叶子
public class Leaf implements Component{
@Override
public void doSomething(){
System.out.println("执行方法");
}
}
客户端
package com.vvvv.pattern.composite;
//客户端
public class Client{
public static void main(String[] args){
Component leaf1 = new Leaf();
Component leaf2 = new Leaf();
Composite comp1 = new Composite();
comp1.add(leaf1);
comp1.add(leaf2);
Component leaf3 = new Leaf();
Component leaf4 = new Leaf();
Composite comp2 = new Composite();
comp2.add(comp1);
comp2.add(leaf3);
comp2.add(leaf4);
comp2.doSomething();
}
}
组合模式的第二种实现
抽象构件接口
package com.vvvv.pattern.composite2;
import java.util.List;
public interface Component{
public void doSomething();
public void add(Component component);
public void remove(Component component);
public List<Component> getAll();
}
组合类
package com.vvvv.pattern.composite2;
import java.util.ArrayList;
import java.util.List;
public class Composite implements Component{
private List<Component> list = new ArrayList<Component>();
public void doSomething(){
for(Component component : list){
component.doSomething();
}
}
public void add(Component component){
list.add(component);
}
public void remove(Component component){
list.remove(component);
}
public List<Component> getAll(){
return this.list;
}
}
叶子结点
package com.vvvv.pattern.composite2;
import java.util.List;
public class Leaf implements Component{
public void doSomething(){
System.out.println("执行方法");
}
public void add(Component component){}
public void remove(Component component){}
public List<Component> getAll(){
return null;
}
}