用.net做网站好 还是用php,免费学编程国内网站,化工建设信息网站,网站设计规划 优帮云byteman我们的应用程序独立存在的时间已经很久了。 如今#xff0c;应用程序是一种非常复杂的野兽#xff0c;它们使用无数的API和协议相互通信#xff0c;将数据存储在传统或NoSQL数据库中#xff0c;通过网络发送消息和事件……例如#xff0c;您多久考虑一次例如数据库… 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;IdGeneratedValueColumn(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?enableQueryTimeoutstrue );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框架具有很好的集成。 在该主题上应该使用RunWithBMUnitRunner.class运行Byteman测试但是我们已经在使用RunWithSpringJUnit4ClassRunner.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(namecreate countDown for AbstractPlainSocketImpl,targetClass java.net.AbstractPlainSocketImpl,targetMethod getOutputStream,condition $0.port3306,action createCountDown( \connection\, 1 )),BMRule(name throw IOException when trying to execute 2nd query to MySQL,targetClass java.net.AbstractPlainSocketImpl,targetMethod getOutputStream,condition $0.port3306 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.htmlbyteman