有效编写自动测试-皮下测试

让我们想象一个假设的情景(我们经常陷入其中)。您已被分配到“自动化”自动化项目。您将获得一个庞大的测试计划,其中包含大量(数千个!)“手动”测试,他们说您需要做一些事情,然后就在那里。而且,要快速稳定。

编写单元测试,甚至考虑TDD都为时已晚,产品代码早就被编写了。您的话,自动测试仪同志!

图片

幸运的是,有一个小技巧可以增加覆盖率并使测试稳定,快速-皮下测试(“皮下测试”),但首先要注意的是。


问题的实质


自动化程序的第一个条件反射是摄取Selenium(好吧,在那里,Selenide或其他一些神童用于UI测试)。这是一个行业标准,但是有很多原因导致它没有成功:

  • UI测试很慢。这是无法逃脱的。它们可以并行运行,归档并可以更快地完成,但是它们将保持缓慢。
  • UI测试不稳定。部分原因是它们很慢。而且还因为未创建Web浏览器和用户界面来由计算机控制(这种趋势目前正在改变,但事实并非如此)。
  • UI- — . . ( , , , «» - , ).
  • , , , UI- . . ID XPath . , «» - - . , , — - , .
  • 有人会说某些功能根本无法进行测试。我会说,如果有只能通过UI测试来测试的功能(UI逻辑本身除外),这可能是产品中体系结构问题的一个好兆头。

UI测试的唯一真正优势在于,它们使您可以“投入”或多或少地进行有用的检查,而无需深入研究产品本身的代码。从长远来看,这几乎不是一个加号。为什么在本演示文稿中可以听到更详细的解释

替代解决方案


作为一个非常简单的例子,让我们考虑一个包含表单的应用程序,您可以在其中输入有效的用户名。如果输入与规则匹配的用户名-用户将在系统中创建并记录在数据库中。



该应用程序的源代码可以在这里找到:github.com/senpay/login-form警告您-在应用程序中有很多错误,没有时髦的工具和框架。

如果您尝试为该应用程序“投掷”一张检查表,则可以获得类似以下内容的信息:
脚步预期成绩
1个1.输入有效的用户名
。2.单击“登录”按钮。
1.
2.创建一个新用户。
21.输入一个空用户名
。2.单击“登录”按钮。
1.
2.给出错误信息。

看起来简单吗?只是!我可以编写UI测试吗?能够。如果您转到git中uitests标签git checkout uitests),则可以在LoginFormTest.java中找到一个书面测试的示例(以及完整的三级框架):

public class LoginFormTest {

    SelenideMainPage sut = SelenideMainPage.INSTANCE;
    private static final String APPLICATION_URL = "http://localhost:4567/index";

    @BeforeClass
    public static void setUpClass() {
        final String[] args = {};
        Main.main(args);
        Configuration.browser = "firefox";
    }

    @Before
    public void setUp() {
        open(APPLICATION_URL);
    }

    @After
    public void tearDown() {
        close();
    }

    @Test
    public void shouldBeAbleToAddNewUser() {
        sut.setUserName("MyCoolNewUser");
        sut.clickSubmit();
        Assert.assertEquals("Status: user MyCoolNewUser was created", sut.getStatus());
        Assert.assertTrue(sut.getUsers().contains("Name: MyCoolNewUser"));
    }

    @Test
    public void shouldNotBeAbleToAddEmptyUseName() {
        final int numberOfUsersBeforeTheTest = sut.getUsers().size();
        sut.clickSubmit();
        Assert.assertEquals("Status: Login cannot be empty", sut.getStatus());
        Assert.assertEquals(numberOfUsersBeforeTheTest, sut.getUsers().size());
    }
}


此代码的一些度量标准:
运行时:〜12 (上次我运行这些测试时为12秒956毫秒)
代码覆盖率
类别:100%
方法:93.8%(30/32)
行:97.4%(75/77) )

现在,假定可以在UI的“正下方”级别编写功能自动测试。这项技术称为皮下测试(“ subderma test”),是在很早以前就在显示逻辑水平以下进行测试的技术,由Martin Fowler于很久以前提出[ 1 ]。

当人们想到“非UI”自动测试时,常常会立即想到REST / SOAP或其API。但是API(应用程序编程接口)是一个更广泛的概念,不一定影响HTTP和其他重量级协议。

如果选择产品代码,我们会发现一些有趣的东西:
public class UserApplication {

    private static IUserRepository repository = new InMemoryUserRepository();
    private static UserService service = new UserService(); {
        service.setUserRepository(repository);
    }

    public Map<String, Object> getUsersList() {
        return getUsersList("N/A");
    }

    public Map<String, Object> addUser(final String username) {
        final String status = service.addUser(username);
        final Map<String, Object> model = getUsersList(status);
        return model;
    }

    private Map<String, Object> getUsersList(String status) {
        final Map<String, Object> model = new HashMap<>();
        model.put("status", status);
        model.put("users", service.getUserInfoList());
        return model;
    }
}


当我们单击UI时,将调用这些方法之一,或者添加新的User对象,或者返回已创建的User对象的列表。如果我们直接使用这些方法怎么办?毕竟,这是一个真正的API!最重要的是,REST和其他API也以相同的原理工作-它们称为“控制器级别”的某种方法。

直接使用这些方法,我们可以编写一个更简单,更好的测试:
public class UserApplicationTest {

    private UserApplication sut;

    @Before
    public void setUp() {
       sut = new UserApplication();
    }

    @Test
    public void shouldBeAbleToAddNewUser() {
        final Map<String, Object> myCoolNewUser = sut.addUser("MyCoolNewUser");
        Assert.assertEquals("user MyCoolNewUser was created", myCoolNewUser.get("status"));
        Assert.assertTrue(((List) myCoolNewUser.get("users")).contains("Name: MyCoolNewUser"));
    }

    @Test
    public void shouldNotBeAbleToAddEmptyUseName() {
        final Map<String, Object> usersBeforeTest = sut.getUsersList();
        final int numberOfUsersBeforeTheTest = ((List) usersBeforeTest.get("users")).size();
        final Map<String, Object> myCoolNewUser = sut.addUser("");
        Assert.assertEquals("Login cannot be empty", myCoolNewUser.get("status"));
        Assert.assertEquals(numberOfUsersBeforeTheTest, ((List) myCoolNewUser.get("users")).size());
    }
}


此代码在subctests标签下可用

git checkout subctests


让我们尝试收集指标吗?
执行时间〜21 毫秒
代码覆盖率
类:77.8%
方法:78.1(30/32)
行:78.7(75/77)

我们失去了一点覆盖范围,但是测试速度提高了600倍!!!

在这种情况下,承保范围丧失的重要性/重要性是什么?视情况而定。我们丢失了一些粘合代码,这可能很重要(或者可能不重要)(我建议在练习中确定要丢失的代码)。

这种覆盖范围的损失是否足以证明在UI级别引入重量级测试?这也取决于情况。例如,我们可以:
  • 添加一个UI测试以检查粘合代码,或者
  • 如果我们不希望对粘连代码进行频繁的更改-请不要进行自动测试,或者
  • 如果我们进行某种“手动”测试,那么测试人员很可能会发现胶水代码存在问题,或者
  • 提出其他建议(与Canary部署相同)


最终


  • 不需要在UI或REST / SOAP API级别上编写功能性自动测试。在许多情况下使用“皮下测试”将以更高的速度和稳定性测试相同的功能。
  • 该方法的缺点之一是覆盖范围的一定损失。
  • 避免失去覆盖范围的一种方法是“ 功能测试模型
  • 但是,即使失去了覆盖范围,速度和稳定性的提高也是显着的。


此处 提供英文版本

All Articles