byteman_使用Byteman和JUnit进行故障注入

byteman

我们的应用程序独立存在的时间已经很久了。 如今,应用程序是一种非常复杂的野兽,它们使用无数的API和协议相互通信,将数据存储在传统或NoSQL数据库中,通过网络发送消息和事件……例如,您多久考虑一次例如数据库的情况当您的应用程序正在主动查询时发生故障? 还是某个API端点突然开始拒绝连接? 将此类事故作为测试套件的一部分覆盖不是很好吗? 这就是故障注入和Byteman框架所要解决的问题。 例如,我们将构建一个现实的,功能完善的Spring应用程序,该应用程序使用Hibernate / JPA访问MySQL数据库并管理客户。 作为应用程序的JUnit集成测试套件的一部分,我们将包括三种测试用例:

  • 储存/寻找顾客
  • 存储客户并尝试在数据库宕机时查询数据库(故障模拟)
  • 存储客户和数据库查询超时(故障模拟)

在本地开发箱上运行应用程序只有两个先决条件:

  • MySQL服务器已安装并具有客户数据库
  • 已安装Oracle JDK ,并且JAVA_HOME环境变量指向它

话虽这么说,我们已经准备好出发了。 首先,让我们描述我们的域模型,该域模型由具有ID和单个属性名的单个Customer类组成。 看起来很简单:

package com.example.spring.domain;import java.io.Serializable;import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;@Entity
@Table( name = "customers" )
public class Customer implements Serializable{private static final long serialVersionUID = 1L;@Id@GeneratedValue@Column(name = "id", unique = true, nullable = false)private long id;@Column(name = "name", nullable = false)private String name;public Customer() {}public Customer( final String name ) {this.name = name;}public long getId() {return this.id;}protected void setId( final long id ) {this.id = id;}public String getName() {return this.name;}public void setName( final String name ) {this.name = name;}
}

为简单起见,服务层与数据访问层混合在一起并直接调用数据库。 这是我们的CustomerService实现:

package com.example.spring.services;import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;import com.example.spring.domain.Customer;@Service
public class CustomerService {@PersistenceContext private EntityManager entityManager;@Transactional( readOnly = true )public Customer find( long id ) {return this.entityManager.find( Customer.class, id );}@Transactional( readOnly = false )public Customer create( final String name ) {final Customer customer = new Customer( name );this.entityManager.persist(customer);return customer;}@Transactional( readOnly = false )public void deleteAll() {this.entityManager.createQuery( "delete from Customer" ).executeUpdate();}
}

最后, Spring应用程序上下文定义了数据源和事务管理器。 这里需要注意的一点是:由于我们不会引入数据访问层( @Repository )类,为了使Spring正确执行异常转换,我们将PersistenceExceptionTranslationPostProcessor实例定义为后处理服务类( @Service )。 其他一切都应该非常熟悉。

package com.example.spring.config;import java.util.Properties;import javax.sql.DataSource;import org.hibernate.dialect.MySQL5InnoDBDialect;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.stereotype.Service;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;import com.example.spring.services.CustomerService;@EnableTransactionManagement
@Configuration
@ComponentScan( basePackageClasses = CustomerService.class )
public class AppConfig {@Beanpublic PersistenceExceptionTranslationPostProcessor exceptionTranslationPostProcessor() {final PersistenceExceptionTranslationPostProcessor processor = new PersistenceExceptionTranslationPostProcessor();processor.setRepositoryAnnotationType( Service.class );return processor;}@Beanpublic HibernateJpaVendorAdapter hibernateJpaVendorAdapter() {final HibernateJpaVendorAdapter adapter = new HibernateJpaVendorAdapter();adapter.setDatabase( Database.MYSQL );adapter.setShowSql( false );return adapter;}@Beanpublic LocalContainerEntityManagerFactoryBean entityManager() throws Throwable {final LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();entityManager.setPersistenceUnitName( "customers" );entityManager.setDataSource( dataSource() );entityManager.setJpaVendorAdapter( hibernateJpaVendorAdapter() );final Properties properties = new Properties();properties.setProperty("hibernate.dialect", MySQL5InnoDBDialect.class.getName());properties.setProperty("hibernate.hbm2ddl.auto", "create-drop" );entityManager.setJpaProperties( properties );return entityManager;}@Beanpublic DataSource dataSource() {final DriverManagerDataSource dataSource = new DriverManagerDataSource();dataSource.setDriverClassName( com.mysql.jdbc.Driver.class.getName() );dataSource.setUrl( "jdbc:mysql://localhost/customers?enableQueryTimeouts=true" );dataSource.setUsername( "root" );dataSource.setPassword( "" );return dataSource;}@Beanpublic PlatformTransactionManager transactionManager() throws Throwable {return new JpaTransactionManager( this.entityManager().getObject() );}
}

现在,让我们添加一个简单的JUnit测试用例,以验证我们的Spring应用程序确实按预期工作。 在此之前,应创建数据库客户

> mysql -u root
mysql> create database customers;
Query OK, 1 row affected (0.00 sec)

这是一个CustomerServiceTestCase ,目前,它具有单个测试以创建客户并验证其是否已创建。

package com.example.spring;import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;import javax.inject.Inject;import org.junit.After;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;import com.example.spring.config.AppConfig;
import com.example.spring.domain.Customer;
import com.example.spring.services.CustomerService;@RunWith( SpringJUnit4ClassRunner.class )
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, classes = { AppConfig.class } )
public class CustomerServiceTestCase {@Inject private CustomerService customerService; @Afterpublic void tearDown() {customerService.deleteAll();}@Testpublic void testCreateCustomerAndVerifyItHasBeenCreated() throws Exception {Customer customer = customerService.create( "Customer A" );assertThat( customerService.find( customer.getId() ), notNullValue() );}
}

看起来很简单明了。 现在,让我们考虑成功创建客户但由于查询超时而导致查找失败的情况。 为此,我们需要Byteman的帮助。 简而言之, Byteman是字节码操作框架。 这是一个Java代理实现,可与JVM一起运行(或附加到JVM)并修改正在运行的应用程序字节码,从而改变其行为。 Byteman有一个很好的文档,并且拥有丰富的规则定义集,可以执行开发人员可以想到的几乎所有事情。 而且,它与JUnit框架具有很好的集成。 在该主题上,应该使用@RunWith(BMUnitRunner.class)运行Byteman测试,但是我们已经在使用@RunWith(SpringJUnit4ClassRunner.class),并且JUnit不允许指定多个测试运行程序。 除非您熟悉JUnit @Rule机制,否则这似乎是一个问题。 事实证明,将BMUnitRunner转换为JUnit规则非常容易:

package com.example.spring;import org.jboss.byteman.contrib.bmunit.BMUnitRunner;
import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.InitializationError;
import org.junit.runners.model.Statement;public class BytemanRule extends BMUnitRunner implements MethodRule {public static BytemanRule create( Class< ? > klass ) {try {return new BytemanRule( klass ); } catch( InitializationError ex ) { throw new RuntimeException( ex ); }}private BytemanRule( Class klass ) throws InitializationError {super( klass );}@Overridepublic Statement apply( final Statement statement, final FrameworkMethod method, final Object target ) {Statement result = addMethodMultiRuleLoader( statement, method ); if( result == statement ) {result = addMethodSingleRuleLoader( statement, method );}return result;}
}

JUnit @Rule注入就这么简单:

@Rule public BytemanRule byteman = BytemanRule.create( CustomerServiceTestCase.class );

容易吧? 我们前面提到的场景可以改写一下:当执行从“客户”表中选择的JDBC语句执行时,我们应该因超时异常而失败。 这是带有附加Byteman批注的JUnit测试用例的外观:

@Test( expected = DataAccessException.class )@BMRule(name = "introduce timeout while accessing MySQL database",targetClass = "com.mysql.jdbc.PreparedStatement",targetMethod = "executeQuery",targetLocation = "AT ENTRY",condition = "$0.originalSql.startsWith( \"select\" ) && !flagged( \"timeout\" )",action = "flag( \"timeout\" ); throw new com.mysql.jdbc.exceptions.MySQLTimeoutException( \"Statement timed out (simulated)\" )")public void testCreateCustomerWhileDatabaseIsTimingOut()  {Customer customer = customerService.create( "Customer A" );customerService.find( customer.getId() );}

我们可以这样写:“当有人调用PreparedStatement类的executeQuery方法,并且查询以'SELECT'开始时,将抛出MySQLTimeoutException ,并且它应该只发生一次(由超时标志控制)”。 运行此测试用例将在控制台中打印stacktrace,并期望引发DataAccessException

com.mysql.jdbc.exceptions.MySQLTimeoutException: Statement timed out (simulated)at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.7.0_21]at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) ~[na:1.7.0_21]at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.7.0_21]at java.lang.reflect.Constructor.newInstance(Constructor.java:525) ~[na:1.7.0_21]at org.jboss.byteman.rule.expression.ThrowExpression.interpret(ThrowExpression.java:231) ~[na:na]at org.jboss.byteman.rule.Action.interpret(Action.java:144) ~[na:na]at org.jboss.byteman.rule.helper.InterpretedHelper.fire(InterpretedHelper.java:169) ~[na:na]at org.jboss.byteman.rule.helper.InterpretedHelper.execute0(InterpretedHelper.java:137) ~[na:na]at org.jboss.byteman.rule.helper.InterpretedHelper.execute(InterpretedHelper.java:100) ~[na:na]at org.jboss.byteman.rule.Rule.execute(Rule.java:682) ~[na:na]at org.jboss.byteman.rule.Rule.execute(Rule.java:651) ~[na:na]at com.mysql.jdbc.PreparedStatement.executeQuery(PreparedStatement.java) ~[mysql-connector-java-5.1.24.jar:na]at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.extract(ResultSetReturnImpl.java:56) ~[hibernate-core-4.2.0.Final.jar:4.2.0.Final]at org.hibernate.loader.Loader.getResultSet(Loader.java:2031) [hibernate-core-4.2.0.Final.jar:4.2.0.Final]

看起来不错,还有另一种情况:创建客户成功但由于数据库关闭而失败了吗? 这一点比较复杂,但无论如何都很容易做,让我们看一下:

@Test( expected = CannotCreateTransactionException.class )
@BMRules(rules = {@BMRule(name="create countDown for AbstractPlainSocketImpl",targetClass = "java.net.AbstractPlainSocketImpl",targetMethod = "getOutputStream",condition = "$0.port==3306",action = "createCountDown( \"connection\", 1 )"),@BMRule(name = "throw IOException when trying to execute 2nd query to MySQL",targetClass = "java.net.AbstractPlainSocketImpl",targetMethod = "getOutputStream",condition = "$0.port==3306 && countDown( \"connection\" )",action = "throw new java.io.IOException( \"Connection refused (simulated)\" )")}
)
public void testCreateCustomerAndTryToFindItWhenDatabaseIsDown() {Customer customer = customerService.create( "Customer A" );customerService.find( customer.getId() );
}

让我解释一下这是怎么回事。 我们希望坐在套接字级别,并且实际上控制通讯尽可能地接近网络,而不是在JDBC驱动程序级别。 这就是为什么我们要检测AbstractPlainSocketImpl的原因。 我们也知道MySQL的默认端口是3306,因此我们仅检测在此端口上打开的套接字。 另一个事实,我们知道第一个创建的套接字与客户创建相对应,我们应该让它通过。 但是第二个对应于查找并且必须失败。 名为“ connection”createCountDown可以满足以下目的:第一次调用通过(闩锁尚未计数为零),但是第二次调用触发MySQLTimeoutException异常。 运行此测试用例将在控制台中打印stacktrace,并期望抛出CannotCreateTransactionException

Caused by: java.io.IOException: Connection refused (simulated)at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.7.0_21]at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:57) ~[na:1.7.0_21]at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.7.0_21]at java.lang.reflect.Constructor.newInstance(Constructor.java:525) ~[na:1.7.0_21]at org.jboss.byteman.rule.expression.ThrowExpression.interpret(ThrowExpression.java:231) ~[na:na]at org.jboss.byteman.rule.Action.interpret(Action.java:144) ~[na:na]at org.jboss.byteman.rule.helper.InterpretedHelper.fire(InterpretedHelper.java:169) ~[na:na]at org.jboss.byteman.rule.helper.InterpretedHelper.execute0(InterpretedHelper.java:137) ~[na:na]at org.jboss.byteman.rule.helper.InterpretedHelper.execute(InterpretedHelper.java:100) ~[na:na]at org.jboss.byteman.rule.Rule.execute(Rule.java:682) ~[na:na]at org.jboss.byteman.rule.Rule.execute(Rule.java:651) ~[na:na]at java.net.AbstractPlainSocketImpl.getOutputStream(AbstractPlainSocketImpl.java) ~[na:1.7.0_21]at java.net.PlainSocketImpl.getOutputStream(PlainSocketImpl.java:214) ~[na:1.7.0_21]at java.net.Socket$3.run(Socket.java:915) ~[na:1.7.0_21]at java.net.Socket$3.run(Socket.java:913) ~[na:1.7.0_21]at java.security.AccessController.doPrivileged(Native Method) ~[na:1.7.0_21]at java.net.Socket.getOutputStream(Socket.java:912) ~[na:1.7.0_21]at com.mysql.jdbc.MysqlIO.(MysqlIO.java:330) ~[mysql-connector-java-5.1.24.jar:na]

大! 字节曼为不同故障模拟提供的可能性是巨大的。 仔细添加测试套件,以验证应用程序如何对错误的条件做出React,可以大大提高应用程序的健壮性和对故障的适应能力。 多亏了Byteman伙计们! 请在GitHub上找到完整的项目。

参考: 使用Byteman和JUnit进行故障注入:通过Andriy Redko {devmind}博客上的JCG合作伙伴 Andrey Redko,可以做更多的工作来确保应用程序的健壮性 。

翻译自: https://www.javacodegeeks.com/2013/04/fault-injection-with-byteman-and-junit.html

byteman

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/349341.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java动态扩展_java栈内存动态扩展要怎么理解?要如何实现?

小伙伴们知道如何在java栈中内存动态扩展吗?这是虚拟机中的一个概念&#xff0c;下面让我们一起来看看该如何实现吧。一、内存概念在java中&#xff0c;我们一般会简单把java内存区域划为两种&#xff1a;堆内存与栈内存。其实这种划分是比较粗粒度的。其中栈内存就是指的是虚…

python 爬虫热搜_Python网络爬虫之爬取微博热搜

微博热搜的爬取较为简单&#xff0c;我只是用了lxml和requests两个库1.分析网页的源代码&#xff1a;右键--查看网页源代码.从网页代码中可以获取到信息(1)热搜的名字都在的子节点里(2)热搜的排名都在的里(注意置顶微博是没有排名的&#xff01;)(3)热搜的访问量都在的子节点里…

优盘复制进来为空_为何电脑上的文件夹一复制到U盘里就变成空文件夹了?

(格式化后的卡在恢复前千万不要往里面存东西,因为这样会覆盖你想恢复的内容,如果你不小心存东西了也不要放弃,只不过恢复的机率会变小),下面开始:::在百度里输入Easy recover 软件找到后下载安装到电脑上,然后将内存卡与电脑连接,打开Easy recover 软件,首次打开时软件会分析系…

Spring Cloud教程–使用Spring Cloud Bus自动刷新配置更改

问题 在上一篇文章Spring Cloud Config Server简介 &#xff08; http://sivalabs.in/2017/08/spring-cloud-tutorials-introduction-to-spring-cloud-config-server/ &#xff09;中&#xff0c;我们已经了解了如何使用Spring Cloud配置服务器。 但是&#xff0c;问题是要重新…

js给标签添加属性和值_jquery节点属性

一.节点操作1.DOM内容节点操作&#xff1a;​ ①innerHTML属性&#xff1a;设置或获取文本的内容&#xff08;普通文本和标签&#xff09;。​ ②innerText属性&#xff1a;设置或获取文本的内容&#xff08;普通文本&#xff09;&#xff0c;存在兼容性问题。2.jQuery内容节点…

sci translate好用吗_228个学科分类对应12000+本SCI和SSCI期刊,总有你要的那款!

最近有很多小伙伴询问选刊的问题&#xff0c;而且都是非常具体的学科方向&#xff0c;我们的小编虽然非常热心且礼貌的回答“近期安排”&#xff0c;但其实我们也感觉到鸭梨山大:根据WOS最新一期&#xff08;2020/9/21&#xff09;名单公布&#xff0c;WOS目前总共收录了12266本…

java 反射 属性顺序_java反射得到的方法数组的顺序

展开全部看了下你好像需要set和get方法&#xff0c;如果你知道属性的名字的话不需要遍历可以这样获取&#xff0c;这个是我以前的demo的一个32313133353236313431303231363533e58685e5aeb931333332633561片段:Class clazzClass.forName("com.demo.reflectdemo.Student&quo…

arrays.sort(._Arrays.sort与Arrays.parallelSort

arrays.sort(.我们都使用Arrays.sort对对象和原始数组进行排序。 此API在下面使用合并排序或Tim排序对内容进行排序&#xff0c;如下所示&#xff1a; public static void sort(Object[] a) {if (LegacyMergeSort.userRequested)legacyMergeSort(a);elseComparableTimSort.sor…

python冒泡排序函数_python冒泡排序-Python,冒泡排序

arr[7,4,3,67,34,1,8].defbubble_sort:最近在学习Python&#xff0c;下面是我的一些笔记冒泡排序实现思路&#xff1a; 使用双重for循环&#xff0c;内层变量为i&#xff0c; 外层为j&#xff0c;在内层循环中不断的比较相邻的两个值(i, i1)的大小&#xff0c;如果i1的值大于i的…

适用于Idea的面向现代TDD的Java 8 JUnit测试模板(带有Mockito和AssertJ)

使用类似BDD的语法&#xff0c;Java 8和Mockito-AssertJ二重奏为Idea调整JUnit测试类模板。 本文涵盖的主题似乎很简单。 但是&#xff0c;根据我的培训师经验&#xff0c;我知道&#xff08;不幸的是&#xff09;这不是常见的做法。 因此&#xff0c;我决定写这篇简短的博客文…

python编程的基本方法有哪些_Python编程中常用的基础知识有哪些?

今天小编要跟大家分享的文章是关于Python编程中常用的基础知识有哪些?正在从事Python相关工作的小伙伴们&#xff0c;来和小编一起看一看本篇文章&#xff0c;希望本篇文章能够对大家有所帮助。1、正则表达式替换目标: 将字符串 line 中的 overview.gif 替换成其他字符串>&…

java取网页源码_Java获取任意http网页源代码的方法

本文实例讲述了JAVA获取任意http网页源代码。分享给大家供大家参考&#xff0c;具体如下&#xff1a;JAVA获取任意http网页源代码可实现如下功能&#xff1a;1. 获取任意http网页的代码2. 获取任意http网页去掉HTML标签的代码Webpage类&#xff1a;/*** 网页操作相关类*/packag…

python数据结构算法优势_Python数据结构与算法(一)----- 算法效率

一.引入先来看一道题&#xff1a;如果abc1000, 且a2b2c^2(a,b,c为自然数)&#xff0c;如何求出所有a,b,c可能的组合&#xff1f;(1) 枚举法import timestart_time time.time()for a in range(0,1001):for b in range(0,1001):for c in range(1,1001):if abc1000 and a**2b**2 …

Java编程字符逆序输出_用JAVA编写一程序:从键盘输入多个字符串到程序中,并将它们按逆序输出在屏幕上。...

展开全部代码如下&#xff1a;import java.util.Scanner;public class ScannerDemo{public static void main(String[] args) throws Exception{Scanner scannew Scanner(System.in);System.out.println("请输入内容&#xff1a;");String strscan.nextLine();char[]…

glassfish 4配置_自己构建GlassFish 4.0快照

glassfish 4配置这篇文章是关于自己发布GlassFish 4.0快照的&#xff0c;其中包括一些黑客。 我找到了GlassFish FullBuild的官方说明&#xff0c;然后决定自己构建服务器。 有时&#xff0c;您可能不想等待团队升级GlassFish构建文件。 在本条目中&#xff0c;我将Artifactory…

julia与python对比_有人说Julia比Python好,还给出了5个理由

选自medium作者&#xff1a;Emmett Boudreau机器之心编译参与&#xff1a;杜伟、张倩、肖清本文作者从速度、通用性、多重派发、适用于 ML 的程度和包管理器 5 个方面阐述了 Julia 语言相较于 Python 的优势所在。Julia 是一种多范式的函数式编程语言&#xff0c;用于机器学习和…

使用Flowable.generate()生成可感知背压的流– RxJava常见问题解答

RxJava缺少创建无限自然数流的工厂。 这样的流很有用&#xff0c;例如&#xff0c;当您想通过压缩两个事件的唯一序列号给可能的无限事件流时&#xff1a; Flowable<Long> naturalNumbers //???Flowable<Event> someInfiniteEventStream //... Flowable<P…

java字符串构造函数的应用_StringTokenizer类的使用

StringTokenizer是一个用来分隔String的应用类&#xff0c;相当于VB的split函数。1.构造函数public StringTokenizer(String str)public StringTokenizer(String str, String delim)public StringTokenizer(String str, String delim, boolean returnDelims)第一个参数就是要分…

linkedhashmap获取第n个元素_机试真题分享——交换链表前后第K个元素

题目描述给定一个编码链表和一个加密条件K&#xff0c;对编码进行加密。加密规则&#xff1a;把编码从前往后开始数第K个元素和从后往前数第K个元素进行交换。注意&#xff1a;编码的长度为0.第一个编码的序号是1.示例&#xff1a;输入&#xff1a;[1 2 3 4 5 6] 2输出&#xf…

maven java 参数_将Maven参数注入Java类

我想将settings.xml配置文件参数注入Java类.我尝试使用maven-annotation-plugin,但值为null.我想知道这是不是因为这个插件是为Mojo设计的Setting.xml片段APP_NAMEUSER_EMAILUSER_PASSWORD在班上Parameter(defaultValue "test.email", readonly true)private Strin…