让我们想象一个假设的情景(我们经常陷入其中)。您已被分配到“自动化”自动化项目。您将获得一个庞大的测试计划,其中包含大量(数千个!)“手动”测试,他们说您需要做一些事情,然后就在那里。而且,要快速稳定。编写单元测试,甚至考虑TDD都为时已晚,产品代码早就被编写了。您的话,自动测试仪同志!
幸运的是,有一个小技巧可以增加覆盖率并使测试稳定,快速-皮下测试(“皮下测试”),但首先要注意的是。问题的实质
自动化程序的第一个条件反射是摄取Selenium(好吧,在那里,Selenide或其他一些神童用于UI测试)。这是一个行业标准,但是有很多原因导致它没有成功:- UI测试很慢。这是无法逃脱的。它们可以并行运行,归档并可以更快地完成,但是它们将保持缓慢。
- UI测试不稳定。部分原因是它们很慢。而且还因为未创建Web浏览器和用户界面来由计算机控制(这种趋势目前正在改变,但事实并非如此)。
- UI- — . . ( , , , «» - , ).
- , , , UI- . . ID XPath . , «» - - . , , — - , .
- 有人会说某些功能根本无法进行测试。我会说,如果有只能通过UI测试来测试的功能(UI逻辑本身除外),这可能是产品中体系结构问题的一个好兆头。
UI测试的唯一真正优势在于,它们使您可以“投入”或多或少地进行有用的检查,而无需深入研究产品本身的代码。从长远来看,这几乎不是一个加号。为什么在本演示文稿中可以听到更详细的解释。替代解决方案
作为一个非常简单的例子,让我们考虑一个包含表单的应用程序,您可以在其中输入有效的用户名。如果输入与规则匹配的用户名-用户将在系统中创建并记录在数据库中。
该应用程序的源代码可以在这里找到:github.com/senpay/login-form。警告您-在应用程序中有很多错误,没有时髦的工具和框架。如果您尝试为该应用程序“投掷”一张检查表,则可以获得类似以下内容的信息:看起来简单吗?只是!我可以编写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级别上编写功能性自动测试。在许多情况下使用“皮下测试”将以更高的速度和稳定性测试相同的功能。
- 该方法的缺点之一是覆盖范围的一定损失。
- 避免失去覆盖范围的一种方法是“ 功能测试模型 ”
- 但是,即使失去了覆盖范围,速度和稳定性的提高也是显着的。
此处
提供英文版本。