使用Byteman和JUnit进行故障注入

我们的应用程序独立存在的时间已经很久了。 如今,应用程序是一种非常复杂的野兽,它们使用无数的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]

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

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

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

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

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

相关文章

Ambari Views的自定义

下载ambari源码&#xff0c; ambari-views/examples/ 下面有很多例子&#xff0c;直接编译是不会成功的&#xff0c; 因为每一个例子项目都需要一个依赖 <dependency><groupId>org.apache.ambari</groupId><artifactId>ambari-views</artifactId>…

angular使用sass的scss语法

一、现象 为了简写样式 二、解决 1、安装sass ,利用npm 安装(npm工具如果没有&#xff0c;请先自行安装好) &#xff08;1&#xff09;、npm install node-sass --save-dev &#xff08;2&#xff09;、npm install sass-loader --save-dev 2、修改.angular-cli.json文件 …

linux 查看java cpu_Linux查看CPU和内存使用情况

在系统维护的过程中&#xff0c;随时可能有需要查看 CPU 使用率&#xff0c;并根据相应信息分析系统状况的需要。在 CentOS 中&#xff0c;可以通过 top 命令来查看 CPU 使用状况。运行 top 命令后&#xff0c;CPU 使用状态会以全屏的方式显示&#xff0c;并且会处在对话的模式…

TCP浅谈为什么3次握手

《计算机网络》中的例子是这样的&#xff0c;“已失效的连接请求报文段”的产生在这样一种情况&#xff1a;客户发出的第一个连接请求报文段并没有丢失&#xff0c;而是在某个网络结点长时间的滞留了&#xff0c;以致延误到连接释放以后的某个时间才到达服务。 本来这是一个早已…

Arrays.sort与Arrays.parallelSort

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

了解ASP.NET MVC的基本架构

MVC&#xff0c;Model – View – Controller 的简写 Model 封装业务逻辑相关的数据及对数据的处理方法 View 向用户提供交互界面 Controller 负责控制Model和View 首先建立一个模板 在这里发现了一个问题&#xff08;好像vs12没有模板&#xff0c;建不出来&#xff09; 其…

Google Chrome 浏览器JS无法更新解决办法

JS无法更新原因&#xff1a; 浏览器为了加载快&#xff0c;默认是按照自定规则更新缓存&#xff0c;非实时更新。 我们在开发的时候&#xff0c;JS变动很快&#xff0c;需要即时让浏览器加载最新文件&#xff0c;也就是禁用浏览器缓存 &#xff08;1&#xff09;使用F12进入…

HDU3415 Max Sum of Max-K-sub-sequence

题意given a circle sequence A[1],A[2],A[3]......A[n]. Circle sequence means the left neighbour of A[1] is A[n] , and the right neighbour of A[n] is A[1]. Now your job is to calculate the max sum of a Max-K-sub-sequence. Max-K-sub-sequence means a continuou…

Spring:自动接线或不自动接线

从使用Spring 2.5开始&#xff0c;我从基于XML的应用程序上下文切换到了注释。 尽管我发现那些非常有用且节省大量时间的人&#xff0c;但我始终觉得在灵活性方面我失去了一些东西。 特别是Autowired批注-或标准Inject-在我看来就像新的“新”&#xff0c;增加了类之间的耦合&a…

刷题总结——Cut the Sequence(POJ 3017 dp+单调队列+set)

题目&#xff1a; Description Given an integer sequence { an } of length N, you are to cut the sequence into several parts every one of which is a consecutive subsequence of the original sequence. Every part must satisfy that the sum of the integers in the …

java crud事件回调_java回调机制 - 神是到着念的个人空间 - OSCHINA - 中文开源技术交流社区...

软件模块之间总是存在着一定的接口&#xff0c;从调用方式上&#xff0c;可以把他们分为三类&#xff1a;同步调用、回调和异步调用。同步调用是一种阻塞式调用&#xff0c;调用方要等待对方执行完毕才返回&#xff0c;它是一种单向调用&#xff1b;回调是一种双向调用模式&…

CSS盒子的浮动

在标准流中&#xff0c;一个块级元素在水平方向会自动伸展&#xff0c;直到包含它的元素的边界&#xff1b;而在竖直方向和兄弟元素依次排列&#xff0c;不能并排。使用“浮动”方式后&#xff0c;块级元素的表现就会有所不同。 CSS中有一个float属性&#xff0c;默认为no…

servlet中url-pattern之/与/*的区别

转载于:https://www.cnblogs.com/hwgok/p/8835350.html

很少使用“ ControlFlowException”

控制流是命令式编程的“遗留物”&#xff0c;它已泄漏到其他各种编程范例中&#xff0c;包括Java的面向对象范例 。 除了有用的和无处不在的分支和循环结构外&#xff0c;还包括原语&#xff08;例如GOTO&#xff09;和非局部变量&#xff08;例如异常&#xff09;。 让我们仔细…

java的if里有多个if_代码里写很多if会影响效率吗?

看你怎么写 if.嵌入很多层if的代码叫做“箭头代码”&#xff0c;是一个anti-pattern。 这种代码会增加程序的循环复杂度 (Cyclomatic complexity)具体可以看这里&#xff1a;Flattening Arrow Code这里&#xff1a;总的来说&#xff0c;程序里用if-else是有开销的。每次conditi…

python基础总结(6)

一、模块。 一个模块就是一个python文件&#xff0c;.py文件。 需要注意的是&#xff1a;自己创建模块时要注意命名&#xff0c;不能和Python自带的模块名称冲突。例如&#xff0c;系统自带了sys模块&#xff0c;自己的模块就不可命名为sys.py&#xff0c;否则将无法导入系统自…

[10.10模拟] water

题意&#xff1a; 有一块矩形土地被划分成 n*m 个正方形小块。这些小块高低不平,每一小块都有自己的高度。水流可以由任意一块地流向周围四个方向的四块地中,但是不能直接流入对角相连的小块中。一场大雨后,由于地势高不同,许多地方都积存了不少降水。给定每个小块的高度,求每个…

使用Amazon Web Services(EC2)

正如我上周参加技术动手AWS培训之前所发布的。 这些天的课程当然是我以前使用过的标准EC2和S3服务。 除此之外&#xff0c;我们还使用了RDS &#xff0c; Elastic Load Balancing &#xff0c; SNS和VPC &#xff0c; Elastic Beanstalk并讨论了许多术语和业务案例。 在本文中&…

java整型和浮点型_Java基本的程序结构设计 整形和浮点型

整形&#xff1a;int 4字节short 2字节long 8字节byte 1字节int的大小差不多是20亿。整形计算如果两个int进行加减乘除数学运算的时候&#xff0c;最终的结果仍然是int&#xff0c;有可能出现了溢出&#xff0c;那么结果就不是我们想要的了。如下&#xff1a;System.out.printl…

盒子模型阴影设置,爱奇艺阴影配置

box-shadow的配置阴影 ul li:hover{ border-color: #dfdfdf; border-radius: 10px; -moz-box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1), 0 0 10px 0 rgba(0, 0, 0, 0.2); -webkit-box-shadow: 0 5px 5px rgba(0, 0, 0, 0.1), 0 0 10px 0 rgba(0, 0, 0, 0.2); box-shadow: 0 5px 5…