Práctica de pruebas unitarias de Android

que es la prueba unitaria

Definición: la prueba unitaria es escribir código de prueba para la unidad funcional más pequeña

La unidad funcional más pequeña de un programa Java es un método, por lo que la prueba unitaria de un programa Java es una prueba para un solo método Java.

¿Qué es JUnit?

JUnit es un marco de pruebas unitarias de código abierto para el lenguaje Java, diseñado específicamente para Java y es el más utilizado. JUnit es el marco estándar de facto para pruebas unitarias, y cualquier desarrollador de Java debería aprender y usar JUnit para escribir pruebas unitarias.

Los beneficios de escribir pruebas unitarias con JUnit son:

  • Muy fácil de organizar el código de prueba y ejecutarlo en cualquier momento
  • JUnit dará pruebas exitosas y pruebas fallidas, y también puede generar informes de prueba
  • No solo incluye la tasa de éxito de la prueba, sino también la cobertura de código de la prueba, es decir, la cantidad de código probado que se ha probado.
  • Casi todas las herramientas IDE integran JUnit para que podamos escribir y ejecutar pruebas JUnit directamente en el IDE.

Para código de alta calidad, la cobertura de la prueba debe ser superior al 80 %.

Los beneficios de las pruebas unitarias

  • Las pruebas unitarias aseguran que los métodos individuales se comporten como se espera. Si modifica el código de un método, solo necesita asegurarse de que la prueba unitaria correspondiente pase y la modificación se considere correcta.
  • El código de prueba en sí se puede usar como código de muestra para demostrar cómo llamar al método

Al escribir pruebas unitarias, tenemos que seguir ciertas reglas:

  • El código de prueba de la unidad en sí debe ser muy simple, se puede entender de un vistazo y no debe escribir pruebas para el código de prueba;
  • Cada prueba unitaria debe ser independiente entre sí y no depender del orden en que se ejecute;
  • Al realizar la prueba, no solo se deben cubrir los casos de prueba comunes, sino que también se debe prestar especial atención a las condiciones límite de la prueba, como la entrada es 0, nulo, la cadena vacía "" y así sucesivamente.

La mayoría de las exposiciones en línea son escenarios anormales, por lo que es necesario centrarse en verificar la lógica anormal relevante en las pruebas unitarias.

Cómo escribir pruebas unitarias

agregar dependencias

Las siguientes dependencias se agregan automáticamente al build.gradle del módulo de la aplicación en el nuevo proyecto de Android:

testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
复制代码
  • testImplementation : indica las dependencias de prueba de la unidad Junit, correspondientes al directorio de prueba
  • androidTestImplementation :表示Android集成测试,对应的是androidTest目录

在写单元测试的时候,有些对象在运行时是没有真实构造的,这个时候我们可以使用mock框架来模拟出一个可用的对象,需要添加如下依赖:

testImplementation 'org.mockito:mockito-core:2.19.0'
复制代码

添加用例

首先添加一个测试类,这里我添加一个简单的计算类:

public class Calculate {

    private int mValue;

    //+1
    public int addOne() {
        return ++mValue;
    }

    //-1
    public int reduceOne() {
        return --mValue;
    }
}
复制代码

然后在方法名上右键鼠标,如下图所示,点击"Test":

goto test

如果之前该类没有创建过Test类,则会提示你没有找到对应的测试类,点击“create Test”即会出现如下弹框:

create test

  • Testing Library:测试用例库,因为我们build.gradle中依赖的是Junit4,所以这里选择Junit4即可
  • Class name:表示生成的测试文件类型。一般用默认的即可(业务类后面加上Test作为测试类名)
  • Superclass:基类名称。一般正常填业务类的基类即可
  • Destination package:test目录下生成的Test类的目标包名
  • setUp/@Before : 是否生成setUp方法,并且加上@Before注解
  • tearDown/@After :是否生成tearDwon方法,并且加上@After注解
  • Member:这里会列出该类提供的所有public方法,这里你可以选择对哪些方法添加测试用例

点击ok按钮,会让你选择创建单元测试用例 还是 集成测试用例,如下图所示:

file

这里我们选择单元测试用例。 然后我们就会在test目录下找到对应的包名和测试文件了,如下图所示:

file

注解

单元测试的时候用的最多的是上面3个注解:

@Before : 表示该方法在其他所有的Test方法执行之前都会执行一遍。一般用于初始化。

@After :表示每个Test方法执行结束后,都会执行一遍After方法。一般用于回收相关资源

@Test:标识该方法是一个测试方法

添加用例

我们在刚才生成的CalculateTest类中增加如下代码:

public class CalculateTest {

    private Calculate mCalculate;
    @Before
    public void setUp() throws Exception {
        mCalculate = new Calculate();
    }

    @After
    public void tearDown() throws Exception {
        mCalculate = null;
    }

    @Test
    public void addOne() {
        Assert.assertTrue(mCalculate.addOne() == 1);

        Assert.assertEquals(mCalculate.addOne(), 2);
        
    }

    @Test
    public void reduceOne() {
        Assert.assertTrue(mCalculate.reduceOne() == -1);
    }
}
复制代码
  1. 我们首先声明一个Calculate类型的变量mCalculate
  2. 我们在setUp中构造一个Calculate对象实例,赋值给mCalculate
  3. 在addOne和reduceOne方法中引用mCalculate,做对应方法的验证

这里我们用到了Junit支持的断言来判断用例是否通过:

  • Assert.assertTrue:支持条件验证,条件满足则该用例能通过,否则用例执行会失败
  • Assert.assertEquals:这里assertEquals重载了多个类型的实现,只是这里是比较int值而已。

异步测试

public class CalculateTest {

    private Calculate mCalculate;

    ExecutorService sSingleExecutorService = Executors.newSingleThreadExecutor();

   	......

    @Test
    public void addOneAsync() {
        final CountDownLatch signal =  new  CountDownLatch(1) ;

        sSingleExecutorService.execute(new Runnable() {
            @Override
            public void run() {
                Assert.assertTrue(mCalculate.addOne() == 1);

                Assert.assertEquals(mCalculate.addOne(), 2);

                signal.countDown();
            }
        });

        try  {
            signal.await();
        }  catch  (InterruptedException e) {
            e.printStackTrace() ;
        }
    }
}
复制代码

如上代码所示,针对异步场景,我们可以使用到 CountDownLatch 类来针对性的暂停执行线程,直到任务执行完成后再唤醒用例线程。

注意,上面的try 才是暂停执行线程的核心。

Mock测试

有些时候我们不免会引用Android框架的对象,但是我们单元测试又不是运行在真实设备上的,在运行时是没有构建出真实的Android对象的,不过我们可以通过mock程序来模拟一个假的对象,并且强制让该对象的接口返回我们预期的结果。

1.添加mock依赖引用,前面添加依赖项的时候有提到:

testImplementation 'org.mockito:mockito-core:2.19.0'
复制代码

2.导入静态会让代码简洁很多,这步不是必要的:

import static org.mockito.Mockito.*;
复制代码

3.创建mock对象

TextView mockView = mock(TextView.class);
复制代码

4.进行测试插桩

when(mockView.getText()).thenReturn("Junit Test");
复制代码

下面我们看一个简单的例子。 首先我们在Calculate 类中新增一个简单的方法,获取TextView的文本信息:

public String getViewString(TextView view) {
        return view.getText().toString();
    }
复制代码

然后我们在CalculateTest类中新增测试方法:

	@Test
    public void mockTest() {
        TextView mockView = mock(TextView.class);

        when(mockView.getText()).thenReturn("Junit Test");

        assertEquals(mCalculate.getViewString(mockView), "Junit Test");
    }
复制代码

最后运行这个用例,正常通过。

参数化测试

当一个方法有参数时,我们可以批量验证不同参数值,对应的用例是否通过,而不用写多遍类似的代码

1.首先参数化测试,要求我们对测试类添加如下注解

@RunWith(Parameterized.class)
复制代码

2.定义参数集合 - 方法必须定义为 public static 的 - 必须添加@Parameterized.Parameters 3.定义接收参数和期望参数对象 4.增加对应的用例

我们看下面的例子: 首先我们在Calculate 中添加一个有参数的add方法:

public class Calculate {

    private int mValue;
	......
    public int add(int other) {
        mValue += other;
        return mValue;
    }
}
复制代码

接着修改测试类

@RunWith(Parameterized.class) //---------@1
public class CalculateTest {

    private Calculate mCalculate;

    private Integer mInputNumber; //---------@3
    private Integer mExpectedNumber;

	//---------@4
    public CalculateTest(Integer input , Integer output) {
        mInputNumber = input;
        mExpectedNumber = output;
    }

    @Parameterized.Parameters //---------@2
    public static Collection paramsCollection() {
        return Arrays.asList(new Object[][] {
            { 2, 2 },
            { 6, 6 },
            { 19, 19 },
            { 22, 22 },
            { 23, 23 }
        });
    }

    @Before
    public void setUp() throws Exception {
        mCalculate = new Calculate();
    }

    @After
    public void tearDown() throws Exception {
        mCalculate = null;
    }

	//---------@5
    @Test
    public void paramsTest() {
        assertEquals(mExpectedNumber, Integer.valueOf(mCalculate.add(mInputNumber)));
    }
}
复制代码

@1 : 给类添加注解RunWith(Parameterized.class)

@2 : 添加数据集合方法,用@Parameterized.Parameters 注解修饰

@3 : 添加输入参数和期望参数

@4 : 添加构造方法,供给输入参数和期望参数赋值

@5 : 添加测试方法,直接使用输入参数和期望参数进行验证

异常测试

异常验证通过@Test注解参数来指定:

@Test(expected = InvalidParameterException.class)
复制代码

看下面具体的例子:

public class Calculate {

    private int mValue;

    public int addException(int other) {
        if (other < 0) {
            throw  new InvalidParameterException();
        }

        return add(other);
    }
}
复制代码

测试类如下:

@RunWith(Parameterized.class)
public class CalculateTest {

    private Calculate mCalculate;

    @Test(expected = InvalidParameterException.class)
    public void exceptionTest() {
        mCalculate.addException(-1);
    }
}
复制代码

这里可以注意以下几点:

  • expected的异常如果是抛出异常的基类,用例测试也是可以通过的
  • 若没有添加expected参数,则用例会失败

运行用例

  • 运行单个用例方法

运行单个用例方法

点击左侧绿色箭头,会弹出如上图菜单,单机Run 即可执行该用例。

  • 批量执行某个类的所有用例

file

如上图所示,选中测试类文件,右键执行 "Run 类名",就会批量执行该类所有的用例了

  • 批量执行项目所有用例

file

如上图所示,右键包名,执行"Run Test in 包名" 即可执行该包下所有类对应的用例

导出测试报告

在执行完测试用例之后,我们可以导出测试报告,如下图所示:

导出测试报告

查看测试覆盖度

查看测试覆盖度

如上图所示:点击converage按钮,在右边窗口会弹出如下覆盖情况,这里从3个方面统计测试覆盖度:

  • class
  • method
  • Line

最后,我们可以导出覆盖报告.

本文由博客一文多发平台 OpenWrite 发布!

Supongo que te gusta

Origin juejin.im/post/6999168679166345230
Recomendado
Clasificación