You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

267 lines
14 KiB
Markdown

2 years ago
# 20链接工具要驱动工具不要被工具驱动
你好,我是柳胜。
在上一讲的微测试Job设计方法论里我们把工具打入了“地牢”但你可能还是对它念念不忘。毕竟设计开发的最后一公里路Job模型还是要转换成一个具体工具的自动化测试案例。
战略上我们都不愿束缚思想被特定工具牢牢“绑票”而战术上又要用好工具。因此如何驱动工具实现Job模型这个问题我们就必须解决要是做不到我们就不得不走回老路看着那些工具稳坐C位。
我们不想看到这种情况,所以这一讲,我们继续深挖后面这两个问题:
1.主流的工具框架能不能被驱动?
2.这些工具框架怎么和微测试Job模型对接执行Test Job
解决完这些问题微测试Job模型就算是可以落地了。
## Job模型往哪里放
要想让Job模型落地我们首先要找准它应该落在哪个地方。尤其在业界自动化测试工具和技术可谓层出不穷、眼花缭乱。我们势必要理清它们之间的关系是什么才能知道Job模型应该放在哪里。
按功能效用我把各种自动化测试技术划分成了三个层面框架层、工具层和Library层。
先来看**框架层**,这一层负责自动化测试的设计。其实它主要回答了设计的三个问题:测什么、怎么运行、结果是什么。
问题相同解法各异。不同的测试理念最终催生了面向这三个问题的不同答案比如TDD、BDD、ATDD。我画了一张表格帮你更直观地对比它们
![图片](https://static001.geekbang.org/resource/image/d2/c9/d2a62a5c69599c65aac7d058bb443bc9.jpg?wh=1920x607)
第二个层面就是**工具层**,这一层负责自动化测试的实现,把测试任务转换成代码,用各种工具跟被测试对象打交道。
比如跟Web对象打交道的工具有Selenium、QTP跟Windows对象打交道的工具有WinAppDriver等跟API打交道的工具有SoapUI和RestAssure等。所以工具的能力主要体现在对象的识别和控制能力上。
第三是**Library层**主要提供了自动化测试运行的支持库。比如Mock库Assert库等等。
我把业界一些常用工具,按照三层来做了一个归类,如下图,你这样看会更清楚一些。
![图片](https://static001.geekbang.org/resource/image/e6/39/e68484363dc5754d1bfd7b2ef76ef739.jpg?wh=1920x785)
以TDD为例这三个层面的调用链条是这样的
![图片](https://static001.geekbang.org/resource/image/d0/c1/d07f99ff02791f2521311ab5183925c1.jpg?wh=1920x868)
显然Job模型应该要放在框架层。我给它起了个名字JDD加在这个表格里。
![图片](https://static001.geekbang.org/resource/image/cd/yc/cdbfca934a07120df99050d4ae9aeyyc.jpg?wh=1920x590)
与TDD对比JDD的实现会是这样
![图片](https://static001.geekbang.org/resource/image/b1/8c/b1324f2c6e890d0949198d0607ff3a8c.jpg?wh=1920x868)
在这个分工图里JDD和TDD起到一样的功能首先遍历找出所有的测试案例计算出执行路径交给工具层去执行。
在TDD里对于TestRunner这两件事都比较简单测试案例都有注解执行路径一个个顺序执行就可以了但在JDD里对于JobRunner要找出Job树的所有叶子结点根据依赖关系计算出执行路径。这件事涉及到算法和实现。我们需要进一步理一下该怎么做。
## Job模型怎么实现
我们在把自动化测试技术进行分层后从设计到实现现在各个环节的分工更加清楚了。但实际应用中你会发现这个层面是模糊的。有的工具遵循了框架和工具分离比如像SeleniumRestAssure这些工具它们可以和Junit集成、也可以和TestNG集成而有的工具比如QTP、Squish直接提供了一揽子解决方案从框架到工具到检查点都是玩自己的一套。
如果你的团队正在使用多个工具又想要落地JDD那就要理清这些工具的接口层面然后才能用JDD来驱动它们。
### JobRunner的实现
以Junit为例我们先看看现在最常用的TestRunner是怎么实现的
测试人员要在Test Class里给method上加@Test注解表示这个方法就是一个TestCase。
```java
public class OrderTest{
@Test
public void testCreateOrder1(){
//test logic 1
}
@Test
public void testCreateOrder2(){
//test logic 2
}
}
```
TestRunner的处理逻辑很简单就是通过Java Reflection从Test Class里获得所有加了@Test注解的方法每一个方法就是一个TestCase运行方法就是运行TestCase。
```java
public class TestRunner extends Runner {
private Class testClass;
public TestRunner(Class testClass) {
super();
this.testClass = testClass;
}
@Override
public Description getDescription() {
return Description
.createTestDescription(testClass, "My runner description");
}
@Override
public void run(RunNotifier notifier) {
try {
Object testObject = testClass.newInstance();
for (Method method : testClass.getMethods()) {
//判断方法是否加了@Test的注解
if (method.isAnnotationPresent(Test.class)) {
notifier.fireTestStarted(Description
.createTestDescription(testClass, method.getName()));
//调用方法运行
method.invoke(testObject);
notifier.fireTestFinished(Description
.createTestDescription(testClass, method.getName()));
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
```
而微测试Job模型设计出来的Job用一个XML文件表达出来是这样的。
```xml
<TestJob name="FooJob">
<TestJob name="TestJob1">
<TestJob name="TestJob11"/>
<TestJob name="TestJob12" depends="TestJob11"/>
<TestJob name="TestJob13" depends="TestJob11"/>
</TestJob>
<TestJob name="TestJob2" depends="TestJob1">
<TestJob name="TestJob21"/>
<TestJob name="TestJob22"/>
</TestJob>
</TestJob>
```
为了让你看得更清楚,我又画了个树状图,这样形象一点。如图所示,它是这样一个树结构,在同一个父节点下的子节点之间,可以有依赖关系。
![图片](https://static001.geekbang.org/resource/image/ff/6b/ff85a51b332a9456d09dd9696ab05c6b.jpg?wh=1920x1102)
那我们要运行FooJob该怎么运行呢
你可以看到要想运行FooJob实际上是运行它的两个子节点TestJob1和TestJob2。但是TestJob2依赖于TestJob1所以我们就得先运行TestJob1再运行TestJob2。运行TestJob1的策略又和运行FooJob的策略完全一样递归下去把TestJob1这棵子树运行完。TestJob2也按照同样的策略运行完。这时FooJob就执行完返回最终执行结果了。
运行FooJob其实就是一个按照一定策略去递归遍历FooJob树的过程。这个过程类似于树的深度优先DFS的前序遍历算法只不过左右节点是靠依赖关系确定的没有前置依赖的节点是最左节点然后按照依赖关系从左到右排列。
JobRunner的伪代码实现如下
```java
public class JobRunner{
private File jobXMLFile;
public TestRunner(File jobXMLFile) {
super();
this.jobXMLFile = jobXMLFile;
}
public void run() {
try {
TestJob testJob = TestJob.loadFile(jobXMLFile);
for(TestJob childJob:testJob.getChildJobs()){
//当前job有依赖Job
if(childJob.getDepends()!=null){
//先运行依赖Job
TestJob depdendedJob = childJob.getDepends();
dependedJob.run();
}else{
//当有Job没有依赖Job可以运行它了
childJob.run();
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
```
JobRunner对于上面FooJob的运行顺序如下
FooJob->TestJob1->TestJob11->TestJob12->TestJob13->TestJob2->TestJob21->TestJob22
JobRunner搞定了自动化测试设计人员只管往Job树里加减JobJobRunner会帮你理清执行的计划。如果你给每个Job增加一个权值JobRunner甚至帮你理出一个冒烟执行链。这个怎么实现我留给你课后思考后面我们留言区里交流。
### Job的Input和Output
说完了JobRunner我们再来看看输入和输出这是Job模型和TestCase的另外一个重要区别。因为Job运行之前会从外接收数据运行之后会向外输出数据。在这个过程中它只关心自己的逻辑和对外的承诺并不需要关心谁会使用它的Output。
你可以想象是这样的一个场景有一条像传送带一样的数据通道Job可以从这个数据通道上取东西也可以放东西。
![图片](https://static001.geekbang.org/resource/image/28/c0/28898e8ce1f6fdda6b7406e3125378c0.jpg?wh=1920x693)
**要实现Job Input和Output机制核心就是实现这个数据通道。**
因为能提供数据通道功能的载体有很多种这块我鼓励你广开思路可能你先想到的就是Hashmap那数据通道可以是内存里的一张Hashmap对象。数据都是Key Value方式存储在Hashmap里读取Input就是Map.getValue(Key)写入Output就是Map.put(Key,Value)。
这种方式的优点是简单缺点是它有一个前提就是所有的Job都在一个进程里。
如果我们想跨进程来读取Input和Output可以采用最常见的文件方式。把数据放到文件系统的一个txt文件里读取Input就是读文件操作写入Output就是写文件操作。但文件方式也有局限一个局限是并发写入需要引入文件锁的机制另外一个局限是本地文件也没办法应对分布式跨主机的Job。
当然最强大的解决方案就是Message Queue启动一个Message Queque的Broker服务RabbitMQ或者KafkaJob通过Queue Client来操作Queue里的消息。
总之,方案多多,所以我想给你的选择建议是,基于我们专栏的观点“做性价比最高的自动化测试”,不要过度工作,追求强大。强大的另一面就是复杂。所以,寻找合适、够用的方案就可以。
### 驱动工具
现在我们看剩下的最后一个问题Job模型怎么驱动工具
具体包括两种情况。第一种对于那些和框架解耦的工具像SeleniumRestAssure它们的TestCase不需要变我们只需更换调用框架就可以了。用自定义的JobRunner来替代传统的TestRunner然后提供数据Channel的API用来操作Input和Output整个Job模型驱动就运转起来了。
```java
public class seleniumLoginTest{
String username="";
String password="";
@setup
public void setUp(){
//从DataChannel里获得input
username = DataChannel.getInput("username");
password = DataChannel.getInput("password")
}
@Test
public void login(){
//执行登录
}
@TearDown
public void tearDown(){
DataChannel.output("output1","outputValue1");
DataChannel.output("output2","outputValue2");
}
}
```
另外一种情况对于那些和框架深度绑定的工具比如QTP、Squish这个时候我们要切割出它们的框架面和工具面。让工具面和新的JDD框架进行对接。
这个时候你可以找它的驱动接口去执行Job。比如QTP提供了Automation Object Model
```plain
' A Sample Script to Demostrate AOM
Dim App 'As Application
Set App = CreateObject("QuickTest.Application")
App.Launch
App.Visible = True
App.loadTest("/user/sheng/myQTP/script")
App.run()
App.close
```
详细的接口信息你可以从工具的官方网站得到比如QTP的接口信息你可以查看[这个链接](https://admhelp.microfocus.com/uft/en/all/AutomationObjectModel/Content/AutomationIntro/Output/AutoObjModel.htm)。
## 小结
现在我们总结一下。现在常用的TestCase方式对自动化测试设计能力的支持非常有限它是一种弱设计。而Job模型则让自动化测试的设计更加强大丰富。
Job模型怎么落地呢我们先把业界的自动化测试实现理念捋了一遍分成了框架层、工具层和通用库这样就清楚多了。Job模型落地在框架层需要实现自己的JobRunner对Job树进行遍历生成执行路径。
![图片](https://static001.geekbang.org/resource/image/b1/8c/b1324f2c6e890d0949198d0607ff3a8c.jpg?wh=1920x868)
另外Job的Input和Output是Job之间的重要交互方式我们也找出了三种实现方法分别是Hashmap文件和Message Queue。
在Job模型下自动化测试层次更加清楚设计和实现的分工也更加清晰明确。
设计的产出结果是整理出Job树Job树的表现形式是一个XML、Yaml或者Json文件这就相当于开发用Swagger来表达OpenAPI的设计而到了自动化测试实现阶段才是工具登场的时候利用工具实现Job模型就像是开发去实现Swagger定义的API一样。这样就达到了分层清楚驱动工具的效果。
后面三讲内容我们就利用Job模型来讲解更多的实践案例下一讲先从一个最简单的金融交易自动化测试开始金融业务的特点是复杂精准度要求高Job模型能不能理清复杂的金融业务呢敬请期待。
## 思考题
你的自动化测试设计目前是怎么做的?是以什么方式输出这个设计方案的?
欢迎你在留言区跟我交流讨论,也推荐你把这一讲分享给更多朋友。