一、UIAutomator

Android自动化测试工具有很多,但是要免费、易上手,本人觉得就直接使用Eclipse自带的UIAutomator就不错。测试人员无需跟开发要代码信息,只要手机上有安装之后的APP自己就能做出自动测试用例,况且一通百通,就算是不满足于UI测试的,找个简单易上手的先明白原理,再深入了解其它复杂工具也会轻松很多。何乐而不为呢?

UIAutomator是Eclipse自带的用于UI自动化测试工具,可仿真APP上的单击、滑动、输入文本等操作。

在使用之前,需要安装好java有关的JDK,SDK,然后配置java环境变量。关于安装JDK,SDK,配置JAVA环境变量网上有数不清的教程,这里就不废话了。直接进入主题。

还是简单介绍一下自动化测试代码中使用到的类关系:现在还不清楚也没有关系,先让程序跑起来,然后在使用API的过程中自然就理解了。

1、创建java工程

打开Eclipse,操作步骤: 点击“File—>New—>java Project”,然后打开如下图的界面:

其它的先不用管,既然要求必须输入Project name,那就输入。请用英文命名,很多时候中文会带来许多意想不到的问题。

输入Project name之后,点击“Next”进入下一步,看到的界面如下:

Source下的东西无需关注,点击“Libraries”,既然是调用java自带的UIAutomator,就要先引入UIAutomator使用到的包。导入uiautomator相关包:点击“Add External JARs”

先要找到自己的android.jar 和 Uiautomator.jar 包的位置,一般在安装文件下的adt-bundle-windows-x86-20130917\sdk\platforms\android-18\ 下,具体要找到自己电脑上的jar位置哦,

选中两个jar文件,“打开”导入。

此外还需要再加入本地库,点击“Add Library”,选择Junit,后点击“next”,使用默认的JUit3。点击“Finish”。

回到New Java Project,点击“Finish”即可创建工程成功。

2、创建class文件

刚刚创建的工程,Src下还没有任何东西,我们需要创建一个Class文件,用来写自动化程序代码。

创建class文件:项目名称右键——NEW——class,写class内容如下

2.1 import UIautomator两个包

2.2 class 名称必须和XXX.java名称相同

2.3每一个public开头,且以test命名开始的函数,系统就视为一个case,运行时方法的调用顺序并不是代码中的位置顺序,而是根据方法名的ASCII码大小顺序调用的。

2.4每一个class都必须继承自UiAutoMatorTestCase

3、生成xml 文件

找到target版本:>android list targets

Id:1

创建build.xml到测试工程中去,用于生成.jar文件:>Android create UItest-project –n testprojectname –t 1 –p testprojectpath

4.build jar 文件

4.1 Update Ant to 1.9

4.2 配置ant使用的javac版本:

右键build.xml—》RUN AS —》 External Tools Configurations。

        在main Tab 下输入Argumens: -Dbuild.compiler=javac1.7——》

Apply。

4.3配置sdk中的build.xml 文件E:\Android\android\adt-bundle-windows-x86-20130917\sdk\tools\ant\build.xml: 找到javac,添加includeantruntime="false"

<javac encoding="${java.encoding}"

source="${java.source}" target="${java.target}"

debug="true" extdirs="" includeantruntime="false"

destdir="${out.classes.absolute.dir}"

bootclasspathref="project.target.class.path"

verbose="${verbose}"

classpathref="project.javac.classpath"

fork="${need.javac.fork}" includeantruntime="false">

<src path="${source.absolute.dir}" />

<src path="${gen.absolute.dir}" />

<compilerarg line="${java.compilerargs}" />

</javac>

5. push jar文件到手机

Cmd环境下需要先找到adb.exe所在位置目录,完整的命令语句:

adb push <path_to_output_jar>  /data/local/tmp

“<path_to_output_jar>”即要放到手机里的jar文件名及路径

相反的如果是从手机复制文件到电脑:

6. 运行测试

Cmd环境下需要先找到adb.exe所在位置目录,完整的命令语句:

adb shell uiautomator runtest  XXX.jar  -c  XXXClassname

备注:XXX.jar需要运行的jar文件名,

Classname: 需要运行jar中的哪个类的名字

7.测试结果

运行结果如下:

Current: 当前运行的测试编号,与方法名称相关

Class:当前运行的函数所在的类

Numtests:测试的总数,每一个public testXX就是一个测试数

Test: 当前测试的函数名称

INSTRUMENTAION_STATUS_CODE:测试状态码,1表示正在执行,0表示执行成功,-1编辑运行错误

二、使用UIAUTOMATORVIEWER获取APP控件

1.手机打开app

使用UIAutomator获取手机app控件时,先在手机上运行起对应app

2 .电脑连接手机

确保电脑与手机已连通。

先在cmd界面到达java的adb.exe所在位置,

运行命令“ >adb shell ”获取当前连接电脑的设备,如下图,如果adb shell 运行正常,会弹出“shell@hwH30-C00:/$”

有多个设备连接开发机器时(模拟器或真机),通过设置ANDROID_SERIAL环境变量指定需要截图的设备。比如下图的:shell@hwH30-C00:/$ set ANDROID_SERIAL= hwH30-C00,设置连接成功后,返回:“set ANDROID_SERIAL=hwH30-C00”

adb devices命令查看连接中的设备编号

如果当前只连接了一个设备,则不用再在Cmd中执行连接命令

3.运行uiautomatorviewer.bat

找到uiautomatorviewer.bat安装目录,本机中的位置如下:

E:\Android\android\adt-bundle-windows-x86-20130917\sdk\tools\uiautomatorviewer.bat

双击运行uiautomatorviewer.bat,打开界面如下图:

上图中左半部分显示当前手机的呈现界面,若要获取其它界面的控件,需要在手机上后,点击上图左上角顶部的刷新,重新获取新界面。

上图右半部分显示当前界面的所有ui元素层即控件信息。右上半部分显示层级,右下部分显示指定层级上具体的控件属性,比如当前属性“class”:当前控件的所在class

“Package”:显示当前控制所在包

“Resource-id”:这个属性有最好了,有些Android开发人员没有为每个控件单独命名,在抓取的时候就非常不方便。如果有Resource-id,在抓取控件时,直接用Resource-id就能获取到正确的控件,并且操作正确,如果没有Resource-id,就只能通过查找同类控件后再按顺序获取了。比如先找到所有的textbox控件,再从所有的textbox控件中按序取几个,才能抓取到对应控件。

还有一些控件属性显示是否可见,是否checked,有实际应用需要时,可以获取

这里附上一个简单的测试java源代码,供参考代码格式

import com.android.uiautomator.core.UiDevice;

import com.android.uiautomator.core.UiObject;

import com.android.uiautomator.core.UiObjectNotFoundException;

import com.android.uiautomator.core.UiScrollable;

import com.android.uiautomator.core.UiSelector;

import com.android.uiautomator.testrunner.UiAutomatorTestCase;

public class MyTestCase extends UiAutomatorTestCase {

public void testDemo() throws UiObjectNotFoundException {

//点击home回到桌面

UiDevice device = getUiDevice();

device.pressHome();

// 点击并等待打开app

UiObject czmApp = new UiObject(new UiSelector().className(android.widget.TextView.class.getName()));

czmApp.clickAndWaitForNewWindow();

sleep(1000);

//登录;instan从0开始,

UiObject username= new UiObject(new UiSelector().className("android.widget.EditText").resourceId("com.wlyc.warehousechampions:id/login_username_edit"));

username.clearTextField();

username.setText("15300000018");

UiObject pwd= new UiObject(new UiSelector().className("android.widget.EditText").enabled(true).resourceId("com.wlyc.warehousechampions:id/login_password_edit"));

pwd.clearTextField();

pwd.setText("00OA8C");

device.pressBack();

sleep(500);

UiObject enter= new UiObject(new UiSelector().className("android.widget.Button").resourceId("com.wlyc.warehousechampions:id/login_btn"));

enter.click();

sleep(5000);

//跳转到“我的”

UiObject My= new UiObject(new UiSelector().className("android.widget.RelativeLayout").resourceId("com.wlyc.warehousechampions:id/personcenter"));

My.clickAndWaitForNewWindow();

sleep(500);

//检查认证状态

UiObject result= new UiObject(new UiSelector().className("android.widget.TextView").resourceId("com.wlyc.warehousechampions:id/personcenter_apply_status"));

int n=result.getText().length();

//认证审核中

n=Dur_verify(n);

//认证失败 或者认证成功

if(n==4)

{

UiObject failed= new UiObject(new UiSelector().className("android.widget.RelativeLayout").resourceId("com.wlyc.warehousechampions:id/apply_certificate_layout"));

failed.click();

UiObject isedit= new UiObject(new UiSelector().className("android.widget.RelativeLayout").resourceId("com.wlyc.warehousechampions:id/titlebar_right_layout"));

//认证失败

if(isedit.exists())

{

isedit.click();

write();

sleep(500);

}

else //认证成功

{

UiObject backtoMy= new UiObject(new UiSelector().className("android.widget.LinearLayout").resourceId("com.wlyc.warehousechampions:id/titlebar_left_layout"));

backtoMy.click();

Release();

}

}

n=result.getText().length();

n=Dur_verify(n);

Release();

}

//录入并提交企业认证

public void write()throws UiObjectNotFoundException

{

//跳转到企业认证

UiObject Company= new UiObject(new UiSelector().className("android.widget.RadioButton").resourceId("com.wlyc.warehousechampions:id/company_certificate"));

Company.click();

//录入企业认证信息

UiObject CompanyName=new UiObject(new UiSelector().className("android.widget.EditText").resourceId("com.wlyc.warehousechampions:id/cettificate_companyname_edit"));

CompanyName.setText("test");

sleep(500);

//拍照

UiObject photo= new UiObject(new UiSelector().className("android.widget.ImageView").resourceId("com.wlyc.warehousechampions:id/uploadimage_image"));

photo.click();

sleep(500);

UiObject takephoto= new UiObject(new UiSelector().className("android.widget.TextView").instance(1));

takephoto.click();

sleep(500);

UiObject phototake= new UiObject(new UiSelector().className("android.widget.ImageView").resourceId("com.android.gallery3d:id/shutter_button"));

phototake.click();

sleep(500);

UiObject photook= new UiObject(new UiSelector().className("android.widget.ImageView").resourceId("com.android.gallery3d:id/btn_done"));

photook.click();

sleep(500);

//提交认证

UiObject commit= new UiObject(new UiSelector().className("android.widget.Button").resourceId("com.wlyc.warehousechampions:id/commit_btn"));

commit.click();

sleep(500);

}

//发布仓库

public void Release() throws UiObjectNotFoundException

{

UiObject wh_release= new UiObject(new UiSelector().className("android.widget.RelativeLayout").resourceId("com.wlyc.warehousechampions:id/warehouse_distribute"));

wh_release.click();

UiObject wh_name= new UiObject(new UiSelector().className("android.widget.EditText").resourceId("com.wlyc.warehousechampions:id/warehouse_name_edit"));

wh_name.setText("nametest");

UiObject wh_ctg= new UiObject(new UiSelector().className("android.widget.RelativeLayout").resourceId("com.wlyc.warehousechampions:id/spinnerview_layout"));

wh_ctg.clickAndWaitForNewWindow();

UiObject wh_ctg1= new UiObject(new UiSelector().className("android.widget.TextView").resourceId("com.wlyc.warehousechampions:id/spinnerview_listitem_text"));

wh_ctg1.click();

UiObject wh_mod= new UiObject(new UiSelector().className("android.widget.RelativeLayout").resourceId("com.wlyc.warehousechampions:id/spinnerview_layout").instance(1));

wh_mod.click();

sleep(500);

UiObject wh_mod1= new UiObject(new UiSelector().className("android.widget.TextView").enabled(true).instance(0));

wh_mod1.click();

sleep(500);

UiObject wh_area= new UiObject(new UiSelector().className("android.widget.EditText").resourceId("com.wlyc.warehousechampions:id/warehouse_area_edit"));

wh_area.setText("5000");

UiObject wh_area_low= new UiObject(new UiSelector().className("android.widget.EditText").resourceId("com.wlyc.warehousechampions:id/warehouse_rentarea_edit"));

wh_area_low.setText("500");

UiObject wh_area_cpb= new UiObject(new UiSelector().className("android.widget.EditText").resourceId("com.wlyc.warehousechampions:id/warehouse_availrentarea_edit"));

wh_area_cpb.setText("1000");

UiObject wh_pric= new UiObject(new UiSelector().className("android.widget.EditText").resourceId("com.wlyc.warehousechampions:id/warehouse_price_edit"));

wh_pric.setText("26");

UiObject wh_etpris= new UiObject(new UiSelector().className("android.widget.EditText").resourceId("com.wlyc.warehousechampions:id/warehouse_company_edit"));

wh_etpris.setText("companyname");

UiObject wh_floor= new UiObject(new UiSelector().className("android.widget.CheckBox").enabled(true).instance(0));

wh_floor.click();

sleep(500);

//向下滑动屏幕

UiScrollable  wh_scroll=new UiScrollable(new UiSelector().className("com.wlyc.warehousechampions:id/pulltorefresh_listview"));

wh_scroll.setAsVerticalList();

;

UiObject wh_addre= new UiObject(new UiSelector().className("android.widget.TextView").resourceId("com.wlyc.warehousechampions:id/warehousedetail_hightemperature_unit"));

wh_addre.click();

}

//认证审核中等待

public int Dur_verify(int n) throws UiObjectNotFoundException

{

while (n!=4) {

UiObject My= new UiObject(new UiSelector().className("android.widget.RelativeLayout").resourceId("com.wlyc.warehousechampions:id/personcenter"));

My.clickAndWaitForNewWindow();

sleep(5000);

UiObject result= new UiObject(new UiSelector().className("android.widget.TextView").resourceId("com.wlyc.warehousechampions:id/personcenter_apply_status"));

n=result.getText().length();

}

return n;

}

}

三、Uiautomator Api分析

UiAutomator主要涉及以下几个类,大多数位于源码包的com.android.uiautomator.core下,其中粗体字部分为主要会接触到的类,熟知这5个类的作用,就可以大体顺畅的写出UiAutomator的测试用例。

UiAutomatorTestCase

UiDevice

UiSelector

UiScrollable

UiObject

UiCollection

UiTestAutomationBridge, UiAutomatorBridge

InteractionController, QueryController

UiWatcher

1、UiAutomatorTestCase

TestCase (Junit) -> UiAutomatorTestCase  -> App Test

每个测试用例(类)都需要继承UiAutomatorTestCase,以实现测试环境的setup,teardown(拆卸)等同能。而UiAutomatorTestCase则是通过继承Junit3中的TestCase类,并在其中的setUp() 、tearDown() 、getParams() 函数中。其中主要是用Bundle实现Android Activity之间的通讯。在UiAutomatorTestCase,还加入了getUiDevice()等关于UiDevice的 函数,以实现在测试的任意地方均可调用UiDevice()。

2、UiDevice

此类主要包含了获取设备状态信息,和模拟用户至于设备的操作两类api。

可以通过getDisplaySizeDp(), getDisplayWidth() , getDisplayHeight() ,getProductName() ,getCurrentActivityName(), getCurrentPackageName() 等获取设备相关信息。

pressMenu(), pressBack(), pressHome(), pressSearch() ,pressDPadCenter(), pressDPadRight(), pressDPadLeft(), pressDPadUp(), pressDPadDown() ,pressDelete(), pressEnter(), pressKeyCode(), pressRecentApps(),click(),swipe(),getDisplayRotation() setOrientationLeft()… wakeUp(), sleep() ,dumpWindowHierarchy(), waitForWindowUpdate()等API可以灵活的操纵设备。

而takeScreenshot() 允许随时对设备截屏。

3、UiSelector

主要是通过一定查询方式,定位到所要操作的UI元素。

一般UI元素均可通过以下API定位:text(), textMatches(String regex), textStartsWith(), textContains() ,className() ,classNameMatches(String regex), className(Class type) ,Description(), descriptionMatches(String regex),descriptionStartsWith(),descriptionContains() ,packageName(), packageNameMatches(String regex)。

值得注意的是index()和 instance() 两个函数,其中index()是当前页面的ID编号,instance()则表示在一定的搜索结果下,获取的子元素集的第几个元素。如:

new UiSelector().className("android.widget.ImageView").enabled(true).instance(2);

另有enabled(), focused(), focusable(), scrollable(), selected(), checked(), clickable() ,longClickable() ,childSelector()等检索条件,顾名思义。

4、UiObject

UiObject可代表页面的任意元素,它的各种属性定位通常通过UiSelector来完成。

比较常用的Api如clickAndWaitForNewWindow(),表示点击该元素,并且等待新窗口的展示完毕。这一过程是Android UI Testing框架支持的,不需要额外的控制等待时间。

UiObject允许点击该元素的具体一个部分,Api如clickTopLeft(), longClickBottomRight(),…

通过getText(), getContentDescription(), getVisibleBounds(),… 等api可获取UiObject的相关属性,getPackageName() 可用来明确是否打开了目标测试的App.

setText(), clearTextField() 可以 用来设置以及清空所关联的输入框。

waitForExists() 可以用来操纵相关等待或验证。

5、UiCollection

UiCollection一般与UiSelector连用,如它的构造函数也要求提供Uiselector: UiCollection(UiSelector selector)。

它的api较少,主要用以从Uiselector筛选出的元素集中挑出所要的元素:getChildByDescription(), getChildByInstance(), getChildByText() ,以及统计元素集的个数getChildCount()

6、UiScrollable

UiObject -> UiCollection ->UiScrollable

UiScrollable 用来表示可以滑动的界面元素,其继承关系如上图所示。

其Api中,setAsVerticalList(), setAsHorizontalList() 用以设置Ui元素列表是基于横向滚动还是纵向滚动。其后可以用getMaxSearchSwipes() ,flingForward(), flingBackward() ,scrollForward(),scrollBackward() ,scrollToEnd(), scrollToBeginning() 等函数控制滑动,以及getChildByDescription(), getChildByInstance(), getChildByText() ,scrollIntoView(), scrollTextIntoView(),… 来选择是否已经转换到具有目标元素的页面。如:

UiScrollable appViews = new UiScrollable(new UiSelector().scrollable(true));

appViews.setAsHorizontalList();

UiObject helperApp;

helperApp = appViews.getChildByText(new UiSelector()

.className(android.widget.TextView.class.getName()), " 91助手 ");  则若当前页面没有91助手APP, 测试会自动滑动页面,直到91助手App出现。

下面介绍下UI Testing Framework构成的重要类:

7、UiTestAutomationBridge

这是整个Testing Framework的基础,此类负责连接系统了,记录最新的可链接事件(AccessibilityEvent) , 窗口内容查询Api等。可以被Android App调用,或者Java程序从shell调用。

这里需要注意两个概念:

7.1、AccessibilityEvent:所有的Ui元素可以被操纵,因为这些Event都是AccessibilityEvent。对于怎样令页面元素可以被操纵,使得相关的事件都是AccessibilityEvent,请参见Uiautomator 词条-"确认程序可以被测试" 部分。

7.2、AccessibilityNodeInfo:视窗中的组件树节点,也就是uiautomtorViewer中展示的各个节点。

Api中connect(), disconnect() 负责建立与设备的实际连接。

executeCommandAndWaitForAccessibilityEvent() performAccessibilityAction() findAccessibilityNodeInfosByText(), findAccessibilityNodeInfoByViewIdInActiveWindow() 都是其中重要的Api。

8、UiAutomatorBridge

UiAutomatorBridge是UiTestAutomationBridge的子类,区别主要是在构造函数中加上了InteractionController 和QueryController 两大对象的调用。以及一些常量定义等。除了上述差异,UiAutomatorBridge还定义了executeCommandAndWaitForAccessibilityEvent() 、onAccessibilityEvent() 、waitForIdle() 、addAccessibilityEventListener() 等函数。

9、InteractionController

介绍InteractionController,需要先提InteractionProvider,它负责注入用户事件(如点击、输入等) ,并且反应事件的对应坐标。

InteractionController则定义了几乎所有至于手机的基础操作,如runAndWaitForEvents(), clickAndWaitForEvents() ,click(), longTap(), scrollSwipe(),Swipe() ,clickAndWaitForNewWindow() ,touchUp(), touchDown(), TouchMove() ,isNaturalRotation(), setRotationRight(), setRotationLeft() ,freezeRotation() ,wakeDevice(), sleepDevice() 等。

10、QueryController

QueryController负责把UiSelector 的查找信息转化为AccessibilityNodeInfo。

具体Api如下:findNodePatternRecursive(), translatePatternSelector(), translateReqularSelector(), translateCompoundSelector(), getRootNode() ,findAccessibilityNodeInfo()。

11、UiWatcher

UiWatcher只在UiSelector无法找到匹配的结果时被调用,意在重试、等待页面更新 (如弹出对话框)等。其中只有一个主要函数:checkForCondition() 。

它的相关函数均在UiDevice中,如:UiDevice.registerWatcher() ,UiDevice. resetWatcherTriggers() ,UiDevice.runWatchers() ,UiDevice.removeWatcher()

android app自动化测试之UIAutomator的更多相关文章

  1. Android app自动化测试之Python+Appium环境搭建

    1.安装JDK (1)JDK安装时会有两次,一次是jdk,第二次是jre. (2)环境变量配置: 添加JAVA_HOME变量, 值:Jdk的安装路径 添加CLASSPATH变量,值: .;%JAVA_ ...

  2. Appium+Python app自动化测试之脚本启动和停止Appium服务

    研究了一段时间的Appium android app的自动化测试,工作中需要连接多台手机终端同时执行测试用例,我实现的方式是获取用例中需要执行用例的设备id个数以及实际连接到的设备数(通过adb de ...

  3. Android App性能测试之adb命令

    本篇文章总结了Android App性能测试过程中常用的adb命令.通过这些adb命令,可以查看App的性能数据,为评判性能好坏作参考. CPU相关 显示占用CPU最大的5个应用 adb shell ...

  4. android自动化测试之uiautomator

    1.通过monkeyrunner.bat   monkey_record.py启动MonkeyRecorder进行拿到各个控件的坐标(要连上手机才可以启动) 2.也可以通过uiautomatorvie ...

  5. app自动化测试之实战应用(百度app简单测试)

    模拟在百度app中搜索python相关内容代码如下: from appium import webdriver desired_caps = {} desired_caps['deviceName'] ...

  6. 【Python + ATX基于uiaotumator2】之Android—APP自动化简易例子

    上代码: import uiautomator2 as u2 from time import sleep d = u2.connect_usb('608ad0fe') #打开小卖 # d(text= ...

  7. app UI测试之UIAutomator

    执行UIAutomator测试步骤 1.新建Java项目,导入android.jar和uiautomator.jar包,继承UiAutomatorTestCase 2.生成编译文件 android c ...

  8. Android APP 性能测试之 GT 工具

    一.介绍: GT(随身调)是 APP 的随身调测平台,它是直接运行在手机上的"集成调测环境"(IDTE, Integrated Debug Environment).利用 GT,仅 ...

  9. Android App性能测试之二:CPU、流量

    CPU---监控值的获取方法.脚本实现和数据分析 1.获取CPU状态数据 adb shell dumpsys cpuinfo | findstr packagename 自动化测试脚本见cpustat ...

随机推荐

  1. PS 多次剪裁同一图片

    一个图品里面有两个小图,要分别抠出来. 我以前的做法是,先扣一个,重新打开文件,再扣另外一个. 今天发现一个简单的办法,不用重新打开文件. 就是在扣完第一个的时候,打开历史记录面板,双击 打开 动作, ...

  2. IOS 多线程05-OperationQueue 、GCD详解

      注:本人是翻译过来,并且加上本人的一点见解. 1. 开始 目前在 iOS中有两套先进的同步 API 可供我们使用:操作队列OperationQueue和 GCD .其中 GCD 是基于 C 的底层 ...

  3. Eclipse 工程迁移到 Android Studio

    目标:迁移成功,并成功正常运行! 附加:同步视频在文章后面! 两种方式: 1. 用Gradle导出,在Android Studio中用Gradle导入 (不推荐) 2. 用Android Studio ...

  4. Atitit 深入了解UUID含义是通用唯一识别码 (Universally Unique Identifier),

    Atitit 深入了解UUID含义是通用唯一识别码 (Universally Unique Identifier), UUID1 作用1 组成1 全球唯一标识符(GUID)2 UUID 编辑 UUID ...

  5. atitit。wondows 右键菜单的管理与位置存储

    atitit.wondows 右键菜单的管理与位置存储 原理 .这样的功能称为Windows外壳扩展(Shell Extensions) 1 常用右键菜单 atiContentMenu1 通用tool ...

  6. 每天一个linux命令(41):ps命令

    Linux中的ps命令是Process Status的缩写.ps命令用来列出系统中当前运行的那些进程.ps命令列出的是当前那些进程的快照,就是执行ps命令的那个时刻的那些进程,如果想要动态的显示进程信 ...

  7. angularjs的resource实例对象

    angularjs的resource实例对象 我们看看都有啥 而直接使用service对象的时候没有前面这些$

  8. Account problem-There may be a problem with your account. Please contact us. Sign out

    很多人在使用开发者账号AppleID的时候,都会碰到如下问题 There may be a problem with your account. Please contact us. 登录到苹果的开发 ...

  9. Reporting Service 配置SMTP和设置订阅出现的异常

    SSRS能够按照schedule,以mail的形式发送report,这是通过设置subscription report来实现的. 1,发送mail需要在SSRS中配置SMTP Server,如果没有R ...

  10. writing-mode改变文字书写方式

    古代书写方式都是垂直方向上的,如果要实现这种效果的话,还是挺麻烦的,不过现在CSS3有一个"writing-mode"属性,它可以改变文字的书写方式. writing-mode:h ...