qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

iOS单元测试框架Kiwi for iOS

 Kiwi是一个适用于iOS开发的行为驱动开发(BDD)库,因其接口简单而高效,深受开发者的欢迎,也因此,成为了许多开发新手的首选测试平台。和大多数iOS测试框架一样,Kiwi使用Objective-C语言编写,因此对于iOS开发者而言,绝对称得上是最佳测试拍档。
  示例代码:
describe(@"Team", ^{
context(@"when newly created", ^{
it(@"should have a name", ^{
id team = [Team team];
[[team.name should] equal:@"Black Hawks"];
});
it(@"should have 11 players", ^{
id team = [Team team];
[[[team should] have:11] players];
});
});
});

posted @ 2014-12-23 00:21 顺其自然EVO 阅读(743) | 评论 (0)编辑 收藏

移动应用测试框架—AppGrader(Android)

  AppGrader是来自以色列的应用测试服务商uTest推出的一款测试产品。相比其他主流移动应用测试框架,AppGrader可能并不太为开发者所熟知,但它却能够为众多的Android开发者提供非常专业的意见参考。
  通过AppGrader,开发者可以将自己所开发的应用与其他同类应用就图形、功能及其他方面进行比较,从而对应用进行改善。据悉,继AppGrader for Android之后,uTest还将推出AppGrader for iOS。

posted @ 2014-12-23 00:20 顺其自然EVO 阅读(484) | 评论 (0)编辑 收藏

移动应用测试框架—Cedar(iOS)

 和Kiwi一样,Cedar也是一款BDD风格的Objective-C测试框架。它不仅适用于iOS和OS X代码库,而且在其他环境下也可以使用。
  Kiwi、Specta、Expecta以及Cedar都可以通过CocoaPods添加到你的项目中。

posted @ 2014-12-23 00:19 顺其自然EVO 阅读(452) | 评论 (0)编辑 收藏

Appium Android Bootstrap之控件AndroidElement

通过上一篇文章Appium Android Bootstrap源码分析之简介》我们对bootstrap的定义以及其在appium和uiautomator处于一个什么样的位置有了一个初步的了解,那么按照正常的写书的思路,下一个章节应该就要去看bootstrap是如何建立socket来获取数据然后怎样进行处理的了。但本人觉得这样子做并不会太好,因为到时整篇文章会变得非常的冗长,因为你在编写的过程中碰到不认识的类又要跳入进去进行说明分析。这里我觉得应该尝试吸取著名的《重构》这本书的建议:一个方法的代码不要写得太长,不然可读性会很差,尽量把其分解成不同的函数。那我们这里就是用类似的思想,不要尝试在一个文章中把所有的事情都做完,而是尝试先把关键的类给描述清楚,最后才去把这些类通过一个实例分析给串起来呈现给读者,这样大家就不会因为一个文章太长影响可读性而放弃往下学习了。
  那么我们这里为什么先说bootstrap对控件的处理,而非刚才提到的socket相关的socket服务器的建立呢?我是这样子看待的,大家看到本人这篇文章的时候,很有可能之前已经了解过本人针对uiautomator源码分析那个系列的文章了,或者已经有uiautomator的相关知识,所以脑袋里会比较迫切的想知道究竟appium是怎么运用了uiautomator的,那么在appium中于这个问题最贴切的就是appium在服务器端是怎么使用了uiautomator的控件的。
  这里我们主要会分析两个类:
  AndroidElement:代表了bootstrap持有的一个ui界面的控件的类,它拥有一个UiObject成员对象和一个代表其在下面的哈希表的键值的String类型成员变量id
  AndroidElementsHash:持有了一个包含所有bootstrap(也就是appium)曾经见到过的(也就是脚本代码中findElement方法找到过的)控件的哈希表,它的key就是AndroidElement中的id,每当appium通过findElement找到一个新控件这个id就会+1,Appium的pc端和bootstrap端都会持有这个控件的id键值,当需要调用一个控件的方法时就需要把代表这个控件的id键值传过来让bootstrap可以从这个哈希表找到对应的控件
  1. AndroidElement和UiObject的组合关系
  从上面的描述我们可以知道,AndroidElement这个类里面拥有一个UiObject这个变量:
  public class AndroidElement {
  private final UiObject el;
  private String         id;
  ...
  }
  大家都知道UiObject其实就是UiAutomator里面代表一个控件的类,通过它就能够对控件进行操作(当然最终还是通过UiAutomation框架). AnroidElement就是通过它来跟UiAutomator发生关系的。我们可以看到下面的AndroidElement的点击click方法其实就是很干脆的调用了UiObject的click方法:
  public boolean click() throws UiObjectNotFoundException {
  return el.click();
  }
  当然这里除了click还有很多控件相关的操作,比如dragTo,getText,longClick等,但无一例外,都是通过UiObject来实现的,这里就不一一列举了。
  2. 脚本的WebElement和Bootstrap的AndroidElement的映射关系
  我们在脚本上对控件的认识就是一个WebElement:
  WebElement addNote =  driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");
  而在Bootstrap中一个对象就是一个AndroidElement. 那么它们是怎么映射到一起的呢?我们其实可以先看如下的代码:
  WebElement addNote = driver.findElementByAndroidUIAutomator("new UiSelector().text(\"Add note\")");
  addNote.getText();
  addNote.click();
  做的事情就是获得Notes这个app的菜单,然后调用控件的getText来获得‘Add note'控件的文本信息,以及通过控件的click方法来点击该控件。那么我们看下调试信息是怎样的:
  3. AndroidElement控件哈希表
  上一节我们说到appium pc端是通过id把WebElement和目标机器端的AndroidElement映射起来的,那么我们这一节就来看下维护AndroidElement的这个哈希表是怎么实现的。
  首先,它拥有两个成员变量:
  private final Hashtable<String, AndroidElement> elements;
  private       Integer                           counter;
  elements :一个以AndroidElement 的id的字串类型为key,以AndroidElement的实例为value的的哈希表
  counter : 一个整型变量,有两个作用:其一是它代表了当前已经用到的控件的数目(其实也不完全是,你在脚本中对同一个控件调用两次findElement其实会产生两个不同id的AndroidElement控件),其二是它代表了一个新用到的控件的id,而这个id就是上面的elements哈希表的键
  这个哈希表的键值都是从0开始的,请看它的构造函数:
  /**
  * Constructor
  */
  public AndroidElementsHash() {
  counter = 0;
  elements = new Hashtable<String, AndroidElement>();
  }
  而它在整个Bootstrap中是有且只有一个实例的,且看它的单例模式实现:
  public static AndroidElementsHash getInstance() {
  if (AndroidElementsHash.instance == null) {
  AndroidElementsHash.instance = new AndroidElementsHash();
  }
  return AndroidElementsHash.instance;
  }
  以下增加一个控件的方法addElement充分描述了为什么说counter是一个自增加的key,且是每个新发现的AndroidElement控件的id:
  public AndroidElement addElement(final UiObject element) {
  counter++;
  final String key = counter.toString();
  final AndroidElement el = new AndroidElement(key, element);
  elements.put(key, el);
  return el;
  }
  从Appium发过来的控件查找命令大方向上分两类:
  1. 直接基于Appium Driver来查找,这种情况下appium发过来的json命令是不包含控件哈希表的键值信息的
  WebElement addNote = driver.findElement(By.name("Add note"));
  2. 基于父控件查找:
  WebElement el = driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));
  以上的脚本会先尝试找到Note1这个日记的父控件ListView,并把这个控件保存到控件哈希表,然后再根据父控件的哈希表键值以及子控件的选择子找到想要的Note1:
AndroidElementHash的这个getElement命令要做的事情就是针对这两点来根据不同情况获得目标控件
/**
* Return an elements child given the key (context id), or uses the selector
* to get the element.
*
* @param sel
* @param key
*          Element id.
* @return {@link AndroidElement}
* @throws ElementNotFoundException
*/
public AndroidElement getElement(final UiSelector sel, final String key)
throws ElementNotFoundException {
AndroidElement baseEl;
baseEl = elements.get(key);
UiObject el;
if (baseEl == null) {
el = new UiObject(sel);
} else {
try {
el = baseEl.getChild(sel);
} catch (final UiObjectNotFoundException e) {
throw new ElementNotFoundException();
}
}
if (el.exists()) {
return addElement(el);
} else {
throw new ElementNotFoundException();
}
}
  如果是第1种情况就直接通过选择子构建UiObject对象,然后通过addElement把UiObject对象转换成AndroidElement对象保存到控件哈希表
  如果是第2种情况就先根据appium传过来的控件哈希表键值获得父控件,再通过子控件的选择子在父控件的基础上查找到目标UiObject控件,最后跟上面一样把该控件通过上面的addElement把UiObject控件转换成AndroidElement控件对象保存到控件哈希表
  4. 求证
  上面有提过,如果pc端的脚本执行对同一个控件的两次findElement会创建两个不同id的AndroidElement并存放到控件哈希表中,那么为什么appium的团队没有做一个增强,增加一个keyMap的方法(算法)和一些额外的信息来让同一个控件使用不同的key的时候对应的还是同一个AndroidElement控件呢?毕竟这才是哈希表实用的特性之一了,不然你直接用一个Dictionary不就完事了?网上说了几点hashtable和dictionary的差别,如多线程环境最好使用哈希表而非字典等,但在bootstrap这个控件哈希表的情况下我不是很信服这些说法,有谁清楚的还劳烦指点一二了
  这里至于为什么appium不去提供额外的key信息并且实现keyMap算法,我个人倒是认为有如下原因:
  有谁这么无聊在同一个测试方法中对同一个控件查找两次?
  如果同一个控件运用不同的选择子查找两次的话,因为最终底层的UiObject的成员变量UiSelector mSelector不一样,所以确实可以认为是不同的控件
  但以下两个如果用同样的UiSelector选择子来查找控件的情况我就解析不了了,毕竟在我看来bootstrap这边应该把它们看成是同一个对象的:
  同一个脚本不同的方法中分别对同一控件用同样的UiSelelctor选择子进行查找呢?
  不同脚本中呢?
  这些也许在今后深入了解中得到解决,但看家如果知道的,还望不吝赐教
  5. 小结
  最后我们对bootstrap的控件相关知识点做一个总结
  AndroidElement的一个实例代表了一个bootstrap的控件
  AndroidElement控件的成员变量UiObject el代表了uiautomator框架中的一个真实窗口控件,通过它就可以直接透过uiautomator框架对控件进行实质性操作
  pc端的WebElement元素和Bootstrap的AndroidElement控件是通过AndroidElement控件的String id进行映射关联的
  AndroidElementHash类维护了一个以AndroidElement的id为键值,以AndroidElement的实例为value的全局唯一哈希表,pc端想要获得一个控件的时候会先从这个哈希表查找,如果没有了再创建新的AndroidElement控件并加入到该哈希表中,所以该哈希表中维护的是一个当前已经使用过的控件
相关文章:
Appium Android Bootstrap源码分析之简介

posted @ 2014-12-23 00:18 顺其自然EVO 阅读(623) | 评论 (0)编辑 收藏

iOS应用功能测试框架 KIF概述

  KIF的全称是Keep It Functional,来自Square,是一款专为iOS设计的移动应用测试框架。由于KIF是使用Objective-C语言编写的,因此,对于iOS开发者而言,用起来要更得心应手,可以称得上是一款非常值得收藏的iOS测试利器。
  KIF最酷的地方是它是一个开源的项目,且有许多新功能还在不断开发中。例如下一个版本将会提供截屏的功能并且能够保存下来。这意味着当你跑完测试之后,可以在你空闲时通过截图来查看整个过程中的关键点。难道这不是比鼠标移动上去并用肉眼观察KIF点击和拖动整个过程好上千倍万倍么?KIF变得越来越好了,所以学习如何使用它,对于自己来说是一个很好的投资。
  由于KIF测试用例是继承了OCUnit,并使用了标准的Xcode5测试框架,你可以使用持续集成来跑这个测试。当你在忙着别的事情的时候,就拥有了一个能够像人的手指一样准点触控的机器人去测试你的应用程序。太棒了!

posted @ 2014-12-23 00:17 顺其自然EVO 阅读(384) | 评论 (0)编辑 收藏

移动应用测试框架—Calabash Android 简介

什么是 Calabash
  Calabash 是一个自动化测试框架,它可以测试 Android 和 iOS 原生应用和混合应用。
  它有:
  calabash-android
  calabash-ios
  主页: http://calabash.sh
  Calabash-android介绍
  Calabash-android 是支持 android 的 UI 自动化测试框架,PC 端使用了 cucumber 框架,通过 http 和 json 与模拟器和真机上安装的测试 apk 通信,测试 apk 调用 Robotium 的方法来进行 UI 自动化测试,支持 webview 操作。
  Calabash-android 架构图
  Features —— 这里的 feature 就是 cucumber 的 feature,用来描述 user stories 。
  Step Definitions —— Calabash Android 事先已经定义了一些通用的 step。你可以根据自己的需求,定义更加复杂的步骤。
  Your app —— 测试之前,你不必对你的应用修改。(这里其实是有问题,后面我们会说到。)
  Instrumentation Test Server —— 这是一个应用,在运行测试的时候会被安装到设备中去。 这个应用是基于 Android SDK 里的 ActivityInstrumentationTestCase2。它是 Calabash Android 框架的一部分。Robotium 就集成在这个应用里。
  Calabash-android 环境搭建
  ruby 环境
  rvm
  rbenv
  RubyInstaller.org for windows
  Android 开发环境
  JAVA
  Android SDK
  Ant
  指定 JAVA 环境变量, Android SDK 环境变量(ANDROID_HOME), Ant 加入到 PATH 中去。
  安装 Calabash-android
  gem install calabash-android
  sudo gem install calabash-android # 如果权限不够用这个。
  如有疑问,请参考: https://github.com/calabash/calabash-android/blob/master/documentation/installation.md
  创建 calabash-android 的骨架
  calabash-android gen
  会生成如下的目录结构:
  ?  calabash  tree
  .
  features
  |_support
  | |_app_installation_hooks.rb
  | |_app_life_cycle_hooks.rb
  | |_env.rb
  |_step_definitions
  | |_calabash_steps.rb
  |_my_first.feature
写测试用例
  像一般的 cucumber 测试一样,我们只要在 feature 文件里添加测试用例即可。比如我们测试 ContactManager.apk (android sdk sample 里面的, Appium 也用这个 apk)。
  我们想实现,
  打开这个应用
  点击 Add Contact 按钮
  添加 Contact Name 为 hello
  添加 Contact Phone 为 13817861875
  添加 Contact Email 为 hengwen@hotmail.com
  保存
 所以我们的 feature 应该是这样的:
  Feature: Login feature  Scenario: As a valid user I can log into my app    When I press "Add Contact"
  Then I see "Target Account"
  Then I enter "hello" into input field number 1    Then I enter "13817861875" into input field number 2    Then I enter "hengwen@hotmail.com" into input field number 3    When I press "Save"
  Then I wait for 1 second    Then I toggle checkbox number 1    Then I see "hello"
  这里 input field number 就针对了 ContactAdder Activity 中输入框。我现在这样写其实不太友好,比较好的方式是进行再次封装,对 DSL 撰写者透明。比如:
When I enter "hello" as "Contact Name"
step_definition
When (/^I enter "([^\"]*)" as "([^\"]*)"$/) do | text, target |
index = case target
when "Contact Name": 1
...
end
steps %{
Then I enter #{text} into input field number #{index}
}end
  这样 feature 可读性会强一点。
  运行 feature
  在运行之前,我们对 apk 还是得处理下,否则会遇到一些问题。
  App did not start (RuntimeError)
  因为calabash-android的client和test server需要通信,所以要在 AndroidManifest.xml 中添加权限:
  <uses-permission android:name="android.permission.INTERNET" />
  ContacterManager 代码本身的问题
  由于 ContacerManager 运行时候,需要你一定要有一个账户,如果没有账户 Save 的时候会出错。为了便于运行,我们要修改下。
  源代码地址在 $ANDROID_HOME/samples/android-19/legacy/ContactManager,大家自己去找。
  需要修改 com.example.android.contactmanager.ContactAdder 类里面的 createContactEntry 方法,我们需要对 mSelectedAccount 进行判断, 修改地方如下:
// Prepare contact creation request
//
// Note: We use RawContacts because this data must be associated with a particular account.
//       The system will aggregate this with any other data for this contact and create a
//       coresponding entry in the ContactsContract.Contacts provider for us.
ArrayList<ContentProviderOperation> ops = new ArrayList<ContentProviderOperation>();
if(mSelectedAccount != null ) {
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType())
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName())
.build());
} else {
ops.add(ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null)
.build());
}....
if (mSelectedAccount != null) {
// Ask the Contact provider to create a new contact
Log.i(TAG,"Selected account: " + mSelectedAccount.getName() + " (" +
mSelectedAccount.getType() + ")");
} else {
Log.i(TAG,"No selected account");
}
  代码修改好之后,导出 apk 文件。
  运行很简单:
  calabash-android run <apk>
  如果遇到签名问题,请用: calabash-android resign apk。
  可以看看我运行的情况:
?  calabash  calabash-android run ContactManager.apk
Feature: Login feature
Scenario: As a valid user I can log into my app                # features/my_first.feature:33135 KB/s (556639 bytes in 0.173s)3315 KB/s (26234 bytes in 0.007s)
When I press "Add Contact"                                   # calabash-android-0.4.21/lib/calabash-android/steps/press_button_steps.rb:17
Then I see "Target Account"                                  # calabash-android-0.4.21/lib/calabash-android/steps/assert_steps.rb:5
Then I enter "hello" into input field number 1               # calabash-android-0.4.21/lib/calabash-android/steps/enter_text_steps.rb:5
Then I enter "13817861875" into input field number 2         # calabash-android-0.4.21/lib/calabash-android/steps/enter_text_steps.rb:5
Then I enter "hengwen@hotmail.com" into input field number 3 # calabash-android-0.4.21/lib/calabash-android/steps/enter_text_steps.rb:5
When I press "Save"                                          # calabash-android-0.4.21/lib/calabash-android/steps/press_button_steps.rb:17
Then I wait for 1 second                                     # calabash-android-0.4.21/lib/calabash-android/steps/progress_steps.rb:18
Then I toggle checkbox number 1                              # calabash-android-0.4.21/lib/calabash-android/steps/check_box_steps.rb:1
Then I see "hello"                                           # calabash-android-0.4.21/lib/calabash-android/steps/assert_steps.rb:51 scenario (1 passed)9 steps (9 passed)0m28.304s
All pass!
  大家看到 gif 是 failed,是因为在模拟器上运行的。而上面全部通过的是我在海信手机上运行的。环境不一样,略有差异。
  总结
  本文是对 calabash-android 的一个简单介绍,做的是抛砖引玉的活。移动测试框架并非 Appium 一家,TesterHome 希望其他框架的话题也能热火起来。watch and learn!

posted @ 2014-12-23 00:15 顺其自然EVO 阅读(825) | 评论 (0)编辑 收藏

Appium Server源码分析之作为Bootstrap客户端

     摘要: Appium Server拥有两个主要的功能:  它是个http服务器,它专门接收从客户端通过基于http的REST协议发送过来的命令  他是bootstrap客户端:它接收到客户端的命令后,需要想办法把这些命令发送给目标安卓机器的bootstrap来驱动uiatuomator来做事情  通过上一篇文章《Appium Server 源码分析之启动运行Express http服务器》我们分...  阅读全文

posted @ 2014-12-23 00:14 顺其自然EVO 阅读(3494) | 评论 (0)编辑 收藏

移植MonkeyRunner的图片对比功能实现-Appium篇

 如果你的目标测试app有很多imageview组成的话,这个时候monkeyrunner的截图比较功能就体现出来了。而其他几个流行的框架如Robotium,UIAutomator以及Appium都提供了截图,但少了两个功能:
  获取子图
  图片比较
  既然Google开发的MonkeyRunner能盛行这么久,且它体功能的结果验证功能只有截屏比较,那么必然有它的道理,有它存在的价值,所以我们很有必要在需要的情况下把它相应的功能给移植到其他框架上面上来。
  经过本人前面文章描述的几个框架的源码的研究(robotium还没有做),大家可以知道MonkeyRunner是跑在PC端的,只有在需要发送相应的命令事件时才会驱动目标机器的monkey或者shell等。比如获取图片是从目标机器的buffer设备得到,但是比较图片和获取子图是从客户PC端做的。
  这里Appium工作的方式非常的类似,因为它也是在客户端跑,但需要注入事件发送命令时还是通过目标机器段的bootstrap来驱动uiatuomator来完成的,所以要把MonkeyRunner的获取子图已经图片比较的功能移植过来是非常容易的事情。
  但UiAutomator就是另外一回事了,因为它完全是在目标机器那边跑的,所以你的代码必须要android那边支持,所以本人在移植到UiAutomator上面就碰到了问题,这里先给出Appium 上面的移植,以方便大家的使用,至于UiAutomator和Robotium的,今后本人会酌情考虑是否提供给大家。
  还有就是这个移植过来的代码没有经过优化的,比如失败是否保存图片以待今后查看等。大家可以基于这个基础实现满足自己要求的功能
  1. 移植代码
  移植代码放在一个Util.java了工具类中:
public static boolean sameAs(BufferedImage myImage,BufferedImage otherImage, double percent)
{
//BufferedImage otherImage = other.getBufferedImage();
//BufferedImage myImage = getBufferedImage();
if (otherImage.getWidth() != myImage.getWidth()) {
return false;
}
if (otherImage.getHeight() != myImage.getHeight()) {
return false;
}
int[] otherPixel = new int[1];
int[] myPixel = new int[1];
int width = myImage.getWidth();
int height = myImage.getHeight();
int numDiffPixels = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) {
numDiffPixels++;
}
}
}
double numberPixels = height * width;
double diffPercent = numDiffPixels / numberPixels;
return percent <= 1.0D - diffPercent;
}
public static BufferedImage getSubImage(BufferedImage image,int x, int y, int w, int h)
{
return image.getSubimage(x, y, w, h);
}
public static BufferedImage getImageFromFile(File f) {
BufferedImage img = null;
try {
img = ImageIO.read(f);
} catch (IOException e) {
//if failed, then copy it to local path for later check:TBD
//FileUtils.copyFile(f, new File(p1));
e.printStackTrace();
System.exit(1);
}
return img;
}
这里就不多描述了,基本上就是基于MonkeyRunner做轻微的修改,所以叫做移植。而UiAutomator就可能需要大改动,要重现实现了。
 2. 客户端调用代码举例
packagesample.demo.AppiumDemo;
importstaticorg.junit.Assert.*;
importjava.awt.image.BufferedImage;
importjava.io.File;
importjava.io.IOException;
importjava.net.URL;
importjavax.imageio.ImageIO;
importlibs.Util;
importio.appium.java_client.android.AndroidDriver;
importorg.apache.commons.io.FileUtils;
importorg.junit.After;
importorg.junit.Before;
importorg.junit.Test;
importorg.openqa.selenium.By;
importorg.openqa.selenium.OutputType;
importorg.openqa.selenium.WebElement;
importorg.openqa.selenium.remote.DesiredCapabilities;
publicclassCompareScreenShots{
privateAndroidDriverdriver;
@Before
publicvoidsetUp()throwsException{
DesiredCapabilitiescap=newDesiredCapabilities();
cap.setCapability("deviceName","Android");
cap.setCapability("appPackage","com.example.android.notepad");
cap.setCapability("appActivity",".NotesList");
driver=newAndroidDriver(newURL("http://127.0.0.1:4723/wd/hub"),cap);
}
@After
publicvoidtearDown()throwsException{
driver.quit();
}
@Test
publicvoidcompareScreenAndSubScreen()throwsInterruptedException,IOException{
Thread.sleep(2000);
WebElementel=driver.findElement(By.className("android.widget.ListView")).findElement(By.name("Note1"));
el.click();
Thread.sleep(1000);
Stringp1="C:/1";
Stringp2="C:/2";
Filef2=newFile(p2);
Filef1=driver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(f1,newFile(p1));
BufferedImageimg1=Util.getImageFromFile(f1);
f2=driver.getScreenshotAs(OutputType.FILE);
FileUtils.copyFile(f2,newFile(p2));
BufferedImageimg2=Util.getImageFromFile(f2);
Booleansame=Util.sameAs(img1,img2,0.9);
assertTrue(same);
BufferedImagesubImg1=Util.getSubImage(img1,6,39,474,38);
BufferedImagesubImg2=Util.getSubImage(img1,6,39,474,38);
same=Util.sameAs(subImg1,subImg2,1);
Filef3=newFile("c:/sub-1.png");
ImageIO.write(subImg1,"PNG",f3);
Filef4=newFile("c:/sub-2.png");
ImageIO.write(subImg1,"PNG",f4);
}
}
  也不多解析了,没有什么特别的东西。
  大家用得上的就支持下就好了...

posted @ 2014-12-23 00:10 顺其自然EVO 阅读(998) | 评论 (0)编辑 收藏

Android单元测试框架 Robolectric

 Android开发者们注意了,这款测试框架一定会让你们兴奋不已,因为它是一款已基本上摆脱了模拟器测试的老套路的速率单元测试框架Robolectric可以解压Android SDK,还能直接对应用进行测试,从而帮你轻而易举地解决所遇到的任何问题。
  Robolectric 是一款Android单元测试框架,示例代码:
@RunWith(RobolectricTestRunner.class)
public class MyActivityTest {
@Test
public void clickingButton_shouldChangeResultsViewText() throws Exception {
Activity activity = Robolectric.buildActivity(MyActivity.class).create().get();
Button pressMeButton = (Button) activity.findViewById(R.id.press_me_button);
TextView results = (TextView) activity.findViewById(R.id.results_text_view);
pressMeButton.performClick();
String resultsText = results.getText().toString();
assertThat(resultsText, equalTo("Testing Android Rocks!"));
}
}

posted @ 2014-12-23 00:08 顺其自然EVO 阅读(448) | 评论 (0)编辑 收藏

JUnit中的测试套件和参数化测试

JUnit4.x的测试运行器
  JUnit单元测试提供了默认的测试运行器,它的测试方法都是由它负责执行的
  我们也可以定制自己的运行器,所有的运行器都继承自org.junit.runner.Runner
  还可以使用org.junit.runer.RunWith注解 为每个测试类指定使用具体的运行器
  一般情况下,默认测试运行器可以应对绝大多数的单元测试要求
  当使用JUnit提供的一些高级特性,或者针对特殊需求定制JUnit测试方式时
  显式的声明测试运行就必不可少了
  JUnit4.x测试套件的创建步骤
  ① 创建一个空类作为测试套件的入口
  ② 使用org.junit.runner.RunWith 和org.junit.runners.Suite.SuiteClasses注解 修饰该空类
  ③ 将org.junit.runners.Suite 作为参数传入RunWith注解,即使用套件运行器执行此类
  ④ 将需要放入此测试套件的测试类组成数组,作为SuiteClasses注解的参数
  ⑤ 保证这个空类使用public 修饰,而且存在公开的不带有任何参数的构造函数
  下面是JUnit4.x中创建测试套件类的示例代码
package com.jadyer.junit4;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;
/**
* JUnit4.x测试套件的举例
* @see 下面的CalculatorTest.class和ParameterTest.class均为我们自己编写的JUnit4单元测试类
*/
@RunWith(Suite.class)
@SuiteClasses({CalculatorTest.class, ParameterTest.class})
public class TestAll {}
  下面是JUnit3.8中创建测试套件类的示例代码
package com.jadyer.junit3;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;
/**
* JUnit3.8中批量运行所有的测试类。。直接在该类上Run As JUnit Test即可
* @see 这里就用到了设计模式中典型的组合模式,即将不同的东西组合起来
* @see 组合之后的东西,即可以包含本身,又可以包含组成它的某一部分
* @see TestSuite本身是由TestCase来组成的,那么TestSuite里面就可以包含TestCase
* @see 同时TestSuite里面还可以继续包含TestSuite,形成一种递归的关系
* @see 这里就体现出来了,所以这是一种非常非常好的设计模式,一种好的策略
*/
public class TestAll extends TestCase {
//方法名固定的,必须为public static Test suite()
public static Test suite() {
//TestSuite类实现了Test接口
TestSuite suite = new TestSuite();
//这里传递的是测试类的Class对象。该方法还可以接收TestSuite类型对象
suite.addTestSuite(CalculatorTest.class);
suite.addTestSuite(MyStackTest.class);
return suite;
}
}


JUnit4.X的参数化测试
  为保证单元测试的严谨性,通常会模拟不同的测试数据来测试方法的处理能力
  为此我们需要编写大量的单元测试的方法,可是这些测试方法都是大同小异的
  它们的代码结构都是相同的,不同的仅仅是测试数据和期望值
  这时可以使用JUnit4的参数化测试,提取测试方法中相同代码 ,提高代码重用度
  而JUnit3.8对于此类问题,并没有很好的解决方法,JUnit4.x弥补了JUnit3.8的不足
  参数化测试的要点
  ① 准备使用参数化测试的测试类必须由org.junit.runners.Parameterized 运行器修饰
  ② 准备数据。数据的准备需要在一个方法中进行,该方法需要满足的要求如下
  1) 该方法必须由org.junit.runners.Parameterized.Parameters注解 修饰
  2) 该方法必须为返回值是java.util.Collection 类型的public  static方法
  3) 该方法没有参数 ,方法名可随意 。并且该方法是在该类实例化之前执行的
  ③ 为测试类声明几个变量 ,分别用于存放期望值和测试所用的数据
  ④ 为测试类声明一个带有参数的公共构造函数 ,并在其中为③ 中声明的变量赋值
  ⑤ 编写测试方法,使用定义的变量作为参数进行测试
  参数化测试的缺点
  一般来说,在一个类里面只执行一个测试方法。因为所准备的数据是无法共用的
  这就要求,所要测试的方法是大数据量的方法,所以才有必要写一个参数化测试
  而在实际开发中,参数化测试用到的并不是特别多
  下面是JUnit4.x中参数化测试的示例代码
  首先是Calculator.java
package com.jadyer.junit4;
/**
* 数学计算-->加法
*/
public class Calculator {
public int add(int a, int b) {
return a + b;
}
}
  然后是JUnit4.x的参数化测试类ParameterTest.java
package com.jadyer.junit4;
import static org.junit.Assert.assertEquals; //静态导入
import java.util.Arrays;
import java.util.Collection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.jadyer.junit4.Calculator;
/**
* JUnit4的参数化测试
*/
@RunWith(Parameterized.class)
public class ParameterTest {
private int expected;
private int input11;
private int input22;
public ParameterTest(int expected, int input11, int input22){
this.expected = expected;
this.input11 = input11;
this.input22 = input22;
}
@Parameters
public static Collection prepareData(){
//该二维数组的类型必须是Object类型的
//该二维数组中的数据是为测试Calculator中的add()方法而准备的
//该二维数组中的每一个元素中的数据都对应着构造方法ParameterTest()中的参数的位置
//所以依据构造方法的参数位置判断,该二维数组中的第一个元素里面的第一个数据等于后两个数据的和
//有关这种具体的使用规则,请参考JUnit4的API文档中的org.junit.runners.Parameterized类的说明
Object[][] object = {{3,1,2}, {0,0,0}, {-4,-1,-3}, {6,-3,9}};
return Arrays.asList(object);
}
@Test
public void testAdd(){
Calculator cal = new Calculator();
assertEquals(expected, cal.add(input11, input22));
}
}
/********************【该测试的执行流程】************************************************************************/
//1..首先会执行prepareData()方法,将准备好的数据作为一个Collection返回
//2..接下来根据准备好的数据调用构造方法。Collection中有几个元素,该构造方法就会被调用几次
//   我们这里Collection中有4个元素,所以ParameterTest()构造方法会被调用4次,于是会产生4个该测试类的对象
//   对于每一个测试类的对象,都会去执行testAdd()方法
//   而Collection中的数据是由JUnit传给ParameterTest(int expected, int input11, int input22)构造方法的
//   于是testAdd()用到的三个私有参数,就被ParameterTest()构造方法设置好值了,而它们三个的值就来自于Collection
/************************************************************************************************************/

posted @ 2014-12-23 00:07 顺其自然EVO 阅读(657) | 评论 (0)编辑 收藏

仅列出标题
共394页: 上一页 1 2 3 4 5 6 7 8 9 下一页 Last 
<2024年11月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜