关于工作用测试的一些小常识

本文探讨了单元测试中@Mock和@InjectMocks的区别,@BeforeMethod与@BeforeClass的适用场景,如何Mock静态方法,处理对象内方法互调的场景以及Mock私有方法的方法。介绍了PowerMockito在模拟静态方法中的作用以及使用反射访问和测试私有方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题1:@Mock和@InjectMocks的区别
@Target(FIELD)
@Retention(RUNTIME)
@Documented
public @interface Mock {

    Answers answer() default Answers.RETURNS_DEFAULTS;

    String name() default "";

    Class<?>[] extraInterfaces() default {};
    
    boolean serializable() default false;
}

可以看出@Mock用于类的属性注解,且运行时才能发挥作用。

@Documented
@Target(FIELD)
@Retention(RUNTIME)
public @interface InjectMocks {}

@InjectMocks用于类的属性注解,且运行时发挥作用。

Mark a field as a mock.
@Mock注解的属性对象,在代码运行时,调用属性的方法不会进入具体的方法体;

Mark a field on which injection should be performed.
@InjectMocks注解的属性对象,在代码运行时,调用该属性的方法,则会进入具体的方法体。

问题2:@BeforeMethod和@BeforeClass的区别

@BeforeMethod注解源码

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(java.lang.annotation.ElementType.METHOD)
public @interface BeforeMethod {
	public boolean enabled() default true;
	public String[] groups() default {};
	public String[] dependsOnGroups() default {};
	public String[] dependsOnMethods() default {};
	public boolean alwaysRun() default false;
	public boolean inheritGroups() default true;
	public String description() default "";
	public boolean firstTimeOnly() default false;    // 注意这个方法
	public long timeOut() default 0;
}

@BeforeClass注解源码

@Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
@Target(java.lang.annotation.ElementType.METHOD)
public @interface BeforeClass {
	public boolean enabled() default true;
	public String[] groups() default {};
	public String[] dependsOnGroups() default {};
	public String[] dependsOnMethods() default {};
	public boolean alwaysRun() default false;
	public boolean inheritGroups() default true;
	public String description() default "";
	public long timeOut() default 0;
}

源码中很明显表达出:

  • @BeforeMethod注解和@BeforeClass注解用于方法上;
  • @BeforeMethod注解的方法可以多次执行,而@BeforeClass注解的方法没有明确说明;
  • @BeforeClass注解的方法在测试类中只会执行一次,@BeforeMethod注解的方法在测试类中每个方法执行前都会执行一次;

那么它们分别在什么情况下使用?
我个人觉得,如果每个方法都需要初始化或者注入一些对象,而这些对象在其他方法中已经被改变,则应该使用@BeforeMethod,否则应该使用@BeforeClass。

问题3:如何Mock静态方法?

最近在写测试类,发现对于静态方法的mock不能按照对象方法的思路来测试。先说说如何做的吧。
第一步:测试类继承PowerMockTestCase类;
第二步:在测试类上添加注解:@PrepareForTest({xxUtils.clss});
注意:xxUtils.class为静态方法所在的类,如果有多个类,可用逗号隔开。
第三步:在初始化方法内添加PowerMockito.mockStatic(xxUtils.class);
第四步:测试方法内通过PowerMockito.when(xxUtils.method(args…)).then…模拟静态方法;

实例:
定义一个工具类,并含有一个检查范围的静态类:

public class TestUtils {
	public static boolean checkScope(int value, int minValue, int maxValue) throws LSQException {
		if (minValue > maxValue) {
			throw new LSQException("最小值不应大于最大值.");
		}
		if (value < minValue || value > maxValue){
			return false;
		}
		return true;
	}
}

实体类Client调用静态方法:

public class Client {
	public void method1() {
		boolean result = false;
		try {
			result = TestUtils.checkScope(3, 0, 2);   // 运行结果为false, 但测试结果返回的是true,表明测试通过
		} catch (LSQException e) {
			e.printStackTrace();
		}
		System.out.println(result);
	}
}

那么测试类如下:

@PrepareForTest({TestUtils.class})
public class TestUtilsTest extends PowerMockTestCase {

	@InjectMocks
	Client client;
	
	@BeforeClass
	public void BeforeClass(){
		MockitoAnnotations.initMocks(this);
		PowerMockito.mockStatic(TestUtils.class);
	}
	
	@Test
	public void test0(){
		try {
			PowerMockito.when(TestUtils.checkScope(Matchers.anyInt(), Matchers.anyInt(), Matchers.anyInt())).thenReturn(true);
		} catch (LSQException e) {
			e.printStackTrace();
		}
		client.method1();
	}
	@Test
	public void test1(){
		try {
			PowerMockito.when(TestUtils.checkScope(Matchers.anyInt(), Matchers.anyInt(), Matchers.anyInt())).thenThrow(new LSQException("test"));
		} catch (LSQException e) {
			e.printStackTrace();
		}
		client.method1();
	}
}

测试结果为:test0通过,test1不通过。
报错为:

org.mockito.exceptions.misusing.InvalidUseOfMatchersException: 
Misplaced argument matcher detected here:

You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
    when(mock.get(anyInt())).thenReturn(null);
    doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
    verify(mock).someMethod(contains("foo"))

Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.

上面错误大致意思是:检测到参数匹配器有误。。百度好久都没有找到具体原因。。。几乎崩溃。。。
但能锁定的是PowerMockito的问题,单独运行test1能通过,同时运行却不行,因此我直觉性的把@BeforeClass改为@BeforeMethod,再次运行,顺利通过。
什么原因呢?应该是要为每个测试方法都模拟出一个静态方法的类,或者说不同方法中,每调用一次静态方法就需要模拟出一个静态方法的类。为了验证我的想法,我把两个测试方法合并为一个方法,测试通过,因此上述说法是成立哒。

原理分析:
PowerMockTestCase类专门用来模拟静态方法,它和注解@PrepareForTest配合使用;模拟出静态方法的调用。关于PowerMockTestCase可以看这篇文章

问题4:如何处理一个对象内A方法与B方法互调的场景?

关于这个问题,很常见,因为一个类不全是外部对象的方法。
最先的思路是:再@Mock出一个模拟对象,在测试方法中使用该模拟对象来调用A方法中的B方法,而不是使用@InjectMock模拟出的对象。本以为这样可以绕过B方法,而避免资源消耗;但结果并不像预期那样。
分析原因:虽然@Mock出一个对象,但该对象与@InjectMock对象不同名,在测试方法中通过@InjectMock对象调用A方法,所以调用B方法同样是@InjectMock对象,而并非@Mock对象,所以结果很显然。

想了好久没有找到可行的方案,若有哪位大佬有更好的办法,麻烦告知,谢谢~

问题5:如何Mock类中的私有方法?

私有方法无法通过正常的InjectMock类来调用进行测试。下面提供一个实例来解决私有方法的测试:
普通类:

public class A {
	private void method(String str) {
		System.out.println(str);
	}
}

测试类:

public class ATest {
	@InjectMocks
	private A a;
	@BeforeMethod
	public void init(){
		MockitoAnnotations.initMocks(this);
	}
	
	@Test
	public void method() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		//a.method("");  报错
		
		// 参数1是要调用的方法名, 参数2是方法参数类型,可以有多个参数
		Method method = a.getClass().getDeclaredMethod("method", String.class);
		method.setAccessible(true);  // 放开权限
		// 调用方法,参数1是实体bean,参数2是该方法的具体参数
		method.invoke(a, new Object[] {"haha"}); 
		// 关闭方法访问权限
		method.setAccessible(false);
	}
}

可以看出,这种访问方式是通过反射修改方法的访问权限来实现的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值