自动化测试用例的开发设计

用例开发与设计的好坏直接体现个人的专业性、职业性; 好的用例设计在代码维护性与可读性、重构等方面更加地友好。这里我根据自己多年经验,并结合PnxTest框架,整理与提炼出以下规范与原则,供大家参考。

总体原则

  • 任何需求,要从宏观上了解它的商业背景:做这个需求或产品的出发点是什么,有什么商业价值,对用户产生的意义

  • 不要机械地去follow需求或者接口文档,尽量站在更高的层面(终端用户的角度,商业价值等)去思考和设计用例,并大胆地去质疑设计的合理性、合规性。

  • 用例的覆盖:review从其它渠道反馈的bug,如果可以,增加维护到你的自动化测试程序中。

  • 每个模块负责人需要非常熟悉自己负责的业务,输出test specification并定期维护。

  • 对于接口测试:

    1) 入参出参的详细描述和定义:要深入具体到每个参数字段,它们的作用与目的,以及在数据库中对应的字段
    2) 全链路调用流程和处理逻辑:哪些客户端在什么页面、什么条件下触发&调用,后端会经过哪些子系统(服务、中间件)去处理
    3) 数据流:产生的数据是如何在 本地、缓存服务、持久性数据库之间流转的
    4) 终极思考:根据前面3点,结合现实场景,思考:安全性、业务逻辑上的合理性,并发性,稳定性的要求,对响应速度的要求...

最佳实践

1、一个测试方法应该只对应一个测试点,能直观地体现测试的要点和目的。

//Don't: 不要多个测试点揉合在一个测试方法中
@Test
void testProduct(){
  //获取产品信息
  Product myProduct = PnxHttp.get("/product")
                .queryString("productId", 1024)
                .asObject(Product.class);
  PnxAssert.assertThat(myProuct.getStatus())
           .isEqualTo(ProductStats.Online);

  //修改产品信息
  PnxHttp.put("/product")
         .field("productId", 1024)
         .field("stock", "9998")
             .asString();
  PnxAssert.assertThat(myProuct.getStock())
           .isEqualTo(9998);
}
//Do: 拆分为两个用例
@Test
@DisplayName("能够获取正确的产品信息")
void testProductRetrieving(){
  //获取产品信息
  Product myProduct = PnxHttp.get("/product")
                .queryString("productId", 1024)
                .asObject(Product.class);
  PnxAssert.assertThat(myProuct.getStatus())
           .isEqualTo(ProductStats.Online);
}

@Test
@DisplayName("修改产品信息")
void testProductUpdating(){
  //修改产品信息
  PnxHttp.put("/product")
         .field("productId", 1024)
         .field("stock", "9998")
             .asString();
  PnxAssert.assertThat(myProuct.getStock())
           .isEqualTo(9998);
}

2、使用@Controller对用例进行模块分类:归类测试用例, 展示逻辑更清晰

//Don't: 
public class ProductTest{
  //test methods
}
//Do: 使用@Controller分类(多个Class可以使用相同的模块名)
@Controller(module="ProductService")
public class ProductTest{
  //test methods
}

3、测试方法内的逻辑尽可能的简短,具体的实现逻辑放到@Step中

//Don't
@Controller(module="product service")
public class ProductTest{

    @Test
        @DisplayName("能够获取正确的产品信息")
    void testProductRetrieving(){
      //获取产品信息
      Product myProduct = PnxHttp.get("/product")
              .queryString("productId", 1024)
              .asObject(Product.class);
      PnxAssert.assertThat(myProuct.getStatus())
               .isEqualTo(ProductStats.Online);
    }
}

//Do: 使用@Step实现具体功能,便于复用
@Repository
public class ProductService{

  @Step("获取产品ID{0}的详细信息")
  public Product getProductInfo(long productId){
    //...
  }

  @Step("更新产品ID{0}的详细信息")
  public Product updateProductInfo(long productId){
    //...
  } 
}

@Controller(module="product service")
public class ProductTest{
    @Steps 
    ProductService productService;

    @Test
    @DisplayName("能够获取正确的产品信息")
    void testProductRetrieving(){
      //获取产品信息
      Product myProduct = productService.getProductInfo(1024L);
      PnxAssert.assertThat(myProuct.getStatus())
               .isEqualTo(ProductStats.Online);
    }
}

4、使用“actual” 和“expected”前缀命名, 增加代码可读性

//Don't
@Test
@DisplayName("能够获取正确的产品信息")
void testProductRetrieving(){
  //获取产品信息
  Product myProduct1 = productService.getProductInfo(1024L);
  Product myProduct2 = productService.getProductInfo(1025L);
  PnxAssert.assertThat(myProuct1)
    .isEqualTo(myProuct2;
}
//Do
@Test
@DisplayName("能够获取正确的产品信息")
void testProductRetrieving(){
  //获取产品信息
  Product actualProduct = productService.getProductInfo(1024L);
  Product expectedProduct = productService.getProductInfo(1025L);
  PnxAssert.assertThat(actualProduct)
    .isEqualTo(expectedProduct;
}

5、避免使用过多的变量,会使程序看起来臃肿

//Don't
@Test
@DisplayName("批量添加商品")
void testProductBatchAdding(){
  String name1 = "telsa model 3";
  String name2 = "telsa model Y";
  Long productID1 = 1024L;
  Long productID2 = 1025L;
  Long productID3 = 1026L;

  productService.batchAdd(new Product(productID1, name1),
                          new Product(productID2, name1),
                          new Product(productID3, name2),
                         );
}
//Do
@Test
@DisplayName("批量添加商品")
void testProductBatchAdding(){  
  productService.batchAdd(new Product(1024, "telsa model 3"),
                          new Product(1025, "telsa model 3"),
                          new Product(1026, "telsa model Y"),
                         );
}

6、只验证自己需要验证/检测的数据

//Don't: 不要直接验证一个大的对象是否相等,如一个Map、一个JSON对象或者一个Bean对象
Product product1 = new Product("id", "name", "stock", "price");
Product product2 = new Product("id", "name", "stock", "price");

PnxAssert.assertThat(product1).isEqualTo(product2);
//Do
PnxAssert.assertThat(product1.getPrice())
  .isEqualTo(prodcut2.getPrice())
  .isLessThan(100);

7、除了验证接口返回的内容外,尽可能地检查中间链路节点上的数据是否正确落地

//验证接口返回数据
PnxAssert.assertThat(actualReponseData).isEqualTo(true);

//除了验证接口数据,还要验证缓存,数据库、消息系统等
PnxCache.get(key);
PnxSql.select("SELECT * FROM table");

8、分开异步与同步的测试用例,并且单独执行。

9、不要想着自动化一切,对自身团队/组织有实际价值和回报的区域去投入精力实现自动化。例如有些测试每一年才执行一次,就没有必要投入过多的精力去搞自动化;通常来说,90%的自动化是一个合理的标准。

10、选择正确的自动化框架,推荐 PnxTest