为什么我们需要船上的Vulcan:Spock框架评论

测试自动化有助于持续监控IT产品的质量,并从长期来看降低成本。自动化中有多种方法,例如,行为驱动开发(BDD),通过行为进行开发。



与这种方法相关联的是黄瓜,机器人框架,行为以及其他,它们将执行脚本和每个构造的实现分开。这种分离有助于编写可读的脚本,但是这很耗时,因此在编写实现时不切实际。



让我们看一下如何通过使用正确的工具(例如Spock框架)简化BDD的工作,该工具结合了BDD原理的优美性,便利性和jUnit的功能。







Spock框架



Spock是针对Java和Groovy应用程序的测试和规范框架。通过使用JUnit平台作为基础,此框架可与所有流行的IDE(尤其是IntelliJ IDEA),各种构建工具(Ant,Gradle,Maven)和持续集成(CI)服务器兼容。Spock



如何编写框架的开发人员,“启发了JUnitRSpecjMockMockitoGroovyScalaVulcans和其他令人兴奋的生活方式。”



在本文中,我们将介绍最新的可用版本Spock Framework 2.0。它的功能:能够使用JUnit5,Java 8 +,groovy 2.5(还有一个版本为3.0的程序集)。Spock在Apache 2.0下获得了许可,并具有响应用户社区。框架开发人员继续完善和开发Spock,其中已经包含许多扩展,这些扩展使您可以微调测试运行。例如,已宣布的最有趣的改进领域之一是添加了并行测试执行。



Groovy



Groovy是为Java平台开发的一种面向对象的编程语言,具有Python,Ruby和Smalltalk功能。Groovy将类Java语法与动态编译到JVM字节码一起使用,并直接与其他Java代码和库一起使用。该语言可以在任何Java项目中使用或用作脚本语言。



groovy的特征包括:静态和动态类型;列表,数组和正则表达式的内置语法;操作过载。但是,Groovy中的闭包早于Java。



Groovy非常适合快速测试开发,您可以在其中使用类似python的语法糖,而不必担心键入对象。



Spock框架的功能



该框架的关键特征之一是,开发人员能够使用BDD方法原理编写具有预期系统特征的规范这种方法可以为具有较高主题和组织复杂性的软件产品编写面向业务的功能测试。 该规范是扩展spock.lang.groovy的类。







class MyFirstSpecification extends Specification {
  // fields
  // fixture methods
  // feature methods
  // helper methods
}


BOM可以包含为每个BOM类触发的各种辅助字段。



使用@Shared批注,您可以访问从规范继承的类的字段。



abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver


    def setup() {
        this.driver = DriverFactory.createDriver()
        driver.get("www.anywebservice.ru")
    }

    void cleanup() {
        driver.quit()
    }

}


BOM类自定义方法:



def setupSpec() {} //     feature    
def setup() {}     //    feature 
def cleanup() {}   //    feature 
def cleanupSpec() {} //     feature   


下表显示了Spock框架中具有JUnit对应项的关键字和方法。







面团块



在Spock Framework中,每个测试阶段都分为一个单独的代码块(有关示例,请参阅文档)。







代码块以标签开头,并以下一个代码块的开头或测试的结尾结束。给定的



负责设置初始测试条件。 阻止whenthen一起使用。when包含系统的刺激物和刺激物,then包含系统的响应。 如果可以将when-then子句缩短为单个表达式,则可以使用单个Expect







... 以下示例将从官方Spock框架文档中使用:



when:
def x = Math.max(1, 2)
 
then:
x == 2


或一个表达



expect:
Math.max(1, 2) == 2


清除用于在下一次测试迭代之前释放资源。



given:
def file = new File("/some/path")
file.createNewFile()
 
// ...
 
cleanup:
file.delete()


where子句用来传输数据用于测试(数据驱动测试)。



def "computing the maximum of two numbers"() {
  expect:
  Math.max(a, b) == c
 
  where:
  a << [5, 3]
  b << [1, 9]
  c << [5, 9]
}


输入数据传输的类型将在下面讨论。



Spock框架上的示例测试实现



接下来,我们将考虑使用硒在系统中测试用户授权网页的方法。



import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification

abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver
    
    def setup() {
        this.driver = DriverFactory.createDriver()
        driver.get("www.anywebservice.ru")
    }

    void cleanup() {
        driver.quit()
    }
}


在这里,我们看到页面规范的基类。在课程开始时,我们将看到所需课程的导入。以下是共享注释,该注释允许继承的类访问Web驱动程序。在setup()块中,我们看到用于初始化Web驱动程序和打开Web页面的代码。cleanup()包含用于终止Web驱动程序代码。



接下来,让我们继续介绍用户登录页面规范。



import pages.LoginPage
import spock.lang.Issue

class LoginPageTest extends PagesBaseSpec {

    @Issue("QAA-1")
    def "QAA-1: Authorization with correct login and password"() {

        given: "Login page"
        def loginPage = new LoginPage(driver)

        and: "Correct login and password"
        def adminLogin = "adminLogin"
        def adminPassword = "adminPassword"

        when: "Log in with correct login and password"
        loginPage.login(adminLogin, adminPassword)

        then: "Authorized and moved to main page"
        driver.currentUrl == "www.anywebservice.ru/main"
    }
}


授权页面规范继承自基本页面规范。Issue批注指定外部跟踪系统(例如Jira)中测试的ID。在下一行中,我们看到测试的名称,按照惯例,该名称是由字符串文字设置的,这使您可以使用测试名称中的任何字符(包括俄语)。在给定的块中,初始化授权页面类的页面对象,并获得系统中用于授权的正确登录名和密码。在when块中,执行授权操作。then用于检查预期的操作,即成功授权和重定向到系统主页。



在本规范的示例中,我们看到了在spock中使用BDD范例的最重要的优点-系统的规范也是其文档。每个测试都描述某种行为,测试的每个步骤都有自己的描述,这不仅对于开发人员而且对于客户都是可以理解的。块的描述不仅可以在测试的源代码中显示,而且可以在有关测试操作的诊断消息或报告中显示。



该框架提供了传输各种登录名和密码以进行测试(参数化测试)的能力。



Spock框架中的数据驱动测试



数据驱动测试=表驱动测试=参数化测试


要测试具有多个参数的方案,可以使用各种选项来传递它们。



数据表



让我们看一下官方框架文档中的一些示例。



class MathSpec extends Specification {
  def "maximum of two numbers"() {
    expect:
    Math.max(a, b) == c
 
    where:
    a | b | c
    1 | 3 | 3
    7 | 4 | 7
    0 | 0 | 0
  }
}


表中的每一行都是一个单独的测试迭代。同样,该表可以由一列表示。



where:
a | _
1 | _
7 | _
0 | _


_是BOM表类的存根对象。



为了更好地直观了解参数,可以按照以下形式重写上面的示例:



def "maximum of two numbers"() {
    expect:
    Math.max(a, b) == c
 
    where:
    a | b || c
    1 | 3 || 3
    7 | 4 || 7
    0 | 0 || 0
}


现在我们可以看到ab是输入,c是期望值。



数据管道



在某些情况下,使用设计表会很麻烦。在这种情况下,可以使用以下类型的参数传递:



...
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]


在这里,左移<<是一个重载的常规运算符,现在可以将其添加到列表中。



对于每个测试迭代,将为每个变量请求列表中的以下数据:



1个迭代:a = 1,b = 3,c = 3;

第二次迭代:a = 7,b = 4,c = 7;

第3次迭代:a = 0,b = 0,c = 0。



此外,不仅可以显式地传输输入数据,还可以根据需要从各种来源请求输入数据。例如,从数据库中:



@Shared sql = Sql.newInstance("jdbc:h2:mem:", "org.h2.Driver")
 
def "maximum of two numbers"() {
  expect:
  Math.max(a, b) == c
 
  where:
  [a, b, c] << sql.rows("select a, b, c from maxdata")
}


数据变量分配



...
where:
a = 3
b = Math.random() * 100
c = a > b ? a : b


在这里,我们在测试数据中看到了动态计算的变量c。



结合不同类型的参数传递



...
where:
a | _
3 | _
7 | _
0 | _
 
b << [5, 0, 0]
 
c = a > b ? a : b


如有必要,没有人禁止您一次使用多种传输方式。



在Spock框架上实现参数化测试的示例



@Issue("QAA-1-parametrized")
def "QAA-1-parametrized: Authorization with correct login and password"() {

   given: "Login page"
   def loginPage = new LoginPage(driver)

   when: "Log in with correct login and password"
   loginPage.login(login, password)

   then: "Authorized and moved to main page"
   driver.currentUrl =="www.anywebservice.ru/main"

   where: "Check for different logins and passwords"
   login            | password
   "adminLogin"     | "adminPassword"
   "moderatorLogin" | "moderatorPassword"
   "userLogin"      | "userPassword"
}


在这里,我们看到已经熟悉的where块,其中设置了参数的键(登录名和密码),这些键存储在配置文件中。



由于所用规范实现的特殊性,将对每个参数执行Web驱动程序配置周期及其关闭(并因此关闭浏览器),这会对测试执行时间产生负面影响。我们建议最终确定规范并改善测试运行时间。



用修改后的规范实现参数化测试的示例



修订前



abstract class PagesBaseSpec extends Specification {

    @Shared
    protected WebDriver driver


    def setup() {
        this.driver = DriverFactory.createDriver()
        driver.get("www.anywebservice.ru")
    }

    void cleanup() {
        driver.quit()
    }

}


修改后



import helpers.DriverFactory
import org.openqa.selenium.WebDriver
import spock.lang.Shared
import spock.lang.Specification

abstract class PagesNoRestartBaseSpec extends Specification {

    @Shared
    protected WebDriver driver

    def setupSpec() {
        this.driver = DriverFactory.createDriver()
    }

    def setup() {
        this.driver.get("www.anywebservice.ru")
    }

    def cleanup() {
        this.driver.get("www.anywebservice.ru/logout")
        this.driver.manage().deleteAllCookies();
    }

    void cleanupSpec() {
        this.driver.quit()
    }
}


在更新的规范中,我们看到仅在设置规范类时才执行创建Web驱动程序的过程,只有在规范的测试运行完毕后才关闭浏览器。在setup()方法中,我们看到用于获取服务网址并在浏览器中打开它的相同代码,在cleanup()方法中,我们转到www.anywebservice.ru/logout为当前用户完成使用该服务并删除co​​okie (对于测试当前的Web服务,此过程足以模拟“唯一”启动)。测试代码本身未更改。



结果,借助简单的改进,与最初的实现相比,我们的自动测试操作时间至少减少了两倍。



testNG,pytest,pytest-bdd的测试比较



首先,我们将研究以Java编程语言在testNG测试框架上实现测试的方法,该语言与Spock Framework一样,受到jUnit框架的启发,并支持数据驱动的测试。



package javaTests;

import org.testng.Assert;
import org.testng.annotations.*;
import pages.LoginPage;


public class LoginPageTest extends BaseTest {


    @BeforeClass
    public final void setup() {
        createDriver();
        driver.get("www.anywebservice.ru");
    }

    @DataProvider(name = "userParameters")
    public final Object[][] getUserData(){
        return new Object[][] {
                {"adminLogin", "adminPassword"},
                {"moderatorLogin", "moderatorPassword"},
                {"userLogin", "userPassword"}
        };
    }

    @Test(description = "QAA-1-1: Authorization with correct login and password",
            dataProvider = "userParameters")
    public final void authorizationWithCorrectLoginAndPassword(String login, String password){
        //Login page
        LoginPage loginPage = new LoginPage(driver);

        //Log in with correct login and password
        loginPage.login(login, password);

        //Authorized and moved to main page
        Assert.assertEquals("www.anywebservice.ru/main", driver.getCurrentUrl());
    }

    @AfterMethod
    public final void cleanup() {
        driver.get("www.anywebservice.ru/logout");
        driver.manage().deleteAllCookies();
    }

    @AfterClass
    public final void tearDown() {
        driver.quit();
    }
}


在这里,我们可以看到带有所有必需的setup(),cleanup()方法以及带有@DataProvider批注的附加getUserData()方法形式的测试参数化的测试类,在使用Spock在测试中进行检查之后,看起来有点麻烦框架。另外,为了了解测试中发生的情况,留下了与步骤描述类似的注释。



值得注意的是,与Spock Framework不同,testNG支持并行测试执行。







接下来,让我们继续使用Python编程语言中的pytest测试框架进行测试。



import pytest
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from PageObjects.LoginPage import LoginPage


class TestLogin(object):

    @pytest.mark.parametrize("login,password", [
        pytest.param(("adminLogin", "adminPassword"), id='admin'),
        pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
        pytest.param(("userLogin", "userPassword"), id='user')
    ])
    def test_authorization_with_correct_login_and_password(self, login, password, driver, test_cleanup):
        # Login page
        login_page = LoginPage(driver)
        # Log in with correct login and password
        login_page.login(login, password)

        # Authorized and moved to main page
        assert expected_conditions.url_to_be("www.anywebservice.ru/main")
 
    @pytest.fixture()
    def test_cleanup(self, driver):
        yield "test"
        driver.get("www.anywebservice.ru/logout")
        driver.delete_all_cookies()


在这里,我们还将对数据驱动测试的支持视为一个单独的结构,类似于testNG中的@DataProvider。配置Web驱动程序的方法“隐藏”在驱动程序固定装置中。由于使用了动态打字和pytest固定装置,因此该测试的代码看起来比Java更干净。







接下来,让我们继续使用pytest-bdd插件对测试代码进行概述,它允许您以Gherkin功能文件的形式编写测试(纯BDD方法)。



登录功能



Feature: Login page
  A authorization

  Scenario: Authorizations with different users
    Given Login page
    When Log in with correct login and password
    Then Authorized and moved to main page


test_login.py



import pytest
from pytest_bdd import scenario, given, when, then
from selenium.webdriver.support import expected_conditions
from selenium.webdriver.support.wait import WebDriverWait

from PageObjects.LoginPage import LoginPage


@pytest.mark.parametrize("login,password", [
    pytest.param(("adminLogin", "adminPassword"), id='admin'),
    pytest.param(("moderatorLogin", "moderatorPassword"), id='moderator'),
    pytest.param(("userLogin", "userPassword"), id='user')
])
@scenario('login.feature', 'Authorizations with different users')
def test_login(login, password):
    pass


@given('Login page')
def login_page(driver):
    return LoginPage(driver)


@when('Log in with correct login and password')
def login_with_correct_login_and_password(login_page, login, password):
    login_page_object = login_page
    login_page_object.login(login, password)

@then('Authorized and moved to main page')
def authorized_and_moved_to_main_page(driver, login):
    assert expected_conditions.url_to_be("www.anywebservice.ru/main")


优点之一是它仍然是pytest框架,它具有许多用于各种情况的插件,包括用于并行运行测试的插件。缺点是纯BDD方法本身,它将不断限制开发人员自己的特征。与PyTest + pytest-bdd捆绑包相比,Spock Framework可以编写更加简洁和易于设计的代码。







结论



在本文中,我们研究了如何使用Spock框架简化使用BDD的工作。总结一下,我们简要介绍一下Spock与其他一些常见测试框架相比的主要优缺点。



优点:



  • 使用BDD原理代替纯BDD方法可为您编写测试提供更大的灵活性。
  • 书面测试规范也是系统的文档。
  • .
  • groovy ( , , closures ).


:



  • groovy. , , IDE , . Intellij IDEA, , , , .
  • groovy JVM -. , groovy, , . java, groovy .
  • 例如,扩展集不如testNG扩展。结果,没有并行测试运行。有计划添加此功能,但实施时间未知。


最终,Spock框架无疑值得关注,因为它适合解决具有较高主题和组织复杂性的软件产品的业务场景自动化的复杂常见问题。



您还能读到什么:






All Articles