对于有经验的开发写单元测试是很是有必要的,而且对本身的代码质量以及编码能力也是有提升的。单元测试能够帮助减小bug泄露,经过运行单元测试能够直接测试各个功能的正确性,bug能够提早发现并解决,因为能够跟断点,因此可以比较快的定位问题,比泄露到生产环境再定位要代价小不少。同时充足的UT是保证重构正确性的有效手段,有了足够的UT防御,才能放开手脚大胆重构已有代码,工 做多年后更了解了UT,了解了UT的重要性。java
在敏捷的开发理念中,覆盖全面的自动化测试是添加新特性和重构的必要前提。单元测试在软件开发过程当中的重要性不言而喻,特别是在测试驱动开发的开发模式愈来愈流行的前提下,单元测试更成为了软件开发过程当中不可或缺的部分。同时单元测试也是提升软件质量,花费成本比较低的重要方法。数据库
在作单元测试的时候,咱们会发现咱们要测试的方法会引用不少外部依赖的对象,如调用平台接口、链接数据库、网络通信、远程服务、FTP、文件系统等等。 而咱们无法控制这些外部依赖的对象,为了解决这个问题,咱们就须要用到Mock工具来模拟这些外部依赖的对象,来完成单元测试。
如今比较流行的Mock工具备JMock、EasyMock、Mockito、PowerMock。咱们使用的是Mockito和PowerMock。PowerMock弥补了其余3个Mock工具不能mock静态、final 、私有方法的缺点。
在下面的状况下咱们可使用Mock对象来完成单元测试。缓存
mock能够模拟各类各样的对象,从而代替真正的对象作出但愿的响应。服务器
一、模拟对象的建立网络
List cache = mock(ArrayList.class); System.out.println(cache.get(0)); //-> null 因为没有对mock对象给预期,因此返回都是null
二、模拟对象方法调用的返回值多线程
List cache = mock(ArrayList.class); when(cache.get(0)).thenReturn("hello"); System.out.println(cache.get(0)); //-> hello
三、模拟对象方法屡次调用和屡次返回值并发
List cache = mock(ArrayList.class); when(cache.get(0)).thenReturn("0").thenReturn("1").thenReturn("2"); System.out.println(cache.get(0)); System.out.println(cache.get(0)); System.out.println(cache.get(0)); System.out.println(cache.get(0)); //-> 0,1,2,2 若是实际调用的次数超过了预期的次数,则会一直返回最后一次的预期值。
四、模拟对象方法调用抛出异常app
List cache = mock(ArrayList.class); when(cache.get(0)).thenReturn(new Exception("Exception")); System.out.println(cache.get(0));
五、模拟对象方法在没有返回值时也能够抛异常框架
List cache = mock(ArrayList.class); doThrow(new Exception("Exception")).when(cache).clear();
六、模拟方法调用时的参数匹配ide
AnyInt的使用,匹配任何int参数 List cache = mock(ArrayList.class); when(cache.get(anyInt())).thenReturn("0"); System.out.println(cache.get(0)); System.out.println(cache.get(2)); //-> 0,0
七、模拟方法是否被调用和调用的次数,预期调用了一次
List cache = mock(ArrayList.class); cache.add("steven"); verify(cache).add("steven");
预期调用了两次入缓存,没有调用清除缓存的方法
List cache = mock(ArrayList.class); cache.add("steven"); cache.add("steven"); verify(cache,times(2)).add("steven"); verify(cache,never()).clear();
还能够经过atLeast(int i)和atMost(int i)来替代times(int i)来验证被调用的次数最小值和最大值。
【注意】
Mock对象默认状况下,对于全部有返回值且没有预期过的方法,Mocktio会返回相应的默认值。对于内置类型会返回默认值,如int会返回0,布尔值返回false。对于其余type会返回null。mock对象会覆盖整个被mock的对象,所以没有预期的方法只能返回默认值。这个在初次使用Mock时须要注意,常常会发现测试结果不对,最后才发现本身未给相应的预期。
PowerMock使用一个自定义类加载器和字节码操做来模拟静态方法,构造函数,final类和方法,私有方法,去除静态初始化器等等。
PowerMock使用简单,在类名前添加注解,在预期前调用PowerMock的mock静态类方法,其余的预期方法和Mockito相似。
@PrepareForTest(System.class) @RunWith(PowerMockRunner.class) public class Test { @org.junit.Test public void should_get_filed() { System.out.println(System.getProperty("myName")); PowerMockito.mockStatic(System.class); PowerMockito.when(System.getProperty("myName")).thenReturn("steven"); System.out.println(System.getProperty("myName")); //->null steven } }
测试中须要模拟对象,除了经常使用的mock对象外,咱们还会常常用到Fake对象。Mock对象是预先计划好的对象,带有各类期待,他们组成了一个关于他们期待接受的调用的详细说明。而Fake对象是有实际可工做的实现,可是一般有一些缺点致使不适合用于产品,咱们一般使用Fake对象在测试中来模拟真实的对象。
在测试中常常会发现咱们须要使用系统或者平台给咱们提供的接口,在测试中咱们能够新建立一个类去实现此接口,而后在根据具体状况去实习此模拟类的相应方法。
如咱们建立了本身的FakeLog对象来模拟真实的日志打印,这样咱们能够在测试类中使用FakeLog来代替代码中真实使用的Log类,能够经过FakeLog的方法和预期的结果比较来进行测试正确性的判断。
Fake对象和mock对象还有一个实际中使用的区别,Fake对象咱们构造好后,之后全部的代码都去调用此Fake对象就能够了,不用每一个类每次都要给预期。从这个角度能够看到当一个类的方法或者预期相对不变时,能够采用Fake对象,当这个类的返回信息预期变化很是不可预期时,能够采用MOCK对象。
(1)直接注入:用于类之间的依赖层次较多的状况,测试整个业务流程,粒度大。
ResourceServerService service = mock(ResourcePPUServerService.class); new Processor().process(service );
(2)重写protected方法返回mock对象:用于类直接依赖于该服务的状况,测试行为的细节,粒度小。
ResourceServerService service = mock(ResourceServerService .class); generator = new EutranAnrDeletingItemGenerator() { @Override protected ResourceServerService getService() { return service; } }
Throwable有两个直接子类:Exception和Error
一、expcetd=SomeExecption.class
@Test(expected = AssertionError.class) public void should_occur_assertion_error_when_emb_number_is_not_eutran_or_utran_anr_delete() throws Exception { EMBObject eMBObject = new EMBObject(); new AnrDeleteProcessor().getAnrDeleteGenerator(EMBObject); } @Test(expected = NumberFormatException.class) public void should_throw_number_format_exception_when_input_string_field_greater_255() { TransactionIDConvert.convertTransIDToLong(transactionError); }
二、try-catch-fail只能用于Exception,Error不能用此种方式
try { method.invoke(); fail(); } catch (Exception e) { assertTrue(e.getCause() instanceof RuntimeException); }
@Test public void should_throw_runtime_exception_when_check_eutran_trap_data_fail() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { when(eutranAnrAddItemGenerator.getSrvCelProcessor()).thenReturn(processor); when(processor.validateTrapData(any(AnrItem.class), any(AnrBean.class))).thenReturn(false); Method method = EutranAnrAddItemGenerator.class.getDeclaredMethod("check", AnrItem.class); method.setAccessible(true); try { method.invoke(eutranAnrAddItemGenerator, anrAddItem); } catch (Exception e) { assertTrue(e.getCause() instanceof RuntimeException); } }
public class ExampleTest { @BeforeClass public static void setUp() throws Exception { initGlobalParameter(); registerServices(); } @Before public void setUp() throws Exception { initGlobalParameter(); registerServices(); } @After public void tearDown() throws Exception { ServiceAccess.serviceMap.clear(); clearCache(); } @AfterClass public static void tearDown() throws Exception { ServiceAccess.serviceMap.clear(); clearCache(); } @Test public void should_get_some_result1_when_give_some_condition1{ } @Test public void should_get_some_result2_when_give_some_condition2{ } }
JUnit4是JUnit框架有史以来的最大改进,其主要目标即是利用Java5的Annotation特性简化测试用例的编写。先简单解释一下什么是Annotation,这个单词通常是翻译成元数据。元数据是什么?元数据就是描述数据的数据。也就是说,这个东西在Java里面能够用来和public、static等关键字同样来修饰类名、方法名、变量名。修饰的做用描述这个数据是作什么用的,差很少和public描述这个数据是公有的同样。
在Java中一个包能够横跨两个不一样的目录,因此咱们的测试代码和产品代码放在同一目录中,这样维护起来更方便,测试代码和产品代码在同一个包中,这样也减小了没必要要的包引发,同时在测试类中使用继承更加的方便。
一个测试用例主体内容通常采用三段式:given-when-then
@Test public void should_get_correct_result_when_add_two_numbers() { int a = 1; int b = 2; int c = MyMath.add(a, b); assertEquals(3, c); }
测试类的名称以Test结尾。从目标类的类名衍生出其单元测试类的类名。类名前加上Test后缀。
Fake(伪类)放在测试包中,使用前缀Fake。
should …do something…when…under some conditions…
例如:
should_NOT_delete_A_when_exists_B_related_with_A should_throw_exception_when_the_parameter_is_illegal
在业务代码中为了测试而单独提供的保护方法或者其余方法,咱们经过@ForTest来标注。FofTest类以下:
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) public @interface ForTest { String description() default ""; }
咱们的代码涉及的模块很是众多,常常须要相互协做来完成一个功能,在此过程当中常常须要使用到外部的接口、同时也为别的模块提供服务。
数据库的单元测试,因为测试没法进行数据库的链接,故咱们经过提取通用接口(DBManagerInterface)和FakeDBManager来实现数据库解耦。FakeDBManager能够对真实的数据库进行模拟,也就是咱们经过Fake一个简单的内存数据库来模拟实际真实的数据库。
DBManager是咱们的真实链接数据库的业务类。咱们在测试时,是能够经过注入的方式用FakeDBManager来替换DBManager。
平台中的MinosMmlPPUServerService、ResourcePPUServerService等服务接口,均可以经过mock来进行测试。须要注意的是在业务代码中须要进行相应的解耦,能够经过SET方法或者构造器来注入平台的服务类。
public class ICMEMBMessageListenerTest { private MinosMmlPPUServerService minosMmlPPUServerService = mock(MinosMmlPPUServerService.class); @Before public void setUp() throws Exception { registerServices(); icmembMessageListener = new ICMEMBMessageListener(){ }; when(minosMmlPPUServerService.getIp()).thenReturn("127.0.0.1"); when(minosMmlPPUServerService.getPort()).thenReturn("80"); when(minosMmlPPUServerService.getEmbPort()).thenReturn("8080"); }
此处须要注意若是用到静态变量全局惟一的,须要在使用后在 tearDown中进行清除。
咱们的业务中也会出现与外部文件进行读写的代码。按照单元测试书写的原则,单元测试应该是独立的,不依赖于外部任何文件或者资源的。好的单元测试是运行速度快,可以帮助咱们定位问题。因此咱们普通涉及到外部文件的代码,都须要经过mock来预期其中的信息,如MOCK(I18n)文件或者properties、xml文件中的数据。
对于一些重要的文件,考虑到资源消耗不大的状况下,咱们也会去为这些文件添加单元测试。须要访问真实的文件,咱们第一步就须要去获取资源文件的具体位置。经过下面的FileService的getFileWorkDirectory咱们能够获取单元测试运行时的根目录。
public class FileService { public static String getFileWorkDirectory() { return new StringBuilder(getFileCodeRootDirectory()).append("test").toString(); } public static String getFileCodeRootDirectory() { String userDir = System.getProperty("user.dir"); userDir = userDir.substring(0, userDir.indexOf(File.separator + "CODE" + File.separator)); StringBuilder workFilePath = new StringBuilder(userDir); workFilePath.append(File.separator).append("CODE").append(File.separator); return workFilePath.toString(); } }
咱们在单元测试中能够经过传入具体的文件名称,能够在测试代码中访问真实的文件。
这种方法能够适用I18n文件,xml文件, properties文件。
咱们在对I18n文件进行测试时,也能够经过Fake对象根据具体的语言来进行国际化信息的测试。具体FakeI18nWrapper的代码在第7章中给出能够参考。
@Before public void setUp() throws Exception { String i18nFilePath = FileService.getFileWorkDirectory() + "\\conf\\i18n.xml"; I18N i18N = new FakeI18nWrapper(new File(i18nFilePath), I18nLanguageType.en_US); I18nAnrOsf.setTestingI18NInstance(i18N); }
经过单元测试,能较早地发现 bug 而且能比不进行单元测试更容易地修复bug。可是普通的单元测试方法(即便当完全地进行了测试时)在查找并行 bug 方面不是颇有效。这就是为何在实验室测试没有问题,但在外场常常出现各类莫名其妙的问题。
为何单元测试常常遗漏并行 bug?一般的说法是并行程序和Bug的问题在于它们的不肯定性。可是对于单元测试目的而言,在于并行程序是很是 肯定的。因此咱们单元测试须要对关键的逻辑、涉及到并发的场景进行多线程测试。
多线程的不肯定性和单元测试的肯定的预期确实是有点矛盾,这就须要精心的设计单元测试中的多线程用例。
Junit自己是不支持普通的多线程测试的,这是由于Junit的底层实现上是用System.exit退出用例执行的。JVM都终止了,在测试线程启动的其余线程天然也没法执行。因此要想编写多线程Junit测试用例,就必须让主线程等待全部子线程执行完成后再退出。咱们通常的方法是在主测试线程中增长sleep方法,这种方法优势是简单,但缺点是不一样机器的配置不同,致使等待时间没法肯定。更为高效的多线程单元测试可使用JAVA的CountDownLatch和第三方组件GroboUtils来实现。
下面经过一个简单的例子来讲明下多线程的单元测试。
测试的业务代码以下,功能是惟一事务号的生成器。
class UniqueNoGenerator { private static int generateCount = 0; public static synchronized int getUniqueSerialNo() { return generateCount++; } }
private static Set<Integer> results = new HashSet<>();
@Test public void should_get_unique_no() throws InterruptedException { Thread[] threads = new Thread[100]; for (int i = 0; i < threads.length; i++) { threads[i] = generateThread(); } //启动线程 Arrays.stream(threads).forEach(Thread::start); Thread.sleep(100L); assertEquals(results.size(), 100); } private Thread generateThread() { return new Thread(() -> { int uniqueSerialNo = UniqueNoGenerator.getUniqueSerialNo(); results.add(uniqueSerialNo); }); }
经过Sleep来等待测试线程中的全部线程执行完毕后,再进行条件的预期。问题就是用户没法准确的预期业务代码线程执行的时间,不一样的环境等待的时间也是不等的。因为须要添加延时,同时也违背了咱们单元测试执行时间须要尽可能短的原则。
private static Set<Integer> results = new HashSet<>(); private ThreadGroup threadGroup = new ThreadGroup("test"); @Test public void should_get_unique_no() throws InterruptedException { Thread[] threads = new Thread[100]; for (int i = 0; i < threads.length; i++) { threads[i] = generateThread(); } //启动线程 Arrays.stream(threads).forEach(Thread::start); while (threadGroup.activeCount() != 0) { Thread.sleep(1); } assertEquals(results.size(), 100); } private Thread generateThread() { return new Thread(threadGroup, () -> { int uniqueSerialNo = UniqueNoGenerator.getUniqueSerialNo(); results.add(uniqueSerialNo); }); }
这个是经过ThreadGroup来实现多线程测试的,能够把须要测试的类放入一个线程组,同时去判断线程组中是否还有未结束的线程。测试中须要注意把新建的线程加入到线程组中。
private static Set<Integer> results = new HashSet<>(); private CountDownLatch countDownLatch = new CountDownLatch(100); @Test public void should_get_unique_no() throws InterruptedException { Thread[] threads = new Thread[100]; for (int i = 0; i < threads.length; i++) { threads[i] = generateThread(); } //启动线程 Arrays.stream(threads).forEach(Thread::start); countDownLatch.await(); assertEquals(results.size(), 100); } private Thread generateThread() { return new Thread(() -> { int uniqueSerialNo = UniqueNoGenerator.getUniqueSerialNo(); results.add(uniqueSerialNo); countDownLatch.countDown(); }); }
经过JAVA的CountDownLatch能够很方便的来判断,测试中的线程是否已经执行完毕。CountDownLatch是一个同步辅助类,在完成一组正在其余线程中执行的操做以前,它容许一个或多个线程一直等待,咱们这里是让测试主线程等待。countDown方法是当前线程调用此方法,则计数减一。awaint方法,调用此方法会一直阻塞当前线程,直到计时器的值为0。
单例模式要点:
public class DbManager { private DbManager() { } public static DbManager getInstance() { return DbManagerHolder.instance; } private static class DbManagerHolder { private static DbManager instance = new DbManager(); } public String getRemoteCpuInfo(){ FtpClient ftpClient = new FtpClient("127.0.0.1","22"); return ftpClient.getCpuInfo(); } }
调用类 ResourceManager.java:
public class ResourceManager { public String getBaseInfo() { StringBuilder buffer = new StringBuilder(); buffer.append("IP=").append("127.0.0.1").append(";CPU=").append(DbManager.getInstance().getRemoteCpuInfo()); return buffer.toString(); } } 测试类 @Test public void should_get_cpu_info() { String expected = "IP=127.0.0.1;CPU=Intel"; ResourceManager resourceManager = new ResourceManager(); String baseInfo = resourceManager.getBaseInfo(); assertThat(baseInfo, is(expected)); }
从上面的描述能够看到,因为业务代码强关联了一个单例类,同时这个单例类会去经过网络获取远程机器的信息。这样咱们的单元测试在运行中就会去链接网络中的服务器致使测试失败。在业务类中相似这种涉及到单例类的调用常常用到。
这种状况下咱们须要修改下业务代码使代码可测。
第一种方法:提取方法并在测试类中复写。
public class ResourceManager { public String getBaseInfo() { StringBuilder buffer = new StringBuilder(); buffer.append("IP=").append("127.0.0.1").append("CPU=").append(getRemoteCpuInfo()); return buffer.toString(); } @ForTest protected String getRemoteCpuInfo() { return DbManager.getInstance().getRemoteCpuInfo(); } } @Test public void should_get_cpu_info() { String expected = "IP=127.0.0.1;CPU=Intel"; ResourceManager resourceManager = new ResourceManager(){ @Override protected String getRemoteCpuInfo() { return "Intel"; } }; String baseInfo = resourceManager.getBaseInfo(); assertThat(baseInfo, is(expected)); }
第二种方法:提取单例类中的方法为接口,而后在业务代码中经过set方法或者构造器注入到业务代码中。
public class DbManager implements ResourceService{ private DbManager() { } public static DbManager getInstance() { return DbManagerHolder.instance; } private static class DbManagerHolder { private static DbManager instance = new DbManager(); } @Override public String getRemoteCpuInfo(){ FtpClient ftpClient = new FtpClient("127.0.0.1","22"); return ftpClient.getCpuInfo(); } public interface ResourceService { String getRemoteCpuInfo(); } public class ResourceManager { private ResourceService resourceService = DbManager.getInstance(); public String getBaseInfo() { StringBuilder buffer = new StringBuilder(); buffer.append("IP=").append("127.0.0.1").append("CPU=").append(resourceService.getRemoteCpuInfo()); return buffer.toString(); } public void setResourceService(ResourceService resourceService) { this.resourceService = resourceService; } } @Test public void should_get_cpu_info() { String expected = "IP=127.0.0.1;CPU=Intel"; ResourceManager resourceManager = new ResourceManager(); DbManager mockDbManager = mock(DbManager.class); resourceManager.setResourceService(mockDbManager); when(mockDbManager.getRemoteCpuInfo()).thenReturn("Intel"); String baseInfo = resourceManager.getBaseInfo(); assertThat(baseInfo, is(expected)); }
经过上面的方法能够方便的解开业务代码对单例的强依赖,有时候咱们发现咱们的业务代码是静态类,这个时候你会发下第一种方法是解决不了问题的,只能经过第2中方法来实现。
经过上面的代码能够看到咱们应该尽可能的少用单例,在必须使用单例时能够设计接口来进行业务与单例类的解耦。
静态类与单例类相似,也能够经过提取方法后经过复现方法来解耦,一样也能够经过服务注入的方式来实现。也可使用PowerMock来预期方法的返回。
实际应用中若是单例类不须要维护任何状态,仅仅提供全局访问的方法,这种状况考虑可使用静态类,静态方法比单例更快,由于静态的绑定是在编译期就进行的。
同时须要注意的是不建议在静态类中维护状态信息,特别是在并发环境中,若无适当的同步措施而修改多线程并发时,会致使坏的竞态条件。
单例与静态主要的优势是单例类比静态类更具备面向对象的能力,使用单例,能够经过继承和多态扩展基类,实现接口和更有能力提供不一样的实现。
在咱们开发过程当中考虑到单元测试,仍是须要谨慎的使用静态类和单例类。
在使用一些解依赖技术时,咱们经常会感受到许多解依赖技术都破坏了原有的封装性。但考虑到代码的可测性和质量,牺牲一些封装性也是能够的,封装自己也并非最终目的,而是帮助理解代码的。下面在介绍下经常使用的解依赖方法。这些解依赖方法的思想都是通用的,采用控制反转和依赖注入的方式来进行。
软件开发中调用平台服务查询资源属性的典型代码:
public class DataProceeor{ private static final SomePlatFormService service = ServerService.lookup(SomePlatFormService.ID); public static CompensateData getAttributes(String name){ service.queryCompensate(name); } }
这种代码在实现上没有问题,可是没法进行单元测试(不启动软件)。由于此类加载时须要获取平台查询资源相关的服务,业务代码与平台代码存在强耦合性。
在不破坏原有功能的基础上对这段代码作以下改造:
一、引入实例变量和构造器
public class DataProceeor{ private static final SomePlatformService service = ServerService.lookup(SomePlatformService.ID); private SomePlatformService _service; public DataProceeor(SomePlatformService service) { _service = service; } public DataProceeor() { _service = ServerService.lookup(SomePlatformService.ID);; } public CompensateData getAttributes(String name){ service.queryCompensate(name); } }
二、增长新方法
public CompensateData getSomeAttributes(String name){ _service.queryCompensate(name); }
三、查找代码中全部用到方法getAttributes的地方,所有替换成getSomeAttributes。
四、完成第3步后,删除已经无用的变量和方法。
五、重命名引入的变量和方法,使其符合命名规范。
public class DataProceeor{ private SomePlatformService service; public DataProceeor(SomePlatformService service){ this.service = service; } public DataProceeor() { service = ServerService.lookup(SomePlatformService.ID);; } public static CompensateData getAttributes(String name){ service.queryCompensate(name); } }
六、增长对新方法的测试用例
public class DataProcessorTest { private DataProceeor dataProceeor; private SomePlateService somePlateService; private Map<String, String> attributes; @Before public void setUp() throws Exception { attributes.put("pci", "1"); } @Test public void should_get_attributes() { somePlateService = mock(SomePlateService.class); when(somePlateService.queryAttribue()).thenReturn(attributes); dataProceeor = new DataProceeor(); CompensateData compensateData = dataProceeor.getAttributes("pci"); assertThat(compensateData.value(), is("1")); assertThat(compensateData.value(), is("2")); } }
运行该测试用例,发现最后一句断言没有经过:
修改最后一句断言为:assertThat(attributeValue+"", not("2"));
再次运行测试,测试用例经过。
模式1中的例子查询资源属性时没有设置过滤条件,事实上大多数处理都是依赖其余处理类:
public class NotificationDispatcher { private static Logger logger = LoggerFactory.getLogger(NotificationDispatcher.class); public void processMessage(String notificationMsg) { NotificationMsg notification = new Gson().fromJson(notificationMsg, NotificationMsg.class); Map<String, String> sctpInfo; try { sctpInfo = new NotificationParser().parse(notification.getMessage()); logger.info("Parse notification xml success: " + sctpInfo); NotificationProcessor processor = new NotificationProcessorFactory().getProcessor(sctpInfo.get(CONFIGURATION_TYPE)); processor.process(sctpInfo); } catch (Exception e) { logger.warn(String.format("Deal notification failed. Exception: %s", e.getMessage()), e); } } }
在本例中,查询MOI的Filter是在getCellMoi方法内部构造出来的,咱们能够尝试给getCellMoi方法编写测试用例:
测试用例没有经过,问题出在哪里呢?
Debug代码发现,在getCellMoi方法内部构造出来的Filter和咱们在测试代码中构造的Filter并非同一个对象。很天然地想到为Filter类编写子类,并覆盖其equals方法。
用自定义的Filter代替平台的Filter:
public String getCellMoi(String cellName){ Filter filter = new SelfFilter(cellName); return getAttributers(filter,"moi"); }
修改后测试用例运行经过。
在模式2中,因为Filter是在getCellMoi内部构造的,而且没有euqals方法,致使没法测试。还能够用别的方法对其进行改造。代码示例以下:
1.提取protected方法buildFilter()
public void processMessage(String notificationMsg) { UmeNotificationMsg umeNotificationMsg = new Gson().fromJson(notificationMsg, UmeNotificationMsg.class); Map<String, String> sctpInfo; try { sctpInfo = new NotificationParser().parse(umeNotificationMsg.getMessage()); logger.info("Parse notification xml success: " + sctpInfo); NotificationProcessor processor = getNotificationProcessorFactory().getProcessor(sctpInfo.get(CONFIGURATION_TYPE)); processor.process(sctpInfo); } catch (Exception e) { logger.warn(String.format("Deal notification failed. Exception: %s", e.getMessage()), e); } } @ForTest protected NotificationProcessorFactory getNotificationProcessorFactory() { return new NotificationProcessorFactory(); }
2.在测试代码中重写getNotificationProcessorFactory方法
@Before public void setUp() throws Exception { NotificationProcessorFactory notificationProcessorFactory = mock(NotificationProcessorFactory.class); notificationDispatcher = new NotificationDispatcher(){ @Override protected NotificationProcessorFactory getNotificationProcessorFactory() { return notificationProcessorFactory; } }; }
运行测试,能够经过。
UT是开发人员的利器,是开发的前置保护伞,也是写出健壮代码的有力保证,总之一句话不会写UT的开发不是好厨子。