2.测试相关知识_打印日志_文件
测试相关知识
根据测试时是否有源代码:
- 黑盒测试:
- 白盒测试
根据测试的粒度:
- 方法测试:
- 单元测试:
- 集成测试:
- 系统测试:
根据测试的暴力程度:
- 压力测试:
- 冒烟测试:
monkey工具
用于压力测试. 首先 adb shell 进入终端中.
然后 #monkey 5000 回车.
手机屏幕就会被狂点5000次.
一个比较完整的命令:
adb shell monkey -p com.xinmei365.font -s 500 --ignore-crashes --ignore-timeouts --monitor-native-crashes -v -v 60000 > E:\java_monkey_log.txt
如何停止呢? 先进 adb shell, 然后 ps | grep monkey, 找到 monkey 的进程号, 然后 kill
进程号.
----
为什么junit一点就能运行, 没有main方法
----
Android中的单元测试
要在Android项目中运行单元测试,首先要在AndroidManifest.xml文件中加入如下配置:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
<uses-sdk android:minSdkVersion="8" />
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.gaoyuan.junit" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<uses-library android:name="android.test.runner" />
<activity
……
</activity>
</application>
</manifest>
测试用例要继承 AndroidTestCase.
public class TestMyService extends AndroidTestCase {
private MyService ms;
/**
* 测试类 TestMyService 在第一次被创建的时候 ,做些初始化工作
*/
@Override
protected void setUp() throws Exception {
ms = new MyService();
}
/**
* 测试方法,需要把异常抛给测试框架
* @throws Exception
*/
public void testAdd() throws Exception{
//MyService ms = new MyService();
int result = ms.add(3, 3);
assertEquals(3+3, result);
}
public void testSub() throws Exception {
//MyService ms = new MyService();
int result = ms.sub(3, 3);
assertEquals(3-3, result);
}
/**
* 测试类 TestMyService 在被销毁的时候,做清理工作
*/
@Override
protected void tearDown() throws Exception {
ms = null;
}
}
其中,setUp,tearDown这两个方法是重写父类的方法,作用如注释所说。
另外,注意 Android 中的测试类的测试方法不需要加 @Test 注解。
除此之外, 还可以建立专门的测试工程. 创建一个Android Test Project, 勾选相应的条目即可.
自动生成的清单文件中就有那两项配置. 测试工程和被测试工程使用的是同一个 Context.
测试用例中, 将context设置为成员变量, getContext为空的问题?
1. Android测试框架的运行过程: 打包.apk, 安装到手机, 运行测试机
2. 创建AndroidTestCase对象 - 初始化成员变量 - 构造函数
3. 对象创建完成之后, 测试机会把当前应用的Context对象通过setContext()方法设置进来
4. 执行测试方法
在测试类的成员变量, 或者构造函数中, 不能调用getContext(), 因为还没设置进来, 会得到null
-------
AndroidTestRunner是干嘛的
-------
logcat的使用
logcat视图用于显示系统打印的日志. 在程序中可以使用 Log 这个类打印日志.
public class MainActivity extends Activity {
// TAG 一般为当前Activity的名字
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// log 有五种级别. TAG 表示标签, 用于指明是谁打印的信息
Log.v(TAG, "我是提醒等级的log");
Log.d(TAG, "我是调试等级的log");
Log.i(TAG, "我是信息等级的log");
Log.w(TAG, "我是警告等级的log");
Log.e(TAG, "我是错误等级的log");
// 在 android 中也可以使用 syso, 但是不会打印在控制台上, 而是logact中.
System.out.println("haha"); //System.out info
System.err.println("error"); //System.err warn
}
}
在 logcat 视图中可以选择显示不同级别的日志信息. 还可以配置过滤器,
根据应用名, TAG名, PID 等条件过滤想要的信息.
文件
保存数据到内存储设备中
用户登录案例: 当用户勾选记住密码后登陆, 将用户名密码保存到手机内存储设备中.
界面就不说了.
public class UserInfoService {
// 如果一个方法没有使用到类的成员变量, 则一般将其定义为static, 这是google推荐的做法.
public static boolean saveUserInfo(Context context, String username, String password) {
// 应用私有的数据一般存放在 /data/data/当前应用程序包名/files/ 这个目录下
// 由于不同手机目录结构可能有差异, 需使用context.getFilesDir()得到这个目录
File file = new File(context.getFilesDir(), "info.txt");
// 如果一个方法有返回值, 则异常一般要catch, 若没有返回值则一般往上抛.
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write((username+":"+password).getBytes());
fos.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public static Map<String, String> getUserInfoMap(Context context) {
try {
File file = new File(context.getFilesDir(), "info.txt");
FileReader fr = new FileReader(file);
BufferedReader br = new BufferedReader(fr);
String info = br.readLine();
br.close();
String[] split = info.split(":");
Map<String, String> map = new HashMap<String, String>();
map.put("username", split[0]);
map.put("password", split[1]);
return map;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
保存到SD卡中
其实和保存到手机内存几乎一样, 只不过有两点需要注意:
1. 得到路径的方式不同.
// 判断SD卡是否可用
String state = Environment.getExternalStorageState();
if(!Environment.MEDIA_MOUNTED.equals(state)) {
Toast.makeText(this, "SD卡不可用, 请检查SD卡", Toast.LENGTH_SHORT).show();
return;
}
// 得到SD卡路径的方法
File file = new File(Environment.getExternalStorageDirectory(), "info.txt");
2. 写SD卡是需要权限的, 从4.0开始, 读SD卡也需要权限了.
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
其余代码和保存到内存一模一样.
== 怎样获取可用存储空间 ==
另外几个常用API
- context.openFileOutput(String filename, int mode)
直接得到应用files目录中某个文件的输出流
- context.openFileInput(String filename)
直接得到应用files目录中某个文件的输入流
- context.getCacheDir()
获取 /data/data/当前应用包名/cache/ 目录, 用户可手动清除这个文件夹中的内容
当系统内存储不足时, 系统也会自动(但不保证)清除这个文件夹中的内容
用户和文件的访问权限
Context.MODE_PRIVATE = 0
Context.MODE_APPEND = 32768
Context.MODE_WORLD_READABLE = 1
Context.MODE_WORLD_WRITEABLE = 2
Context.MODE_PRIVATE:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新
写入的内容追加到原文件中。可以使用Context.MODE_APPEND
Context.MODE_APPEND:模式会检查文件是否存在,存在就往文件追加内容,否则就创建
新文件。
Context.MODE_WORLD_READABLE和Context.MODE_WORLD_WRITEABLE用来控制其他应用是否有权限读写该文件。
MODE_WORLD_READABLE:表
示当前文件可以被其他应用读取;MODE_WORLD_WRITEABLE:表示当前文件可以被其他应用写入。如果希望文件被其他应用读和写,可以传入:
openFileOutput("itcast.txt", Context.MODE_WORLD_READABLE +
Context.MODE_WORLD_WRITEABLE); android有一套自己的安全模型,当应用
程序(.apk)在安装时系统就会分配给他一个userid,当该应用要去访问其他资源比如文件的时候,就需要userid匹配。默认情况下,任何应用创建的文
件,sharedpreferences,数据库都应该是私有的(位于/data/data/<package
name>/files),其他程序无法访问。除非在创建时指定了
Context.MODE_WORLD_READABLE或者Context.MODE_WORLD_WRITEABLE
,只有这样其他程序才能正确访问。
关于如何修改data/data下文件的权限, 参看: 修改data/data下文件的权限
ShearedPreference
Android平台给我们提供了一个SharedPreferences类,它是一个轻量级的存储类,特别适合用于保存软件配置参数。使用SharedPreferences保存数据,其背后是用xml文件存放数据,文件存放在/data/data/<package
name>/shared_prefs目录下
public class MainActivity extends Activity {
private CheckBox cb;
private SeekBar sb;
private SharedPreferences sp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cb = (CheckBox) findViewById(R.id.cb);
sb = (SeekBar) findViewById(R.id.sb);
sb.setMax(100);
// 通过 context 获取 SharedPreference
sp = this.getSharedPreferences("config", MODE_PRIVATE);
boolean isChecked = sp.getBoolean("isChecked", false);
cb.setChecked(isChecked);
int progress = sp.getInt("progress", 0);
sb.setProgress(progress);
cb.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Editor editor = sp.edit();
editor.putBoolean("isChecked", isChecked);
editor.commit();
}
});
sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) { }
@Override
public void onStartTrackingTouch(SeekBar seekBar) { }
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
Editor editor = sp.edit();
editor.putInt("progress", progress);
editor.commit();
}
});
}
}
使用sp, 最后一定注意要 commit
.
另外,
sharedpreference也可以链式编程, sp.edit().putXXX().putXXX().apply();
这样的写法很简练
XML
生成XML
----------
使用StringBuilder, 为什么不用指定编码?
----------
public void click(View view) {
try {
FileOutputStream fos = this.openFileOutput("smsInfo.xml", MODE_PRIVATE);
XmlSerializer ser = Xml.newSerializer();
ser.setOutput(fos, "utf-8");
ser.startDocument("utf-8", true);
// 第一个参数是命名空间
ser.startTag(null, "smss");
for (SmsInfo sms : smsList) {
ser.startTag(null, "sms");
ser.startTag(null, "address");
ser.text(sms.getAddress());
ser.endTag(null, "address");
ser.startTag(null, "body");
ser.text(sms.getBody());
ser.endTag(null, "body");
ser.startTag(null, "date");
ser.text(sms.getDate()+"");
ser.endTag(null, "date");
ser.endTag(null, "sms");
}
ser.endTag(null, "smss");
ser.endDocument();
fos.close();
Toast.makeText(this, "生成xml成功", Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(this, "生成xml异常", Toast.LENGTH_SHORT).show();
}
}
}
Pull解析XML
public static List<Channel> getAll(InputStream is) throws Exception {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(is, "utf-8");
List<Channel> list = null;
Channel channel = null;
int type = parser.getEventType();
while (type != XmlPullParser.END_DOCUMENT) {
switch (type) {
case XmlPullParser.START_TAG:
if ("weather".equals(parser.getName())) {
list = new ArrayList<Channel>();
} else if ("channel".equals(parser.getName())) {
channel = new Channel();
channel.setId(Integer.parseInt(parser.getAttributeValue(0)));
} else if ("city".equals(parser.getName())) {
channel.setCity(parser.nextText());
} else if ("temp".equals(parser.getName())) {
channel.setTemp(parser.nextText());
} else if ("wind".equals(parser.getName())) {
channel.setWind(parser.nextText());
} else if ("pm250".equals(parser.getName())) {
channel.setPm250(Integer.parseInt(parser.nextText()));
}
break;
case XmlPullParser.END_TAG:
if ("channel".equals(parser.getName())) {
list.add(channel);
channel = null;
}
break;
default:
break;
}
type = parser.next();
}
return list;
}