Android单元测试(三):使用JUnit进行单元测试

发表于:2018-5-25 11:29

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:一笑小先生    来源:简书

  自定义JUnit Rules
  要编写自定义规则,需要实现TestRule接口。这个接口定义了一个apply(Statement, Description)方法Statement。Statement代表JUnit运行时内的测试,Statement#evaluate()运行这些测试。说明描述了单个测试。它允许通过反射来读取关于测试的信息。
  以下是在测试执行之前和之后添加日志语句的一个简单示例:
  public class CustomRule implements TestRule {
      private Description description;
      @Override
      public Statement apply(Statement base, Description description) {
          this.description = description;
          return new MyStatement(base);
      }
      public class MyStatement extends Statement {
          public final Statement base;
          public MyStatement(Statement statement) {
              this.base = statement;
          }
          @Override
          public void evaluate() throws Throwable {
              System.out.println("CustomRule" + description.getMethodName() + "Started");
              try {
                  base.evaluate();
              } finally {
                  System.out.println("CustomRule" + description.getMethodName() + "Finished");
              }
          }
      }
  }
  要使用这个规则,简单地添加一个注解@Rule到你的测试类的字段。
  @Rule
  public CustomRule customRule = new CustomRule();
  Categories
  Category继承自Suit,Category似乎是Suit的加强版,它和Suit一样提供了将若干测试用例类组织成一组的能力,除此以外它可以对各个测试用例进行分组,使你有机会只选择需要的部分用例。
  具体示例如下:
  public interface FastTests {
  }
  public interface SlowTests { 
  }
  public class A {
      @Test
      public void a() {
          fail();
      }
      @Category(SlowTests.class)
      @Test
      public void b() {
      }
  }
  @Category({ SlowTests.class, FastTests.class })
  public class B {
      @Test
      public void c() {
      }
  }
  @RunWith(Categories.class)
  @IncludeCategory(SlowTests.class)
  @SuiteClasses({ A.class, B.class })
  public class SlowTestSuite {
      // 运行 A.b 和 B.c, 不会运行 A.a
  }
  @RunWith(Categories.class)
  @IncludeCategory(SlowTests.class)
  @ExcludeCategory(FastTests.class)
  @SuiteClasses({ A.class, B.class })
  public class SlowTestSuite {
      // 运行 A.b, 不会运行 A.a 和 B.c
  }
  Theories
  Theories继承自BlockJUnit4ClassRunner,提供了除Parameterized之外的另一种参数测试解决方案——似乎更强大。Theories不再需要使用带有参数的Constructor而是接受有参的测试方法,修饰的注解也从@Test变成了@Theory,而参数的提供则变成了使用@DataPoint或者@Datapoints来修饰的变量,两者的唯一不同是前者代表一个数据后者代表一组数据。Theories会尝试所有类型匹配的参数作为测试方法的入参(有点排列组合的意思)。看一个使用Theories的例子:
  @RunWith(Theories.class)
  public class TheoriesTest {
      @DataPoint
      public static String nameValue1 = "Tony";
      @DataPoint
      public static String nameValue2 = "Jim";
      @DataPoint
      public static int ageValue1 = 10;
      @DataPoint
      public static int ageValue2 = 20;
      @Theory
      public void testMethod(String name, int age) {
          System.out.println(String.format("%s's age is %s", name, age));
      }
  }
  输出结果:
  Tony's age is 10
  Tony's age is 20
  Jim's age is 10
  Jim's age is 20
  同样使用@DataPoints可以获得一样的效果:
  @RunWith(Theories.class)
  public class TheoriesTest2 {
      @DataPoints
      public static String[] names = {"Tony", "Jim"};
      @DataPoints
      public static int[] ageValue1 = {10, 20};
      @Theory
      public void testMethod(String name, int age) {
          System.out.println(String.format("%s's age is %s", name, age));
      }
  }
  除此以外Theories还可以支持自定义数据提供的方式,需要继承JUnit的ParameterSupplier类。下面的代码使用ParameterSupplier实现上述例子:
  public class NameSupplier extends ParameterSupplier {
      @Override
      public List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable {
          PotentialAssignment nameAssignment1 = PotentialAssignment.forValue("name", "Tony");
          PotentialAssignment nameAssignment2 = PotentialAssignment.forValue("name", "Jim");
          return Arrays.asList(nameAssignment1, nameAssignment2);
      }
  }
  public class AgeSupplier extends ParameterSupplier {
      @Override
      public List<PotentialAssignment> getValueSources(ParameterSignature sig) throws Throwable {
          PotentialAssignment ageAssignment1 = PotentialAssignment.forValue("age", 10);
          PotentialAssignment ageAssignment2 = PotentialAssignment.forValue("age", 20);
          return Arrays.asList(ageAssignment1, ageAssignment2);
      }
  }
  @RunWith(Theories.class)
  public class TheoriesTest3 {
      @Theory
      public void testMethod(@ParametersSuppliedBy(NameSupplier.class) String name, @ParametersSuppliedBy(AgeSupplier.class) int age) {
          System.out.println(String.format("%s's age is %s", name, age));
      }
  }
  Assume
  Assume直译为假设,是JUnit提供的一套用于判断测试用例的入参是否有业务含义的工具,如果入参不符合预期时会抛出AssumptionViolatedException,默认的BlockJUnit4ClassRunner及其子类会捕获这个异常并跳过当前测试,如果使用自定义的Runner则无法保证行为,视Runner的实现而定。
  Assume提供的验证方法包括: assumeTrue/assumeFalse、 assumeNotNull、 assumeThat、 assumeNoException 。具体含义都比较简单。
  假设我们有一个打印姓名和年龄的测试用例,使用Theories提供多套参数测试,由于实际年龄不存在负数所以使用Assume排除不合理的数据:
  @RunWith(Theories.class)
  public class AssumeTest {
      @DataPoints
      public static String[] names = {"LiLei", "HanMeiMei"};
      @DataPoints
      public static int[] ages = {10, -2, 12};
      @Theory
      public void printAge(String name, int age) {
          Assume.assumeTrue(age > 0);
          System.out.println(String.format("%s's Age is %s.", name, age));
      }
  }
  输出结果为:
  LiLei's Age is 10.
  LiLei's Age is 12.
  HanMeiMei's Age is 10.
  HanMeiMei's Age is 12.
  JUnit生命周期
  JUnit提供了@Before、@BeforeClass、@After、@AfterClass注解,这些方法的执行顺序是怎样的?通过下面的代码来演示JUnit的生命周期:
  public class JUnitLifeCycleTest {
      public JUnitLifeCycleTest() {
          super();
          System.out.println("<<Person Constructor>>");
      }
      @BeforeClass
      public static void beforeClassM() {
          System.out.println("<<Before Class>>");
      }
      @Before
      public void beforeM() {
          System.out.println("<<Before>>");
      }
      @AfterClass
      public static void afterClassM() {
          System.out.println("<<After Class>>");
      }
      @After
      public void after() {
          System.out.println("<<After>>");
      }
      @Test
      public void testMethod1() {
          System.out.println("Test Method 1.");
      }
      @Test
      public void testMethod2() {
          System.out.println("Test Method 2.");
      }
  }
  输出结果:
  <<Before Class>>
  <<Person Constructor>>
  <<Before>>
  Test Method 1.
  <<After>>
  <<Person Constructor>>
  <<Before>>
  Test Method 2.
  <<After>>
  <<After Class>>
  @BeforeClass:修饰static的方法,在整个类执行之前执行该方法一次。比如你的测试用例执行前需要一些高开销的资源(连接数据库)可以用@BeforeClass搞定。值得注意的是如果测试用例类的父类中也存在@BeforeClass修饰的方法,它将在子类的@BeforeClass之前执行。
  @AfterClass:同样修饰static的方法,在整个类执行结束前执行一次。如果你用@BeforeClass创建了一些资源现在是时候释放它们了。
  @Before:修饰public void的方法,在每个测试用例(方法)执行时都会执行。
  @After:修饰public void的方法,在每个测试用例执行结束后执行。
  Constructor:每个测试用例都会重新创建当前的Class实例,可以看到Constructor执行了两次。
  小结
  这篇文字大概简单介绍了JUnit的使用,相对来说是比较简单,也是比较容易理解的,希望能帮助到大家,后续计划再有一篇记录自己在阅读JUnit4源码码的一些收获和感想。
  Assert可以帮我们验证一个方法的返回结果。然而,这些只能帮我们测试有返回值的那些方法。一个类的方法分两种,一是有返回值的方法,这些可以通过我们今天讲的JUnit来做测试。而另外一种没有返回值的方法,即void方法,则要通过另外一个框架,Mockito,来验证它的正确性。至于怎么样验证void方法的正确性,以及Mockito的使用,请关注后续文章。

22/2<12
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号