Android单元测试的整理
作者:kachidokima 发布时间:[ 2017/6/12 10:19:12 ] 推荐标签:软件测试 单元测试
本人近实践,个人比较喜欢采用JUit+Mock+Espresso,所以也展示了这三个。本来想分篇的,后还是压缩了一下一篇吧。
文中代码大部分是以前摘录的,比较零散也忘记出处了,也有自己写的一些,总体来说都是比较好的示例。
JUnit
导包
//如果只在Java环境下测试,只需以下且默认都有这个配置
testCompile 'junit:junit:4.12'
//如果需要调用Android的组件则需要多加
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support:support-annotations:'+supportLibVersion
//且defaultConfig节点需要加上
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
使用
这是Java界用的广泛,很多框架都是基于这个框架的,用起来也比较简单
public int add(int one, int another) {
return one + another;
}
本来写个测试需要这样:
public int test() {
Calculator calculator = new Calculator();
int sum = calculator.add(1, 2);
if(sum == 3) {
System.out.println("add() works!")
} else {
System.out.println("add() does not works!")
}
}
现在有了这个框架,只需要这样
//会在每个测试方法前执行
@Before
public void setup() {
mCalculator = new Calculator();
}
@Test
public void testAdd() throws Exception {
int sum = calculator.add(1, 2);
Assert.assertEquals(3, sum);
}
//如果要验证抛出异常
@Test(expected = IllegalArgumentException.class)
public void test() {
mCalculator.divide(4, 0);
}
验证方法都在Assert类中,看方法名能理解,不列举了
然后说一下常用的注解:
setUp/@Before:在每个单元测试方法执行之前调用
tearDown/@After:在每个单元测试方法执行后调用
setUpBeforeClass/@BeforeClass:在每个单元测试类运行前调用
tearDownAfterClass/@AfterClass:在每个单元测试类运行完成后调用
Junit3中每个测试方法必须以test打头,Junit4中增加了注解,对方法名没有要求,@Test可以
如果想在测试类中暂时忽略某个方法,标注@Ignore
高阶
Parameterized
主要用于多参数的一次性测试,需要5步
用 @RunWith(Parameterized.class) 来注释 test 类
创建一个由 @Parameters 注释的公共的静态方法,它返回一个对象的集合(数组)来作为测试数据集合
创建一个公共的构造函数,它接受和一行测试数据相等同的东西
为每一列测试数据创建一个实例变量
用实例变量作为测试数据的来源来创建你的测试用例
示例:
@RunWith(Parameterized.class)
public class CalculatorAddParameterizedTest {
@Parameters
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][]{
{0, 0, 0},
{0, -1, -1},
{2, 2, 4},
{8, 8, 16},
{16, 16, 32},
{32, 0, 32},
{64, 64, 128}});
}
private final double mOperandOne;
private final double mOperandTwo;
private final double mExpectedResult;
private Calculator mCalculator;
public CalculatorAddParameterizedTest(double operandOne, double operandTwo,
double expectedResult) {
mOperandOne = operandOne;
mOperandTwo = operandTwo;
mExpectedResult = expectedResult;
}
@Before
public void setUp() {
mCalculator = new Calculator();
}
@Test
public void testAdd_TwoNumbers() {
double resultAdd = mCalculator.add(mOperandOne, mOperandTwo);
assertThat(resultAdd, is(equalTo(mExpectedResult)));
}
}
Rule
类似于@Before、@After,是用来在每个测试方法的执行前后可以标准化的执行一些代码,一般我们直接用框架中现有的Rule可以了
具体可以看这篇:
Junit Rule的使用
Mockito
依赖
androidTestCompile "org.mockito:mockito-core:$mockitoVersion"
Mock作用
专注于单元测试,可以把想要测试类中没有实现的模块虚拟Mock出来,先给需要测试的模块用着
Mock出来的类是空壳,是一个继承与原类,方法都是hook的新类,每个方法都需要Stub,否则返回的都是默认值
Spy出来的类可以使用原来类的方法,但是也可以指定方法有hook处理
使用
创建Mock
使用Rule
@Mock
MyDatabase databaseMock;
@Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
标注RunWith
@RunWith(MockitoJUnitRunner.class)
public class Test{
@Mock
MyDatabase databaseMock;
}
手动mock
MyClass test = mock(MyClass.class);
指定Stub
创建之后由于都是空的,所以要指定行为
若方法中的某一个参数使用了matcher,则所有的参数都必须使用matcher
第一种:Mockito.when(obj.methodCall()).thenReturn(result)
不能用于重复的Stub、返回void函数、Spy出来的类
@Test
public void test1() {
// define return value for method getUniqueId()
when(test.getUniqueId()).thenReturn(43);
// use mock in test....
assertEquals(test.getUniqueId(), 43);
}
@Test
public void testMoreThanOneReturnValue() {
Iterator<String> i= mock(Iterator.class);
when(i.next()).thenReturn("Mockito").thenReturn("rocks");
String result= i.next()+" "+i.next();
//assert
assertEquals("Mockito rocks", result);
}
@Test
public void testReturnValueInDependentOnMethodParameter() {
Comparable<Integer> c= mock(Comparable.class);
when(c.compareTo(anyInt())).thenReturn(-1);
//assert
assertEquals(-1, c.compareTo(9));
}
//return都可以用answer来代替
@Test
public final void answerTest() {
// with thenAnswer():
when(list.add(anyString())).thenAnswer(returnsFirstArg());
// with then() alias:
when(list.add(anyString())).then(returnsFirstArg());
}
但是如果用这个来指定Spy则无效
@Test
public void testLinkedListSpyWrong() {
// Lets mock a LinkedList
List<String> list = new LinkedList<>();
List<String> spy = spy(list);
//无效且会抛出异常,因为调用了一次方法且此时list空
when(spy.get(0)).thenReturn("foo");
assertEquals("foo", spy.get(0));
}
第二种:Mockito.doReturn(result).when(obj).methodCall()
可以重复Stub,可以使用doAnswer来Stub方法
@Test
public void testLinkedListSpyCorrect() {
// Lets mock a LinkedList
List<String> list = new LinkedList<>();
List<String> spy = spy(list);
// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);
assertEquals("foo", spy.get(0));
}
// with doAnswer():
doAnswer(returnsFirstArg()).when(list).add(anyString());
该when(….).thenReturn(….)方法链可以用于抛出异常
Properties properties = mock(Properties.class);
when(properties.get(”Anddroid”)).thenThrow(new IllegalArgumentException(...));
try {
properties.get(”Anddroid”);
fail(”Anddroid is misspelled”);
} catch (IllegalArgumentException ex) {
// good!
}
并且可以指定Spy
@Test
public void testLinkedListSpyCorrect() {
// Lets mock a LinkedList
List<String> list = new LinkedList<>();
List<String> spy = spy(list);
// You have to use doReturn() for stubbing
doReturn("foo").when(spy).get(0);
assertEquals("foo", spy.get(0));
}
doAnswer
//需要测试的代码
public void getTasks(@NonNull final LoadTasksCallback callback) {...}
interface LoadTasksCallback {
void onTasksLoaded(List<Task> tasks);
void onDataNotAvailable();
}
//stub
doAnswer(new Answer() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
Object[] arg=invocation.getArguments();//获取参数
TasksDataSource.LoadTasksCallback callback = (TasksDataSource.LoadTasksCallback) arg[0];//0代表第一个参数
callback.onTasksLoaded(TASKS);
return null;
}
}).when(mTasksRepository).getTasks(any(TasksDataSource.LoadTasksCallback.class));
验证测试
主要验证是否方法调用和次数
//方法调用,且参数一定
Mockito.verify(mockUserManager, Mockito.times(2)).performLogin("xiaochuang", "xiaochuang password");
//如果是一次,可以简写
Mockito.verify(mockUserManager).performLogin("xiaochuang", "xiaochuang password");
//也可以限定次数
Mockito.verify(test, atLeastOnce()).someMethod("called at least once");
高阶
ArgumentCaptor
可以捕获方法的参数,然后进行验证,也可以用来在有回调的方法上,避免doAnswer的复杂写法
需要导包hamcrest-library
@Captor
private ArgumentCaptor<List<String>> captor;
@Test
public final void shouldContainCertainListItem() {
List<String> asList = Arrays.asList("someElement_test", "someElement");
final List<String> mockedList = mock(List.class);
mockedList.addAll(asList);
verify(mockedList).addAll(captor.capture());
final List<String> capturedArgument = captor.getValue();
assertThat(capturedArgument, hasItem("someElement"));
}
InOrder
可以指定验证的次序
// A. Single mock whose methods must be invoked in a particular order
List singleMock = mock(List.class);
//using a single mock
singleMock.add("was added first");
singleMock.add("was added second");
//create an inOrder verifier for a single mock
InOrder inOrder = inOrder(singleMock);
//following will make sure that add is first called with "was added first, then with "was added second"
inOrder.verify(singleMock).add("was added first");
inOrder.verify(singleMock).add("was added second");
// B. Multiple mocks that must be used in a particular order
List firstMock = mock(List.class);
List secondMock = mock(List.class);
//using mocks
firstMock.add("was called first");
secondMock.add("was called second");
//create inOrder object passing any mocks that need to be verified in order
InOrder inOrder = inOrder(firstMock, secondMock);
//following will make sure that firstMock was called before secondMock
inOrder.verify(firstMock).add("was called first");
inOrder.verify(secondMock).add("was called second");
// Oh, and A + B can be mixed together at will
@InjectMocks
主动构造有构造函数的Mock,且其参数也需要用注解来生成
public ArticleManager(User user, ArticleDatabase database) {
super();
this.user = user;
this.database = database;
}
@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {
@Mock
ArticleDatabase database;
@Mock
User user;
@InjectMocks
private ArticleManager manager;
}
Espresso来UI测试
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11