其他测试框架 测试开发之路 ---- 可读性,可维护性,可扩展性 (续)

孙高飞 · 2016年05月13日 · 最后由 纯白约定 回复于 2022年01月24日 · 4333 次阅读
本帖已被设为精华帖!

目录:
第一部分:测试开发之路 ---- 框架中数据的管理策略
第二部分:测试开发之路 ---- 数据驱动及其变种
第三部分:测试开发之路 ---- 可读性,可维护性,可扩展性
第四部分:测试开发之路 ---- 可读性,可维护性,可扩展性 (续)

前言

上一章结尾的比较着急,有一个很常用的设计没有讲,就是 可插拔式设计 ,这次我在这里补充一下吧。平时工作比较忙,我有时间写点就写吧,以后不会再有那么大篇幅的东西了. 因为我发现,篇幅一长我就着急,一着急很多东西我就讲得很浅就过去了,我应该检讨。下面我们来一步一步实现这个机制。

为了变化,将算法抽象出来

举个常见的例子,假如我们现在有一个接口测试框架,它是测试 http 协议的接口,一切工作的很正常。但是如果突然某一天开发那边的架构变化了,为了负载均衡他们将模块分离,并用 RPC 协议作模块间的沟通。 于是乎一个基于 RPC 协议的 java 接口测试需求出现了。即便不是 RPC 协议接口。普通的 java 接口测试需求也有很多有获取方式,有用 spring 管理的,有单纯用 Jar 包的,也有 webservice 的。我们不可能一次性的把所有的可能性都想到。只能是碰到的时候再实现。但是我们预料到了之后的变化,就需要留下一种策略能让框架自由的选择哪一种方式的同时,保证可读性,可维护性和可扩展性。这时候千万别 if else 起来没完,我上一章已经讲过了大堆 if else 和 for 循环嵌套的弊端了。

所以这时候我们决定把每种接口的获取方式都看成一个算法。这些算法有统一的接口,其他模块调用的时候,是 面对这个接口编程 的,这个思想很重要。现在我们定义这么一个接口。

/**
 * 工厂接口:工厂方法返回的是RD待测接口的对象
 * 目前的调用方式为:spring,jar包,http接口。所以预计有三个具体工厂类。如果以后出现新的调用方式,实现此接口即可
 * 具体工厂类一定要穿件在:com.bj58.daojia.test.InterfaceTool.data.classFactory这个包中。
 * 
 * @author Gaofei Sun
 *
 */
public interface IInvokeObjectFactory {
    public Object getInvokeObject(String type);
}
}

这个接口是所有算法的基类,调用方只知道这个接口,针对这个接口编程。每个字类其实都是个工厂类,责任就是给框架创建被测的接口对象,根据不同的创建方式有不同的实现,我们把每个实现都看作一个算法。下面我们看看通过 Spring 管理 dubbo 协议的实现

/**
 * 具体工厂类:使用spring获取待测对象
 * 
 * @author Gaofei Sun
 *
 */
@Component
public class Spring implements IInvokeObjectFactory {
    public static String zkAddress = "zookeeper://192.168.120.88:2181";
    // BeanFactory beanFactory = null;

    public Spring() {
        // beanFactory = new ClassPathXmlApplicationContext("./spring-dubbo.xml");
    }

    @Override
    public Object getInvokeObject(String type) {
        try {
            System.out.println();
            Class temp = Class.forName(type);
            return getInvokeObject("daojia_services_interface", temp);
        } catch (Exception e) {
            e.printStackTrace();
            Assert.assertTrue("Sping获取目标对象失败,请检对象名称是否正确,或配置文件是否正确。传入的对象名:" + type, false);
        }
        return null;
    }
private static Object getInvokeObject(String group, Class interfaceClass) {
        try {
            ApplicationConfig application = new ApplicationConfig();
            application.setName("dj-card-client");

            RegistryConfig registry = new RegistryConfig();
            registry.setAddress(zkAddress);
            registry.setGroup(group);
            // System.out.println("registry build completed.");

            ReferenceConfig reference = new ReferenceConfig();
            reference.setApplication(application);
            reference.setRegistry(registry); // 多个注册中心可以用setRegistries()
            reference.setInterface(interfaceClass);
            reference.setVersion("1.0.0");
            reference.setCheck(false);
            // System.out.println("reference build completed");
            Object obj = null;
            try {
                obj = reference.get();
            } catch (Exception e) {
                System.out.println("error:" + e.getMessage());
            } finally {
                // System.out.println("finally." + "houseLeaveService:");
            }
            return obj;
        } catch (Exception e) {
            e.printStackTrace();
            Assert.assertTrue("Sping获取目标对象失败,请检对象名称是否正确,或配置文件是否正确。传入的对象名:" + interfaceClass.toString(), false);
        }
        return null;
    }

上面是我们当初通过 Spring 去获取 dubbo 协议的对象的实现,可以看到代码不算少吧。我们还有 HTTP 的协议,SCF 协议(公司自主研发的协议),SF 协议(同样是公司自主研发的),每一种协议都需要一些代码去获取对象, 我们想象一下每次用 if else 管理的话得多乱.以后每次来换新的协议你都跑来改代码,加个 if 分支也是挺痛苦的 . 我们有了接口,有了实现. 现在我们再用一个工厂类来生产这些算法。

/**
 * 创建带测接口对象工厂的工厂类 
 * @author Gaofei Sun
 *
 */
public class InvokeObjectFactoryFactory {

    private static ConcurrentHashMap<String, IInvokeObjectFactory> map = new ConcurrentHashMap<String, IInvokeObjectFactory>();

    public static IInvokeObjectFactory getInvokeObjectFactory(String type) {
        String temp = convertStringTo1lower(type);
        return (IInvokeObjectFactory) SpringContext.getBean(temp);
    }

上面的代码很简单,由于我后来把对象都交给 spring 管理了,所以我们创建算法对象的话,连 if else 也不用了。直接传一个 name 进来用 spring 来为客户端创建算法。于是乎客户端就变成了这样。

// 获取待测接口对象的工厂长对象
IInvokeObjectFactory factory = InvokeObjectFactoryFactory.getInvokeObjectFactory(info.getInvokeType());
Object invokeObject = factory.getInvokeObject(info.getClassName());

OK,看到了吧,客户端不清楚算法具体怎么实现的,也不知道是在调用哪个算法,反正我只需要知道这些算法的接口就行了。 传递一个 算法名称 就可以创建算法了,而算法名称其实也是外部传递进来的。所以实际上客户端除了这个接口以外,什么都不需要知道(知道的最少原则)。以上的例子是使用了 spring 的 bean 工厂, 工厂模式策略模式(如果没有大家没有使用 spring 的话,自己写享元模式或者用 java 反射也是可以的)。想想看这是不是比用 if else 管理大篇的代码方便多了。扩展起来也很容易。不必修改原来的代码。只需要新加一个算法就可以。 符合面向对象的 开闭原则

可插拔式设计

用过 testng 的或者 tomcat 的人一定对这种设计不陌生,tomcat 的配置文件里准许用户自定义 connector 然后注册进 tomcat 里代替默认的。testng 也可以自己实现监听器然后注册进 testng 中用以定制化 report。据我所知 QTP 的关键字驱动框架中也有自定义关键字的的功能(没用过,听同事说的)。下面看一下 testng 的例子。

<listeners>
        <listener class-name="InterfaceTool.testListener.ReporterListener"></listener>
        <listener class-name="InterfaceTool.testListener.TestListener"></listener>
        <listener class-name="InterfaceTool.testListener.SuiteListener"></listener>
        <!--  
        <listener class-name="com.bj58.daojia.test.InterfaceTool.testListener.RetryListener"></listener>
        -->
</listeners>

可以看到上面就是 testng 的可插拔机制。我自己实现一个 listener 然后注册进来。这样 testng 使用的就是我的 ReporterListener 生成测试报告了。这些都是可插拔式的设计,基于控制反转的思想,我们定义了好了行为,控制实现留给用户扩展。 有时候这种设计可能很有必要,因为可能你写的框架在一个公司里有很多业务线在用,你无法预测出所有的情况,所以干脆留个接口让他们扩展。或者你也有关键字驱动的框架,你需要能让用户有能力自定义自己的关键字。所以这种设计就变得很有必要。下面我们说说具体的实现原理。通过帖子最开始的那个例子,我们现在已经把算法抽象出来了,留下了一个接口。所有的算法都实现这个接口,我们的客户端也是面向这个接口编程的,也就是说,你是用什么算法我不关心,只要你的算法实现了这个接口就行了。也有一个专门的工厂类来帮客户端创建这些算法。如果像 tomcat 或者是 testng 这种算法是唯一的,也就是只能使用一种算法。那么其实这个工厂类也就不用参数了。如果是关键字驱动这种有很多算法的,我们创建算法的工厂类就需要一个 name 来做参数,判断创建哪个算法。然后我们在运行时读取测试人员编写的用例中使用的哪种关键字当作参数就可以了。例如下面的例子:

<methods url="api/model/deleteModel" httptype="post" invokeType="http" verifyMethod="">

可以看到上面的 xml 文件就是测试人员要填写的。中间的那个 invokeType 就是说我用哪种方式获取接口。这种 把决定使用哪种实现的权利交给用户的机制,很好的体现了控制反转的思想,同时我们编程的时候也完全不用考虑到底是在用哪个算法,这样就充分的解耦了。那么现在我们其实还需要一种注册方式,把用户自己定义的方法注册进框架里。如果测试框架和项目都使用 spring 管理的话就简单了,像第一个例子一样,直接交给 bean 工厂管理就行了。写算法的时候一个@component注解就行了。如果大家不是用 spring 管理的,那么就需要使用 享元模式 加 java 反射来搞这个事情了。 首先我们搞一个 XML 的配置文件,定一个标签让用户去注册算法,就像上面 testng 的做法一样。然后我们在帖子第一个例子中的创建算法的工厂类中,就要像下面这么写 (其实就是个 享元模式 )

public class InvokeObjectFactoryFactory {

    private static ConcurrentHashMap<String, IInvokeObjectFactory> map = new ConcurrentHashMap<String, IInvokeObjectFactory>();

    public static IInvokeObjectFactory getInvokeObjectFactory(String path,String name)

        if (map.containsKey(name)) {
            return map.get(name);
        } else {
            IInvokeObjectFactory factory = null;
            try {
                factory = (IInvokeObjectFactory) Tools
                        .reflectObject(path);
                map.putIfAbsent(factory , name);
            } catch (Exception e) {
                e.printStackTrace();
                Assert.assertTrue("无法生成对应的调用方式:" + path+ " 请检查是否调用方式输入错误", false);
            }

            return factory;
        }
    }

我们看到通过一个 name 和一个自己实现的算法的路径为参数。 是用 java 反射的方式创建这个路径的算法,然后用 name 为 key 存到一个 map 中。以后其他模块调用算法的时候,就会去这个 map 里获取算法了。这样我们就把自定义的算法注册到了框架里。

总结

我们来说说这样做的好处。其实最大的好处就是 扩展性维护性 强,以后新加算法的时候我们随便在哪实现预先定义的算法接口然后注册进框架就行了。 我们不需要改动哪怕一行原来的框架代码。相比我们用 if else 去 hold 住所有情况的方式,加一个算法你就要跑去框架里相应的位置去加一段 if 分支的方式实在是痛苦之极,尤其是你需要对你原来的框架作不小的改动。而这个改动可能只适应你公司的一条业务线,不同的业务可能需要不同的算法。而每出现一个新算法业务线的人都跑来让你加进去你说你烦不烦。举个例子你是做关键字驱动框架的, 那么每个关键字都必然是你来写了,因为每个测试人员想要定义一个新关键字的时候都跑来找你写(他们自己写不了,框架代码他们也看不懂,就算他们知道了在哪里加 if 分支在哪里加算法,你敢让别人随便动你的框架么?),而且这种一换项目就会废弃掉的算法在你的框架里呆着也是不妥的。假如你跳槽了,你说这些算法你是删还是不删。还是退一万步讲,一旦你离开了这个岗位。接手这个框架的人真得通读一遍框架代码才知道怎么扩展关键字了。如果假如这个框架还没文档没注释的。。。那他会不会疯掉。。。

所以我十分鼓励设计可插拔式的框架,准许用户自定义实现他们想做的事情。
注意:文章中所有的设计方式不仅仅在关键字驱动中才有用。很多地方你都可以用到它 。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 14 条回复 时间 点赞

太厉害了。。

#1 楼 @mads 额。。。谢谢夸奖~

可插拔式设计 赞

给你 720 个赞,让你全方位骄傲

—— 来自 TesterHome 官方 安卓客户端

读了很多遍才读懂,写的非常棒,期待其他类似的分享~~

大话设计模式

delicious! 这个系列要写下去,持续关注中。

#8 楼 @jet 多谢支持,你给了我动力哈哈

#6 楼 @airfer 多谢,我会努力的

@ycwdaaaa ,感觉是工厂模式就能解决的问题,不知道理解的对不对?

孙高飞 [該主題已被刪除] 中提及了此贴 06月28日 18:51
孙高飞 [該主題已被刪除] 中提及了此贴 06月28日 18:51
孙高飞 [該主題已被刪除] 中提及了此贴 06月28日 18:51
孙高飞 测试开发之路 ---- 框架中数据的管理策略 中提及了此贴 12月02日 10:47
孙高飞 测试开发之路 ---- 数据驱动及其变种 中提及了此贴 12月10日 11:45

看了一遍,看不懂,飞哥😂 😂 😂

看了一遍,看不懂,飞哥😂 😂 😂 ,飞哥厉害啊

孙高飞 测试开发之路----概要 中提及了此贴 03月13日 13:13
22楼 已删除
ABEE ycwdaaaa (孙高飞) 在 TesterHome 的发帖整理 中提及了此贴 01月12日 13:47

😎 赞同作者的说法,设计模式在框架设计中用的很多,尤其是开闭原则,框架不支持扩展几乎就废了,所有扩展都需要作者自己来~之前有幸和你们前 58 员工共事,哈哈哈赞一个~ 顺便推荐 <<设计模式之禅>> 这本书,通俗易懂我是看了三四遍了

需要 登录 後方可回應,如果你還沒有帳號按這裡 注册