软件测试金字塔

发表于:2018-3-19 10:05

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:Ham Vocke    来源:DevOps时代

  与独立服务集成
  我们的微服务与darksky.net,一个天气RESTAPI交互。当然,我们希望确保我们的服务能够正确地发送请求并解析响应。
  我们希望在运行自动化测试时避免碰到真正的darksky服务器。
  我们免费计划的配额限制只是原因的一部分。真正的原因是解耦。我们在darksky.net的测试应该独立于其他人。
  即使你的机器无法访问darksky服务器或darksky服务器因维护而停机。
  在运行我们的集成测试时,可以通过运行我们自己的虚假darksky服务器来避免碰到真正的darksky服务器。这听起来像是一项艰巨的任务。
  由于像Wiremock这样的工具,这很容易。看这个:
  @RunWith(SpringRunner.class)
  @SpringBootTest
  publicclassWeatherClientIntegrationTest{
  @Autowired
  privateWeatherClientsubject;
  @Rule
  publicWireMockRulewireMockRule=newWireMockRule(8089);
  @Test
  publicvoidshouldCallWeatherService()throwsException{
  wireMockRule.stubFor(get(urlPathEqualTo("/some-test-api-key/53.5511,9.9937"))
  .willReturn(aResponse()
  .withBody(FileLoader.read("classpath:weatherApiResponse.json"))
  .withHeader(CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
  .withStatus(200)));
  Optional<WeatherResponse>weatherResponse=subject.fetchWeather();
  Optional<WeatherResponse>expectedResponse=Optional.of(newWeatherResponse("Rain"));
  assertThat(weatherResponse,is(expectedResponse));
  }
  }
  要使用Wiremock,我们在固定端口(8089)上实例化一个WireMockRule。使用DSL可以设置Wiremock服务器,定义它应该监听的端点,并设置它应该响应的灌装响应(cannedresponses)。
  接下来我们调用想要测试的方法,即调用第三方服务的方法,并检查结果是否正确解析。
  了解测试如何知道应该调用虚拟的Wiremock服务器而不是真正的darkskyAPI非常重要。秘密在我们包含在src/test/resources中的application.properties文件中。这是运行测试时Spring加载的属性文件。在这个文件中,我们覆盖了像API键和URLs这样的配置,其值适合我们的测试目的,例如调用虚拟的Wiremock服务器而不是真正的服务器:
  weather.url=http://localhost:8089
  请注意,这里定义的端口必须与我们在测试中实例化WireMockRule时所定义的端口相同。通过在我们的WeatherClient类的构造函数中注入URL,可以将测试中的真实天气API的URL替换为假天气:
  @Autowired
  publicWeatherClient(finalRestTemplaterestTemplate,
  @Value("${weather.url}")finalStringweatherServiceUrl,
  @Value("${weather.api_key}")finalStringweatherServiceApiKey){
  this.restTemplate=restTemplate;
  this.weatherServiceUrl=weatherServiceUrl;
  this.weatherServiceApiKey=weatherServiceApiKey;
  }
  这样,我们的WeatherClient从应用程序属性中定义的weather.url属性中读取weatherUrl参数的值。
  使用Wiremock等工具为单独服务编写narrowintegrationtests非常简单。不幸的是,这种方法有一个缺点:我们如何确保我们设置的假服务器的行为像真正的服务器?
  在目前的实施中,单独的服务可能会改变其API,我们的测试仍然会通过。现在我们只是测试我们的WeatherClient可以解析假服务器发送的响应。这是一个开始,但非常脆弱。
  使用端到端测试并针对真实服务的测试实例运行测试而不是使用假服务可以解决此问题,但会使我们依赖于测试服务的可用性。
  幸运的是,有一个更好的解决方案来解决这个困境:对虚假服务器和真实服务器运行合同测试可确保我们在集成测试中使用的虚假测试是忠实的测试。让我们看看接下来的工作。
  合同测试
  更多的现代软件开发组织已经找到了通过跨不同团队开发系统来扩展其开发工作的方法。个别团队建立个别的,松散耦合的服务,而不用彼此踩脚趾,并将这些服务整合到一个大的,有凝聚力的系统中。
  最近围绕微服务的讨论正是关注这一点。
  将系统分割成许多小型服务常常意味着这些服务需要通过某些(希望定义明确的,有时意外增长的)接口相互通信。
  不同应用程序之间的接口可以有不同的形状和技术。常见的是
  REST和JSON通过HTTPS
  使用类似gRPC的RPC
  使用队列构建事件驱动的体系结构
  对于每个接口,涉及两方:提供者和消费者。该提供商向消费者提供数据。消费者处理从提供者处获得的数据。
  在REST世界中,提供者使用所有必需的端点构建RESTAPI;
  消费者调用此RESTAPI来获取数据或触发其他服务中的更改。
  在异步的,事件驱动的世界中,提供者(通常称为发布者)将数据发布到队列中;消费者(通常称为订户)订阅这些队列并读取和处理数据。
  Figure8:Eachinterfacehasaproviding(orpublishing)andaconsuming(orsubscribing)party.Thespecificationofaninterfacecanbeconsideredacontract.
  由于你经常在不同团队之间传播消费和提供服务,你会发现自己处于必须明确指定这些服务之间的接口(所谓的合同)的情况。传统上,公司通过以下方式来解决这个问题:
  编写一份详细的长期界面规范(合同)
  按照定义的合同实施提供服务
  将界面规范扔到围栏上的消费团队
  等到他们实现他们消费接口的部分
  运行一些大规模的手动系统测试,看看是否一切正常
  希望两个团队永远坚持界面定义,不要搞砸了
  更现代化的软件开发团队用更自动化的东西取代了第5步和第6步:自动契约测试确保消费者和提供者方面的实现仍然坚持已定义的合同。他们作为一个很好的回归测试套件,并确保早期发现与合同的偏差。
  在一个更敏捷的组织中,你应该采取更有效和浪费更少的路线。你在同一个组织内构建您的应用程序。直接与其他服务的开发人员直接交谈,而不是摒弃过于详细的文档,这不应该太难。毕竟他们是你的同事,而不是第三方供应商,你只能通过客户支持或法律上的防弹合同进行交谈。
  消费者驱动合同测试(CDC测试)让消费者推动合同的实施。使用CDC,接口的使用者编写测试,从接口检查接口所需的所有数据。然后消费团队发布这些测试,以便发布团队可以轻松获取并执行这些测试。支援团队现在可以通过运行CDC测试来开发他们的API。一旦所有测试通过,他们知道已经实施了消费团队所需的一切。
  Figure9:合同测试确保接口的提供者和所有消费者都坚持已定义的接口契约。通过CDC测试,接口的消费者以自动化测试的形式发布他们的需求;提供者不断地获取并执行这些测试
  这种方法允许提供团队只实施真正必要的事情(保持简单,YAGNI(Youain’tgonnaneedit)等等)。
  提供界面的团队应持续(在他们的构建流水线中)获取并运行这些CDC测试,以立即发现任何重大更改。
  如果他们更改界面,他们的CDC测试将会失败,从而阻止突发变化的发生。只要测试保持绿色,团队可以进行他们喜欢的任何更改,而不必担心其他团队。消费者驱动的合同方法会给你带来一个看起来像这样的过程:
  消费团队编写符合所有消费者期望的自动化测试
  他们为提供团队发布测试
  提供团队持续运行CDC测试并保持绿色
  一旦CDC测试中断,两个团队都会互相交流
  如果你的组织采用微服务方法,进行CDC测试是建立自治团队的重要一步。CDC测试是促进团队沟通的自动化方式。
  他们确保团队之间的界面随时都在工作。
  CDC测试失败是一个很好的指标,你应该走到受影响的团队,聊聊任何即将到来的API变化,并了解你想如何前进。
  一个原始的CDC测试实现可以像对API发起请求一样简单,并声明响应包含你需要的所有东西。然后将这些测试打包为可执行文件(.gem,.jar,.sh),并将其上传到其他团队可以获取的地方(例如Artifactory等工件存储库)。
  在过去的几年中,CDC方法变得越来越流行,并且已经构建了几种工具来使它们更容易编写和交换。
  Pact可能是最近最突出的一个。
  它具有为消费者和提供商编写测试的复杂方法,可为你提供开箱即用的独立服务存根,并允许您与其他团队交换CDC测试。
  Pact已经被移植到很多平台上,并且可以与JVM语言,Ruby,.NET,JavaScript等一起使用。
  如果您想开始使用CDC并且不知道如何,Pact可以是一个理智的选择。
  这些文档可能会在第一时间压倒一切。
  保持耐心,并努力通过它。它有助于深入了解疾病预防控制中心,从而使您在与其他团队合作时更容易倡导使用疾病预防控制中心。
  消费者驱动的合同测试(CDC)可以成为一个真正的游戏规则改变者,以建立自信的团队,可以快速而自信地行动。
  帮你自己一个忙,阅读这个概念并试一试。
  一套可靠的CDC测试对于能够快速移动而不会破坏其他服务并对其他团队造成很大的挫折,这个测试是无价的。
  消费者测试(我们的团队)
  我们的微服务使用天气API。
  因此,我们有责任编写一份消费者测试,以确定我们对微服务与天气服务之间的合同(API)的期望。
  首先,我们在build.gradle中包含一个用于编写契约消费者测试的库:
  testCompile('au.com.dius:pact-jvm-consumer-junit_2.11:3.5.5')
  感谢这个库,我们可以实现一个消费者测试并使用pact的模拟服务:
  @RunWith(SpringRunner.class)
  @SpringBootTest
  publicclassWeatherClientConsumerTest{
  @Autowired
  privateWeatherClientweatherClient;
  @Rule
  publicPactProviderRuleMk2weatherProvider=
  newPactProviderRuleMk2("weather_provider","localhost",8089,this);
  @Pact(consumer="test_consumer")
  publicRequestResponsePactcreatePact(PactDslWithProviderbuilder)throwsIOException{
  returnbuilder
  .given("weatherforecastdata")
  .uponReceiving("arequestforaweatherrequestforHamburg")
  .path("/some-test-api-key/53.5511,9.9937")
  .method("GET")
  .willRespondWith()
  .status(200)
  .body(FileLoader.read("classpath:weatherApiResponse.json"),
  ContentType.APPLICATION_JSON)
  .toPact();
  }
  @Test
  @PactVerification("weather_provider")
  publicvoidshouldFetchWeatherInformation()throwsException{
  Optional<WeatherResponse>weatherResponse=weatherClient.fetchWeather();
  assertThat(weatherResponse.isPresent(),is(true));
  assertThat(weatherResponse.get().getSummary(),is("Rain"));
  }
  }
  如果仔细观察,你会看到WeatherClientConsumerTest与WeatherClientIntegrationTest非常相似。这次我们不使用Wiremock作为服务器stub,而是使用Pact。事实上,消费者测试与集成测试完全一样,我们用一个stub替换真正的第三方服务器,定义期望的响应并检查我们的客户端是否可以正确解析响应。在这个意义上,WeatherClientConsumerTest本身就是一个小范围的集成测试。与基于线连接的测试相比,这种测试的优点是每次运行时都会生成一个pact文件(在target/pacts/&pact-name>.json中找到)。该协议文件以特殊的JSON格式描述了我们对合同的期望。然后可以使用此协议文件来验证我们的存根服务器的行为与真实服务器的行为相同。我们可以将协议文件交给提供界面的团队。他们拿这个协议文件,并使用在那里定义的期望写一个提供者测试。这样他们测试他们的API是否满足我们所有的期望。
  你会发现这是CDC消费者驱动部分的来源。
  消费者通过描述他们的期望来推动接口的实现。提供者必须确保他们能够满足所有的期望,并且他们完成了。没有镀金,没有YAGNI和东西。
  将协议文件提供给提供团队可以通过多种方式进行。一个简单的方法是将它们放入版本控制并告诉提供者团队总是获取最新版本的协议文件。更多的进步是使用工件存储库,像亚马逊S3或协议代理的服务。
  开始简单并根据需要增长。
  在你的真实世界的应用程序中,你不需要两者,一个集成测试和一个客户端类的消费者测试。示例代码库包含两个向你展示如何使用任何一个。如果你想使用pact编写CDC测试,我建议坚持使用后者。编写测试的效果是一样的。使用pact的好处是,您可以自动获得一份pact文件,其中包含对其他团队可以轻松实施其供应商测试的合同期望。当然,如果你能说服其他团队也使用pact,这是唯一有意义的。如果这不起作用,使用集成测试和Wiremock组合是一个体面的计划b。
  提供者测试(另一个团队)
  提供者测试必须由提供天气API的人员执行。我们正在使用darksky.net提供的公共API。理论上,darkskyteam将在他们的最后实施提供商测试,以检查他们是否违反了他们的应用程序和我们的服务之间的合同。
  显然,他们不关心我们微不足道的示例应用程序,也不会为我们实施CDC测试。这是面向公众的API和采用微服务的组织之间的巨大差异。面向公众的API不可能考虑每个用户,否则他们将无法前进。在你自己的组织中,可以而且应该。你的应用很可能会为少数几个用户提供服务,最多可能有几十个用户。为了保持稳定的系统,会很好地编写这些接口的提供者测试。
  提供团队获取pact文件并针对其提供的服务运行该文件。为此,他们实现了一个提供程序测试,读取该文件,存储一些测试数据,并根据他们的服务运行在pact文件中定义期望值。
  Pact伙伴已经编写了几个库来执行提供者测试。他们的主要GitHubrepo为你提供了一个很好的概览,哪个消费者和哪些提供程序库可用。选择最适合你的技术堆栈的那个。
  为了简单起见,我们假设darkskyAPI也是在SpringBoot中实现的。在这种情况下,他们可以使用Spring的pact提供者,它很好地钩入Spring的MockMVC机制。darksky.net团队将执行的假设提供者测试可能如下所示:
  @RunWith(RestPactRunner.class)
  @Provider("weather_provider")//sameasthe"provider_name"inourclientConsumerTest
  @PactFolder("target/pacts")//tellspactwheretoloadthepactfilesfrom
  publicclassWeatherProviderTest{
  @InjectMocks
  privateForecastControllerforecastController=newForecastController();
  @Mock
  privateForecastServiceforecastService;
  @TestTarget
  publicfinalMockMvcTargettarget=newMockMvcTarget();
  @Before
  publicvoidbefore(){
  initMocks(this);
  target.setControllers(forecastController);
  }
  @State("weatherforecastdata")//sameasthe"given()"inourclientConsumerTest
  publicvoidweatherForecastData(){
  when(forecastService.fetchForecastFor(any(String.class),any(String.class)))
  .thenReturn(weatherForecast("Rain"));
  }
  }
  你会看到所有提供程序测试必须执行的操作是加载一个pact文件(例如,通过使用@PactFolder注释来加载以前下载的协议文件),然后定义应如何提供预定义状态的测试数据(例如,使用Mockitomocks)。没有定制测试可以被实施。这些都来自pact文件。Providertest与消费者测试中声明的providername和状态匹配的对应对象是非常重要的。
  Provider Test(our team)
  我们已经看到如何测试我们的服务和天气提供商之间的合同。有了这个接口我们的服务作为消费者,天气服务就像提供者一样。进一步思考会看到,我们的服务还充当其他人的提供者:提供了一个RESTAPI,它准备好供其他人使用的端点。
  正如刚刚了解的那样,合同测试非常激烈,我们当然也会为这份合同写一份合同测试。幸运的是,正在使用消费者驱动契约(consumer-drivencontracts),因此所有消费团队都向我们发送他们的Pacts,我们可以使用它们来为我们的RESTAPI实现提供者测试。
  首先,将Spring的Pact提供程序库添加到项目中:
  testCompile('au.com.dius:pact-jvm-provider-spring_2.12:3.5.5')
  实现提供者测试的方式与之前描述的相同。为了简单起见,我将我们的简单消费者的pact文件输入到我们服务的存储库中。这使得目的更容易,在真实场景中,你可能会使用更复杂的机制来分发你的pact文件。
  @RunWith(RestPactRunner.class)
  @Provider("person_provider")//sameasinthe"provider_name"partinourpactfile
  @PactFolder("target/pacts")//tellspactwheretoloadthepactfilesfrom
  publicclassExampleProviderTest{
  @Mock
  privatePersonRepositorypersonRepository;
  @Mock
  privateWeatherClientweatherClient;
  privateExampleControllerexampleController;
  @TestTarget
  publicfinalMockMvcTargettarget=newMockMvcTarget();
  @Before
  publicvoidbefore(){
  initMocks(this);
  exampleController=newExampleController(personRepository,weatherClient);
  target.setControllers(exampleController);
  }
  @State("persondata")//sameasthe"given()"partinourconsumertest
  publicvoidpersonData(){
  PersonpeterPan=newPerson("Peter","Pan");
  when(personRepository.findByLastName("Pan")).thenReturn(Optional.of
  (peterPan));
  }
  }
  所示的ExampleProviderTest需要根据我们提供的pact文件提供状态,就是这样。一旦运行提供程序测试,Pact就会拿起pact文件并针对我们的服务发起HTTP请求,然后根据设置的状态做出响应。
  UI Tests
  大多数应用程序都有某种用户界面。
  通常,我们正在讨论Web应用程序环境中的Web界面。人们经常忘记RESTAPI或命令行界面与花哨的Web用户界面一样多的用户界面。
  UItests测试应用程序的用户界面是否正常工作。用户输入应该触发正确的操作,数据应该呈现给用户,UI状态应该按预期改变。
  UI测试和端到端测试有时(如MikeCohn的案例)被认为是一回事。
  对我来说,这是两个相互正交的概念。
  是的,端到端测试你的应用程序通常意味着通过用户界面来驱动您的测试。
  然而,反过来却是不正确的。
  测试你的用户界面不一定要以端到端的方式进行。
  根据你使用的技术,测试用户界面可能非常简单,只需为后端JavaScript代码编写一些单元测试并将其后端代码删除即可。
  使用传统的Web应用程序测试用户界面可以使用像Selenium这样的工具来实现。如果你认为RESTAPI是你的用户界面,应该通过围绕API编写适当的集成测试来获得所需的一切。
  有了Web界面,可能需要在UI中测试多个方面:行为,布局,可用性,很少对公司设计的测试。
  幸运的是,测试用户界面的行为非常简单。你点击这里,在那里输入数据,并希望用户界面的状态相应地改变。现代的单页面应用程序框架(react,vue.js,Angular等)通常带有自己的工具和helpers,它们允许您以相当低级的(单元测试)方式彻底测试这些交互。即使你使用vanillajavascript来实现自己的前端实现,你也可以使用常规的测试工具,如Jasmine或Mocha。使用更传统的服务器端渲染应用程序,基于Selenium的测试将是你的最佳选择。
  测试你的web应用程序的布局是否保持完好,有点困难。根据你的应用程序和你的用户需求,可能需要确保代码更改不会意外地破坏网站的布局。
  问题在于,计算机在检查某些“看起来不错”(可能是一些聪明的机器学习算法可能在将来改变)方面是非常糟糕的。
  如果你想要在构建管道中自动检查Web应用程序的设计,有一些工具可供尝试。这些工具中的大多数都利用Selenium以不同的浏览器和格式打开您的Web应用程序,截取屏幕截图并将它们与以前拍摄的截图进行比较。如果新旧截图以意想不到的方式出现差异,该工具会通知您。
  Galen是这些工具之一。
  但是,如果您有特殊要求,即使推出自己的解决方案也不难。和我有合作的一些团队已经建立了阵容和基于Java的表哥jimupup来实现类似的功能。两种工具都采用了我之前描述的基于Selenium的方法。
  一旦你想测试可用性和“看起来不错”的因素,你就离开了自动化测试领域。这是你应该依赖探索性测试,可用性测试(这甚至可以像走廊测试一样简单)的领域,并向用户展示他们是否喜欢使用你的产品,并且可以使用所有功能而不会感到沮丧或烦恼。
  端到端测试
  通过用户界面测试已部署的应用程序是你可以测试应用程序的最为端到端的方式。前面描述的webdriver驱动的UI测试是端到端测试的一个很好的例子。
  当你需要确定软件是否正常工作时,端到端测试(也称为广泛堆栈测试)为你提供最大的信心。通过Selenium和WebDriver协议,你可以通过自动驱动(无头)浏览器针对部署的服务,执行点击操作,输入数据并检查用户界面的状态来自动执行测试。您可以直接使用Selenium或使用基于它的工具,Nightwatch就是其中之一。
  端到端测试带来了各自的问题。
  它们是出了名的碎片,往往因意外和不可预见的原因而失败。他们的失败往往是一种误解。你的用户界面越复杂,测试越碎片。浏览器的怪癖,计时问题,动画和意外的弹出对话框只是让我花更多时间进行调试的一些原因,而不是我想承认的。
  在微服务世界中,谁负责编写这些测试也是一个大问题。由于它们跨越多个服务(整个系统),因此没有一个团队负责编写端到端测试。
  如果你有一个集中的质量保证团队,他们看起来很合适。
  然而,拥有一个集中的质量保证团队是一个很大的反模式,在DevOps世界里不应该有一席之地,你的团队是真正意义上的跨职能团队。谁应该拥有端到端的测试并不容易。也许你的组织有一个实践社区或一个可以照顾这些的高质量公会。找到正确的答案在很大程度上取决于你的组织。
  此外,端到端测试需要大量维护,运行速度非常缓慢。
  考虑到不止一两个微服务的格局,你甚至无法在本地运行端到端测试-因为这需要在本地启动所有微服务。在你的开发机器上启动了数百个应用程序,而不会炸毁你的RAM。
  由于维护成本高昂,应该尽量减少端到端测试的数量。
  考虑用户在应用程序中使用的高价值交互。
  尝试提出定义产品核心价值的用户旅程,并将这些用户旅程中最重要的步骤转化为自动化的端到端测试。
  如果你正在建立一个电子商务网站,你最有价值的客户旅程可能是一个用户搜索产品,将其放入购物篮并结帐。仅此而已。
  只要这个旅程仍然有效,不应该太麻烦。
  也许你会发现一两个更重要的用户旅程,可以将其转化为端到端测试。
  除此之外,更多的事情都可能更痛苦。
  请记住:你的测试金字塔中有很多较低的级别,已经测试了各种边界案例并与系统的其他部分进行了集成。没有必要在更高层次上重复这些测试。高昂的维护工作量和大量的误报会减慢你的速度,会让你在测试中失去信心,宜早不宜迟。
  User Interface End-to-End Test
  对于端到端测试,Selenium和WebDriver协议是许多开发人员首选的工具。使用Selenium,您可以选择一个你喜欢的浏览器,然后让它自动调用你的网站,点击界面这里和那里,输入数据并检查用户界面中的变化。
  Selenium需要一个可以启动并用于运行测试的浏览器。对于不同的浏览器,可以使用多个所谓的“驱动程序”。
  选择一个(或多个)并将其添加到您的build.gradle。
  无论你选择哪种浏览器,都需要确保团队中的所有开发人员和你的CI服务器在本地安装了正确版本的浏览器。保持同步可能会非常痛苦。对于Java,有一个很好的小型库叫做webdrivermanager,它可以自动下载并设置你想要使用的正确版本的浏览器。将这两个依赖关系添加到你的build.gradle中,然后可以继续:
  testCompile('org.seleniumhq.selenium:selenium-chrome-driver:2.53.1')
  testCompile('io.github.bonigarcia:webdrivermanager:1.7.2')
  在测试套件中运行完整的浏览器可能会很麻烦。
  特别是在使用持续交付时,运行管道的服务器可能无法启动包含用户界面的浏览器(例如因为没有X-Server可用)。
  您可以通过启动像xvfb这样的虚拟X-Server来解决此问题。
  最近的方法是使用无头浏览器(即没有用户界面的浏览器)来运行webdriver测试。直到最近PhantomJS是领先的自动化的无头浏览器。
  自从Chromium和Firefox宣布他们在浏览器中实现无头模式后,PhantomJS突然变得过时了。
  毕竟,最好使用用户实际使用的浏览器(比如Firefox和Chrome)来测试网站,而不是仅仅作为开发人员方便你使用仿真浏览器。
  无头的Firefox和Chrome都是全新的,并且尚未被广泛采用来执行webdriver测试。我们想保持简单。
  而不是摆弄无边无际的模式,让我们坚持使用Selenium和普通浏览器的经典方式吧。一个简单的端到端测试,使用Chrome浏览器,导航到我们的服务,并检查网站的内容如下所示:
  @RunWith(SpringRunner.class)
  @SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)
  publicclassHelloE2ESeleniumTest{
  privateWebDriverdriver;
  @LocalServerPort
  privateintport;
  @BeforeClass
  publicstaticvoidsetUpClass()throwsException{
  ChromeDriverManager.getInstance().setup();
  }
  @Before
  publicvoidsetUp()throwsException{
  driver=newChromeDriver();
  }
  @After
  publicvoidtearDown(){
  driver.close();
  }
  @Test
  publicvoidhelloPageHasTextHelloWorld(){
  driver.get(String.format("http://127.0.0.1:%s/hello",port));
  assertThat(driver.findElement(By.tagName("body")).getText(),containsString("HelloWorld!"));
  }
  }
  请注意,如果你在运行此测试的系统(本地计算机,你的CI服务器)上安装了Chrome,该测试将仅在你的系统上运行。测试很简单。它使用@SpringBootTest在一个随机端口上运行整个Spring应用程序。然后,我们实例化一个新的Chrome浏览器驱动程序,告诉它导航到我们的微服务的/hello端点,并检查它是否在浏览器窗口中打印出“HelloWorld!”。这是很酷的东西!
  REST API End-to-End Test
  在测试应用程序时避免使用图形用户界面是一个好主意,它可以提供比较完整的端到端测试,同时仍涵盖应用程序堆栈的大部分内容。当通过应用程序的Web界面进行测试特别困难时,这可以派上用场。也许你甚至没有一个WebUI,而是提供一个RESTAPI来代替(因为你有一个单独的页面应用程序在某个地方与该API交谈,或者仅仅是因为你鄙视一切都很好)。无论哪种方式,一个SubcutaneousTest,只是在图形用户界面下进行测试,并且可以让你真正走远,而不会对信心造成太大损失。如果你像我们的示例代码那样提供RESTAPI,那就是正确的做法:
  @RestController
  publicclassExampleController{
  privatefinalPersonRepositorypersonRepository;
  //shortenedforclarity
  @GetMapping("/hello/{lastName}")
  publicStringhello(@PathVariablefinalStringlastName){
  Optional<Person>foundPerson=personRepository.findByLastName(lastName);
  returnfoundPerson
  .map(person->String.format("Hello%s%s!",
  person.getFirstName(),
  person.getLastName()))
  .orElse(String.format("Whoisthis'%s'you'retalkingabout?",
  lastName));
  }
  }
  当我测试一个提供RESTAPI的服务时,让我再向您展示一个更方便的库。
  REST-assured
  是一个为你提供一个很好的DSL的库,用于发送针对API的实际HTTP请求并评估你收到的响应。
  首先要做的事情是:将依赖关系添加到build.gradle中。
  testCompile('io.rest-assured:rest-assured:3.0.3')
  借助这个库,我们可以为我们的RESTAPI实施端到端测试:
  @RunWith(SpringRunner.class)
  @SpringBootTest(webEnvironment=SpringBootTest.WebEnvironment.RANDOM_PORT)
  publicclassHelloE2ERestTest{
  @Autowired
  privatePersonRepositorypersonRepository;
  @LocalServerPort
  privateintport;
  @After
  publicvoidtearDown()throwsException{
  personRepository.deleteAll();
  }
  @Test
  publicvoidshouldReturnGreeting()throwsException{
  Personpeter=newPerson("Peter","Pan");
  personRepository.save(peter);
  when()
  .get(String.format("http://localhost:%s/hello/Pan",port))
  .then()
  .statusCode(is(200))
  .body(containsString("HelloPeterPan!"));
  }
  }
  再次,我们使用@SpringBootTest启动整个Spring应用程序。
  在这种情况下,我们@AutowirePersonRepository,以便我们可以轻松地将测试数据写入我们的数据库。当我们现在要求RESTAPI向我们的朋友“潘先生”说“打招呼”时,我们会得到一个很好的问候。非常好!如果你甚至没有运行网络界面,那么就可以进行足够多的端到端测试。
  Acceptance Tests—Do Your Features Work Correctly?
  在测试金字塔中移动得越高,进入测试领域的可能性就越大,从用户的角度来看,你构建的功能是否正常工作。
  可以将你的应用程序视为黑盒子,并将测试中的焦点从下面中移除
  当我输入值x和y时,返回值应该是z
  而是用
  因为有一个登录用户(giventhere'saloggedinuser)
  还有一篇文章“自行车”(andthere'sanarticle"bicycle")
  当用户导航到“自行车”文章的详细页面时(whentheusernavigatestothe"bicycle"article'sdetailpage)
  并点击“添加到篮子”按钮(andclicksthe"addtobasket"button)
  那么文章“自行车”应该在他们的购物篮中(thenthearticle"bicycle"shouldbeintheirshoppingbasket)
  有时你会听到这些测试的功能测试或验收测试的条款。
  有时人们会告诉你功能和验收测试是不同的东西。这些术语是混淆的。甚至有时候人们会无休止地讨论措辞和定义。通常这种讨论会引起相当大的混乱。这才重要:在某一时刻,你应该确保从用户的角度测试软件是否正常工作,而不仅仅是从技术角度。你认为这些测试真的不是那么重要。
  然而,进行这些测试是有必要的。
  选择一个,坚持下去,然后编写这些测试。
  这也是人们谈论BDD和使您能够以BDD方式实施测试的工具的时刻。
  BDD或BDD风格的编写测试方式可能是一个不错的窍门,可将你的思想从实施细节转移到用户需求。继续尝试吧。
  你甚至不需要像Cucumber那样采用全面的BDD工具(尽管你可以)。
  有些断言库(比如chai.js允许你用should样式的关键字来编写断言,这样可以让你的测试能够读取更多类似于BDD的内容。即使你不使用提供这种表示法的库,聪明且分工合理的代码将允许你编写以用户行为为中心的测试。一些辅助方法/函数可以为你带来很长的路要走:
  #asampleacceptancetestinPython
  deftest_add_to_basket():
  #given
  user=a_user_with_empty_basket()
  user.login()
  bicycle=article(name="bicycle",price=100)
  #when
  article_page.add_to_.basket(bicycle)
  #then
  assertuser.basket.contains(bicycle)
  验收测试可以有不同的粒度级别。
  大多数时候他们将会相当高级并通过用户界面测试您的服务。然而,理解在技术上不需要在测试金字塔的最高级别编写验收测试是很好的。
  如果你的应用程序设计和手头的场景允许您在较低的级别上编写验收测试,那就去做吧。进行低级测试比进行高级测试要好。验收测试的概念-证明功能为用户正确地工作-完全与测试金字塔正交。
  Exploratory Testing
  即使是最用功的自动化测试也不完美。有时候你会错过自动化测试中的某些边缘情况。有时通过编写单元测试来检测特定的错误几乎是不可能的。某些质量问题在您的自动化测试中甚至不明显(考虑设计或可用性)。尽管你对测试自动化有着最好的意图,但某些类型的手动测试仍然是一个好主意。
  Figure12:Useexploratorytestingtospotallqualityissuesthatyourbuildpipelinedidn’tspot
  在测试组合中包含探索性测试。这是一种手动测试方法,强调测试人员的自由和创造力,以便在运行中的系统中发现质量问题。
  只需定期安排一些时间,卷起袖子并尝试破坏应用程序。
  使用破坏性的思维方式,想出办法在应用程序中引发问题和错误。记录您以后找到的所有内容。
  注意错误,设计问题,响应时间缓慢,丢失或误导性的错误信息以及其他一切会让你作为软件用户烦恼的事情。
  好消息是,你可以使用自动化测试你大部分发现。为你发现的错误编写自动化测试,确保将来不会出现该错误的任何回退。此外,它还可以帮助在错误修复期间缩小问题的根源。
  在探索性测试过程中,你会发现通过你的构建管道未被注意到的问题。不要感到沮丧。这对您的构建管道的成熟度有很好的反馈。
  与任何反馈一样,请务必采取行动:考虑你将来可以采取什么措施来避免这些问题。也许你错过了一些自动化测试。
  也许在这次迭代中对自动化测试嗤之以鼻,并且需要在将来进行更彻底的测试。也许有一种闪亮的新工具或方法可以用来避免将来出现这些问题。
  请务必采取行动,以便管道和整个软件交付将走得更远变得更加成熟。
  关于Testing Terminology的结论
  谈论不同的测试分类总是很困难。
  当我谈论单元测试时,我的意思可能与你的理解稍有不同。
  如果是集成测试,情况更糟。
  对于某些人来说,集成测试是一项非常广泛的活动,可以测试整个系统的许多不同部分。对我而言,这是一个相当狭隘的东西,一次只测试一个外部部件的集成。一些人称他们为集成测试,一些人称他们为组件测试,一些人更喜欢术语服务测试。
  甚至其他人也会争辩说,所有这三个术语都是完全不同的东西。没有对错。
  软件开发社区根本没有设法围绕测试定义明确的术语。
  不要太拘泥于模棱两可的话。
  如果您称之为端到端或广泛的堆栈测试或功能测试,则无关紧要。
  如果你的集成测试对你来说意味着与其他公司的人不同,那就没关系了。是的,如果我们的专业能够按照一些明确定义的条件解决并且全部坚持下去,那将会非常好。不幸的是,这还没有发生。
  而且,由于在编写测试时有很多细微差别,反而比一堆离散的存储桶更像一个频谱,这使得一致的命名更加困难。
  重要的是,你应该找到适合你和你的团队的条款。清楚你想写的不同类型的测试。就团队中的命名达成一致,并就每种类型的测试范围达成共识。如果你在团队内部(或者甚至在你的组织内)获得这种一致性,那么你应该关心的就是这些。当SimonStewart描述他们在Google使用的方法时,SimonStewart总结得非常好。
  而且我认为这完全表明,让名字和命名惯例过于沉闷是不值得的麻烦。
  Putting Tests Into Your Depolyment Pipeline
  如果你使用的是持续集成或持续交付,那么将拥有一个部署管道,每次对软件进行更改时都会运行自动化测试。
  通常这个管道分成几个阶段,逐渐让你更加确信软件已准备好部署到生产环境。听到所有这些不同类型的测试,你可能想知道如何将它们放置在部署管道中。要回答这个问题,应该考虑持续交付(实际上是极限编程和敏捷软件开发的核心价值之一)的基本价值之一:快速反馈。
  一个好的构建管道告诉你,尽可能快地搞砸。你不想等一个小时才能发现你的最新代码更改破坏了一些简单的单元测试。如果你的管道需要很长时间才能给反馈,那么你很可能已经回家了。通过快速运行的测试放在流水线的早期阶段,可以在几秒钟内获得这些信息,也可能需要几分钟。相反,在较晚的阶段,较长时间的运行测试(通常是较宽范围的测试)放在不会推迟快速运行测试的反馈。你会发现定义部署管道的阶段不是由测试类型驱动的,而是由速度和范围决定的。考虑到这一点它可以是一个非常合理的决定,把一些真正的狭义范围的和快速运行的集成测试在同一个舞台上你的单元测试-仅仅是因为他们给你更快的反馈,而不是因为你想画沿着你的测试的正式类型。
  Avoid Tes tDuplication
  现在你知道你应该写出不同类型的测试,但还有一个可以避免的错误:在金字塔的不同层次上重复测试。
  虽然你的直觉可能会说不需要太多的测试。我向你保证,需要。
  测试套件中的每一项测试都需要额外的时间,并不是免费的。
  编写和维护测试需要时间。阅读和理解其他人的测试需要时间。
  当然,运行测试需要时间。
  与生产代码一样,应该尽量简化并避免重复。
  在实施你的测试金字塔的背景下,你应该记住两条经验法则:
  1、如果较高级别的测试发现错误,并且没有较低级别的测试失败,则需要编写较低级别的测试
  2、尽可能将测试推到测试金字塔的尽头。
  第一条规则很重要,因为较低级别的测试可以让你更好地缩小错误并以独立方式复制错误。当调试手头的问题时,它们会运行得更快,并且不会臃肿。它们将成为未来良好的回归测试。
  第二条规则对于快速保持测试套件非常重要。
  如果你已经在较低级别的测试中自信地测试了所有条件,则不需要在测试套件中保留更高级别的测试。它只是没有增加更多的信心。
  多余的测试会在日常工作中变得烦人。
  你的测试套件会变慢,当你改变代码的行为时你需要改变更多的测试。
  让我们以不同的方式来表述:如果更高级别的测试让你更加确信应用程序正常工作,那么你应该拥有它。
  为Controller类编写单元测试有助于测试Controller本身的逻辑。
  不过,这并不能告诉这个Controller提供的REST端点是否实际响应HTTP请求。所以,移动测试金字塔并添加一个测试来检查确切的-但没有更多。
  你不要测试低级测试已经在高级测试中覆盖的所有条件逻辑和边界情况。
  确保较高级别的测试侧重于较低级别测试无法覆盖的部分。
  当涉及到不提供任何价值的测试时,我非常严格。
  我删除了较低级别的高级测试(因为它们不提供额外的值)。
  如果可能的话,我用较低级别的测试替换更高级别的测试。
  有时候这很难,特别是如果你知道提出一个测试是艰苦的工作。
  谨防沉没成本谬误并敲击删除键。
  没有理由浪费更多宝贵的时间在不再提供价值的测试上。
  Writing Clean Test Code
  就像一般编写代码一样,写出良好和干净的测试代码需要非常细心。
  在继续之前提出可维护的测试代码以及在自动化测试套件中破解,以下是一些更多提示:
  1、测试代码与生产代码一样重要。给它同样的关注。“这只是测试代码”不是证明草率代码合理的理由
  2、每个测试测试一个条件。这可以帮助你保持测试简短并且容易推理
  3、“安排,采取行动,断言”或“当时,那么”是很好的助记符,可以让你的测试保持良好的结构
  4、可读性很重要。不要试图过度DRY(Don’trepeatyourself)。复制是可以的,如果它提高可读性的话。尝试在DRY和DAMP代码之间找到平衡点
  5、如果有疑问,请使用三条规则来决定何时重构。重用之前使用
  结论
  就这样!我知道这是一个漫长而艰难的阅读,解释为什么以及如何测试你的软件。好消息是,这些信息是持久有用的,并且无论你正在构建什么样样的软件。无论您是从事微服务领域,物联网设备,移动应用程序还是Web应用程序,这些文章的教训都可以应用到所有这些领域。
  我希望在这篇文章中有一些有用的东西。
  现在继续查看示例代码,并将这里介绍的一些概念加入到您的测试组合中。有一个坚实的测试组合需要一些努力。
  它将会在更长的时间内得到回报,并且会让你的开发者更加安宁,相信我。
22/2<12
《2023软件测试行业现状调查报告》独家发布~

精彩评论

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号