zoukankan      html  css  js  c++  java
  • junit设计模式--适配器模式

    • 适配器(Adapter)模式
      在软件系统中,由于环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。那么如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?  这就要利用Adapter模式。


    • Adapter模式意图
      1,将一个类的接口转换成客户希望的另一个接口。
      2,Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。


    • 适配器(Adapter)模式的构成
      1,目标抽象角色(Target)

      定义客户要用的特定领域的接口。


      2,适配器(Adapter)

    调用另一个接口,作为一个转换器。


      3,适配类(Adaptee)

      定义一个接口,Adapter需要接入。即系统中原有的类,需要适配使之可以利用。


      4,客户端(Client)

    协同对象符合Adapter适配器。



    • 适配器的分类
      适配器模式(Adapter Pattern)主要分为三种:

      1.基于类的继承方式,也称类适配器。


      2.基于对象组合方式,也称对象适配器,推荐这种实现而不是上一种使用继承。

      3.缺省的适配器模式(AWT, Swing事件模型所采用的适配器模式)。


    OK,现在我们分别来写几个例子:

    1,基于类的继承方式。

    package org.linkinpark.junit.testjunit;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月5日
     * @功能描述: 新需求的需要的接口
     */
    public interface Target
    {
    	
    	// 新需求需要实现的接口
    	void method();
    
    }
    
    package org.linkinpark.junit.testjunit;
    
    public class TargetImpl implements Target
    {
    
    	@Override
    	public void method()
    	{
    		System.out.println("这里是新需求的方法。。。");
    	}
    
    }
    
    package org.linkinpark.junit.testjunit;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月5日
     * @功能描述: 需要被适配的对象
     */
    public class Adaptee
    {
    	
    	public void method()
    	{
    		System.out.println("这里是原来的方法。。。");
    	}
    
    }
    
    package org.linkinpark.junit.testjunit;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月5日
     * @功能描述: 适配器
     */
    public class Adapter extends Adaptee implements Target
    {
    
    	@Override
    	public void method()
    	{
    		super.method();
    	}
    
    }
    
    package org.linkinpark.junit.testjunit;
    
    import org.junit.Test;
    
    public class AdapterTest
    {
    	
    	@Test
    	public void testAdapter()
    	{
    		Target target = new Adapter();
    		target.method();
    		
    		Target target1 = new TargetImpl();
    		target1.method();
    	}
    
    }
    

    OK,现在我们来看下控制台的输出,OK,完美的兼容了新旧版本。

    这里是原来的方法。。。
    这里是新需求的方法。。。
    


    2,基于组合的方式

    其他类不用动,只是将原来的适配器由继承旧对象变成组合旧对象就OK了。代码如下:

    package org.linkinpark.junit.testjunit;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年2月5日
     * @功能描述: 适配器
     */
    public class Adapter implements Target
    {
    	private Adaptee adaptee;
    	
    	// 这里可以构造器注入旧对象,也可以在上面的属性set()设值
    	public Adapter(Adaptee adaptee)
    	{
    		super();
    		this.adaptee = adaptee;
    	}
    
    
    	@Override
    	public void method()
    	{
    		adaptee.method();
    	}
    
    }
    
    package org.linkinpark.junit.testjunit;
    
    import org.junit.Test;
    
    public class AdapterTest
    {
    
    	@Test
    	public void testAdapter()
    	{
    		Target target = new Adapter(new Adaptee());
    		target.method();
    
    		Target target1 = new TargetImpl();
    		target1.method();
    	}
    
    }
    
    运行上面的测试代码,控制台同样的输出,OK,没问题。

    3,关于第三种方式,这里不做赘述了。就是在有多个方法的接口和一个不想实现那么多方法的实现类中加入实现所有接口方法的一层,加入的这一层的实现可以是空实现,这个无所谓。在自己真正需要的实现类中去重写加入的那一层里面自己敢兴趣的方法就OK了。


    来总结一下:

    • 适配器(Adapter)模式的适用性
      1,对象需要利用现存的并且接口不兼容的类。

      2,需要创建可重用的类以协调其他接口可能不兼容的类。



    • JUnit中的适配器模式:
      1,JUnit3.8的TestCase的源代码中,真正运行测试源码的代码如下,
    public void runBare() throws Throwable {
            setUp();
            try {
                runTest();
            }
            finally {
                tearDown();
            }
        }  
    runTest()中执行我们的测试方法。测试方法testXXX要求必须是public void并且不带参数的,这里就使用了适配器模式。runTest()的实现如下:

    protected void runTest() throws Throwable
    	{
    		assertNotNull("测试的方法名不能为空", fName);
    		Method runMethod = null;
    		try
    		{
    			runMethod = getClass().getMethod(fName, (Class[]) null);
    		}
    		catch (NoSuchMethodException e)
    		{
    			fail("Method "" + fName + "" not found");
    		}
    		if (!Modifier.isPublic(runMethod.getModifiers()))
    		{
    			fail("Method "" + fName + "" should be public");
    		}
    
    		try
    		{
    			System.out.println(String.format("框架开始执行测试,执行的方法是-->%s", runMethod));
    			runMethod.invoke(this);
    			System.out.println(String.format("框架结束执行测试,执行的方法是-->%s", runMethod));
    		}
    		catch (InvocationTargetException e)
    		{
    			e.fillInStackTrace();
    			throw e.getTargetException();
    		}
    		catch (IllegalAccessException e)
    		{
    			e.fillInStackTrace();
    			throw e;
    		}
    	}

    2,在junit4X系列中,junit也给我们向后兼容了38系列,这里用到的也是适配器模式。OK,我们来看一些核心代码:

    Junit4X系列测试入口是junitCore类:

    public class JUnitCore
    {
    	private final RunNotifier notifier = new RunNotifier();
    
    	/**
    	 * @创建时间: 2016年1月22日
    	 * @相关参数: @param args
    	 * @功能描述: 框架测试入口
    	 */
    	public static void main(String... args)
    	{
    		String[] linkinArgs = new String[] { "org.linkinpark.commons.textui.LinkinTest4Annotation" };
    		Result result = new JUnitCore().runMain(new RealSystem(), linkinArgs);
    		System.exit(result.wasSuccessful() ? 0 : 1);
    	}
    }

    /**
    	 * @创建时间: 2016年1月22日
    	 * @相关参数: @param system
    	 * @相关参数: @param args
    	 * @相关参数: @return 测试结果
    	 * @功能描述: 框架核心代码开始执行入口
    	 */
    	Result runMain(JUnitSystem system, String... args)
    	{
    		system.out().println("linkin-frame-junit测试框架版本号==> " + Version.id());
    
    		System.out.println("第一步:框架开始执行====");
    		JUnitCommandLineParseResult jUnitCommandLineParseResult = JUnitCommandLineParseResult.parse(args);
    		System.out.println("第二步:开始添加事件====");
    		RunListener listener = new TextListener(system);
    		addListener(listener);
    		System.out.println("第三步:开始运行测试====");
    		return run(jUnitCommandLineParseResult.createRequest(defaultComputer()));
    	}
    关键是最后一行代码,jUnitCommandLineParseResult.createRequest()创建一个Request对象返回给测试执行器执行。OK,我们接着看下Request对象的这个方法:

    public static Request classes(Computer computer, Class<?>... classes)
    	{
    		try
    		{
    			// 默认的可以执行所有的测试执行器builder
    			AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
    			Runner suite = computer.getSuite(builder, classes);
    			return runner(suite);
    		}
    		catch (InitializationError e)
    		{
    			return runner(new ErrorReportingRunner(e, classes));
    		}
    	}

    AllDefaultPossibilitiesBuilder是所有可能执行的测试执行器的builder,在Request中junit4X用默认的策略类computer来初始化测试执行器。

    public Runner getSuite(final RunnerBuilder builder, Class<?>[] classes) throws InitializationError
    	{
    		return new Suite(new RunnerBuilder()
    		{
    			@Override
    			public Runner runnerForClass(Class<?> testClass) throws Throwable
    			{
    				return getRunner(builder, testClass);
    			}
    		}, classes);
    	}
    
    	protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable
    	{
    		return builder.runnerForClass(testClass);
    	}
    核心代码来了:

    @Override
    	public Runner runnerForClass(Class<?> testClass) throws Throwable
    	{
    		List<RunnerBuilder> builders = Arrays.asList(ignoredBuilder(), annotatedBuilder(), suiteMethodBuilder(), junit3Builder(), junit4Builder());
    		for (RunnerBuilder each : builders)
    		{
    			Runner runner = each.safeRunnerForClass(testClass);
    			if (runner != null)
    			{
    				return runner;
    			}
    		}
    		return null;
    	}
    
    	protected JUnit4Builder junit4Builder()
    	{
    		return new JUnit4Builder();
    	}
    
    	protected JUnit3Builder junit3Builder()
    	{
    		return new JUnit3Builder();
    	}
    
    	protected AnnotatedBuilder annotatedBuilder()
    	{
    		return new AnnotatedBuilder(this);
    	}
    
    	protected IgnoredBuilder ignoredBuilder()
    	{
    		return new IgnoredBuilder();
    	}
    
    	protected RunnerBuilder suiteMethodBuilder()
    	{
    		if (canUseSuiteMethod)
    		{
    			return new SuiteMethodBuilder();
    		}
    		return new NullBuilder();
    	}

    循环迭代所有的可能的测试执行器,如果是38系列的测试源码,也就是说我们自己写的测试类继承于TestCase,那么就会给我们返回一个38系列的测试执行器-->Junit3Builder。

    package org.linkinpark.junit.internal.builders;
    
    import org.linkinpark.commons.framework.TestCase;
    import org.linkinpark.junit.runner.Runner;
    import org.linkinpark.junit.runners.JUnit38ClassRunner;
    import org.linkinpark.junit.runners.model.RunnerBuilder;
    
    public class JUnit3Builder extends RunnerBuilder
    {
    	@Override
    	public Runner runnerForClass(Class<?> testClass) throws Throwable
    	{
    		if (isPre4Test(testClass))
    		{
    			return new JUnit38ClassRunner(testClass);
    		}
    		return null;
    	}
    
    	boolean isPre4Test(Class<?> testClass)
    	{
    		return TestCase.class.isAssignableFrom(testClass);
    	}
    }

    OK,接下来就可以嫁入junit38框架来运行测试了呢。

    public class JUnit38ClassRunner extends Runner implements Filterable, Sortable
    {
    	private volatile Test test;
    	
    	@Override
    	public void run(RunNotifier notifier)
    	{
    		TestResult result = new TestResult();
    		result.addListener(createAdaptingListener(notifier));
    		getTest().run(result);
    	}
    }

    OK,关于junit4我后面会专门整理到,这里简单说下这个Runner,该类是junit4X系列中所有运行执行器父类,该类里面封装一个run()的抽象方法来运行测试用例。


    其实原理和38系列的差不多,但是关于注解的处理那一刻值得我们去学习下的。

    这里以runner抽象类来结束这篇博客:

    package org.linkinpark.junit.runner;
    
    import org.linkinpark.junit.runner.notification.RunNotifier;
    
    /**
     * @创建作者: LinkinPark
     * @创建时间: 2016年1月23日
     * @功能描述: 所有测试执行器的父类
     */
    public abstract class Runner implements Describable
    {
    	public abstract Description getDescription();
    
    	public abstract void run(RunNotifier notifier);
    
    	public int testCount()
    	{
    		return getDescription().testCount();
    	}
    }
    


  • 相关阅读:
    c#使用SoundPlayer播放wav格式音频
    c#NAudio 录音功能实现
    c#异步方法调用
    c# 读取文件目录下的信息
    angular笔记_1
    js获取form元素,不使用id
    事物回滚机制
    ckplayer跨域调用
    帝国移动pc站文章
    页面切换导航样式也随之改变
  • 原文地址:https://www.cnblogs.com/LinkinPark/p/5232879.html
Copyright © 2011-2022 走看看