65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 TestNG 进行健全性测试

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2020年9月8日

CPOL

6分钟阅读

viewsIcon

6498

本文解释了健全性测试的概念以及如何使用 TestNG 功能实现健全性测试。

最近,我遇到了一个需求,需要缩短夜间运行的单元和集成测试作业的总执行时间。通常,有两种方法可以缩短总测试执行时间。

  1. 以独立且快速的方式编写测试用例。这可以通过使用较少的冗余验证、诸如 skip exceptions(跳过异常)以及 soft 或 hard assertions(软断言或硬断言)之类的注解来实现。但是,本文将不深入探讨这个话题。
  2. 以一种能够呈现优化且组织良好的独立测试套件(组或类别 - 基于单个产品功能)的外观和感觉的方式设计整体项目结构。当我们将诸如优先级测试执行、组、依赖项等概念包含在我们的项目结构设计和测试框架设计中时,这就可以实现了。而这正是我们将在本文中讨论的——使用 TestNG 的分组和依赖项概念进行健全性测试。

话虽如此,在继续之前,请注意 TestNG 是一个用于执行端到端测试的测试框架,包括单元、组件、集成、回归、UI 等。这意味着它提供了广泛的功能来满足所有这些测试类型的需求。但是,我们只会讨论健全性测试。

健全性测试

健全性代表一个人的(对象的)理智程度和精神状态。这驱动了健全性测试的概念。健全性测试指的是一些测试用例,它们应该在套件中的所有其他测试用例之前运行,并且其结果(通过/失败)将决定其余测试用例是否应该执行。够清楚了吧?让我用一些实际项目中的例子来帮助你理解健全性测试的应用,以及它如何帮助我们优化执行时间,而这正是本文的重点。

演示应用程序

MorningSmoothie 是一款冰沙业务应用程序。为了演示和方便理解使用 TestNG 进行健全性测试的概念,我只涵盖了这个应用程序的一个功能——产品管理的单元测试。项目结构如下:

代码可以在我的 git 上找到。这显然是任何 Java 测试框架的简单而标准的结构。从 src 文件夹扩展出来的两个主要文件夹 maintest 包含进一步的子包。main 包含代码库(应用程序相关类、常量、业务逻辑层、实用工具等)。而 test 文件夹用于特定功能的测试用例。对于这个示例,maintest 中的 resources 文件夹都是空的。我们在这里存储配置文件。

仔细看,你可以看到,我将 POJO 与我的存储库(ProductManager)类分开放在 main 中,以增强其独立交互性。同样,在 test 文件夹中,有针对逻辑上不同的类的专用包。

然而,这是我逻辑设计一个小型测试应用程序的方法。你可以根据自己的意愿设计你的类及其派生方式,只要它们保持可重用性并使你的框架易于扩展。

代码解释

// src/main/java/com/morningSmoothies/repositories/ProductManager.java

public class ProductManager {

    private List<FreshSmoothie> productStorage;

    public ProductManager() {
        productStorage = new ArrayList<FreshSmoothie>();
    }
    // TODO: you can add any fancy business logic before performing any of the CRUD operations

    public boolean addProduct(FreshSmoothie smoothie) {
        return productStorage.add(smoothie);
    }

    public FreshSmoothie getProduct(int id){
        for(FreshSmoothie s : productStorage){
            if(s.equals(id)){
                return s;
            }
        }
        return null;
    }

    public boolean deleteProduct(final int id){
        return productStorage.removeIf(e -> e.equals(id));
    }

    public List<FreshSmoothie> getAllProducts() {
        return productStorage;
    }
}
[CodeSample1]

ProductManager 类开始 [CodeSample1]。我们有一些方法来执行与我们的产品相关的日常业务操作。这些是简单的 CRUD 操作(Createreadupdatedelete),它们会消耗我们的私有 productStorage。现在,我们将编写一些单元测试来检查这些函数是否按预期工作 [CodeSample2]

// src/test/java/unit/FreshSmoothie/CRUDTests.java

public class CRUDTests extends BaseClassUnitTesting {

    ProductManager pm;
    FreshSmoothie fs1, fs2, fs3;
    @BeforeMethod
    public void localSetup(){
        // Arrange setup
        fs1 = new FreshSmoothie(1, "MalonSmoothie", 25);
        fs2 = new FreshSmoothie(2, "PeachSmoothie", 30);
        fs3 = new FreshSmoothie(3, "MangoSmoothie", 35);
        pm = new ProductManager();
    }

    @Test(description = "Verify that addProduct method returns true 
                         when adds product successfully")
    public void successfulProductAdditionReturnsTrue() {
        // Act
        boolean result = pm.addProduct(fs1);
        // Assert
        Assert.assertTrue(result);
    }

    @Test(description = 
          "Verify that getProduct method returns null if product does not exist")
    public void nonExistingProductReturnsNull(){
        // Arrange
        boolean result = pm.addProduct(fs1);
        // Act
        FreshSmoothie fsReturned = pm.getProduct(fs1.getID());
        // Assert
        Assert.assertNull(fsReturned,"The method should return null 
                          if it doesn't find an added product for the given ID");
    }

    @Test(description = "Verify that getAllProducts method returns valid product collection")
    public void productStorageReturnValidCount(){
        // Arrange
        pm.addProduct(fs1);
        pm.addProduct(fs2);
        pm.addProduct(fs3);
        // Act
        List<FreshSmoothie> smoothies = pm.getAllProducts();
        // Assert
        Assert.assertEquals(3, smoothies.size());
    }
}
[CodeSample2]

我无法逐行解释代码,因为我认为这太冗长了。因此,我按照 AAA 约定(Arrange – Act – Assert)安排了我的测试。这些测试非常简单,但足以完成演示工作。这里需要注意的一点是,我们有一个本地设置,用于为类中的所有测试安排共享的持有者,将其视为在类中的每个测试运行之前都必须运行的本地套件设置。此外,我还添加了 GlobalSetup 来模拟全局套件设置的行为,它将在每次套件执行之前运行一次。这个全局套件设置已添加到基类——BaseClassUnitTesting 中。

显然,此时你可能还无法理解这些本地和全局套件以及方法级别的调用为何重要,然而,在实际应用程序中,它们对于清理共享资源、创建元数据和初始会话,或者用于报告日志的最小部分至关重要。

值得注意的是,在 test/unit 文件夹中有五个带有 @Test 注解的测试。我在 test/unit/sanityTests 文件夹中编写了健全性测试用例;然而,我还没有添加任何组和 DependsOn 字段。所以,我们期望我们的测试按字母顺序正常运行。

作为 TestNG IntelliJ 用户,你应该知道运行类或包中测试的多种方法。所以,我通过在 test/unit 文件夹上选择以下选项来手动运行它们。

一旦你的测试执行完毕,你将看到下面的输出窗口。通过扫描输出窗口,你可以看到健全性测试已经执行,但顺序是正常的。尽管如此,目标是先于其他测试运行我们的健全性测试,并确保这些其他测试的执行基于我们健全性测试的成功结果。

所以,我将添加 groupsdependsOnGroups 字段,分别用于健全性和其他 CRUDTests 类测试。

你可能对名为 sanity 的组感到好奇。请参阅下面的截图。这些健全性测试已经是我们之前执行的一部分,但没有 @Test(groups = "sanity") 字段。

现在我们将像以前一样再次运行我们的测试,并注意执行顺序是否有任何不同。

很公平,对吧?这次测试执行顺序不是按字母顺序,而是逻辑依赖的。健全性测试在其他测试之前运行。即使现在,我敢肯定,你们中的许多人还没有理解,仅仅通过使我们的测试执行顺序依赖于某些逻辑,我们就能获得的想法和好处。

因此,为了演示,我将故意让我的健全性测试失败,然后让你们观察其中的差异。

请注意图像中突出显示的部分。我生成了“除以零异常”来使我的健全性测试失败,并且依赖测试的执行被忽略了。相比之下,如果我们不使用这些组和 dependsOnGroups 字段,如果一些必须成功执行的基本检查失败,导致所有其他测试最终也失败,我们的执行时间就会被浪费。

结论

本文旨在作为一份简短指南,介绍如何使用 TestNG 的 groups 和 dependsOnGroups 功能来推广健全性测试。我带你了解了如何以逻辑依赖的顺序组织我们的测试,以便在一些基本检查失败时优化我们的执行时间。

为演示应用程序编写的健全性测试涵盖了需要执行良好才能通过所有其他测试的基本功能,例如创建产品存储和成功添加产品。如果其中任何一个失败,其余测试的执行就没有意义了,因为它们最终都会失败。最后,代码示例已上传到 github,并以 .zip 格式附加在此处。

© . All rights reserved.