这是我要测试的功能:
@Component
public class DataSourceAttributes {
...
...
public AWSSecretDB getAttribsBySecret() throws Exception {
AbstractConnector abstractConnector = new AWSSecretManagerConnector("secretsmanager." + region + ".amazonaws.com", region);
GenericManager genericManager = new GenericManager(abstractConnector);
System.out.println("Generic Manager: " + genericManager);
AWSSecretDB awsSecretDB;
try {
awsSecretDB = genericManager.getSecretModel(secretName, AWSSecretDB.class);
System.out.println("awsSecretDB: " + awsSecretDB.getEngine()); // It must be mocked
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw e;
}
return awsSecretDB;
}
}
这是我当前的单元测试:
public class DataSourceAttributesTest {
@InjectMocks
private DataSourceAttributes dataSourceAttributes;
@Mock
private GenericManager genericManagerMock;
@Test
public void AWSSecretDBGetAttribsBySecret() throws Exception {
AWSSecretDB awsSecretDB = new AWSSecretDB();
awsSecretDB.setEngine("Engine Test");
awsSecretDB.setDbname("DB Test");
awsSecretDB.setHost("Host Test");
when(genericManagerMock.getSecretModel(ArgumentMatchers.any(), ArgumentMatchers.any())).thenReturn(awsSecretDB);
dataSourceAttributes.getAttribsBySecret();
// The assert is missing, but it's not important for this question
}
}
我需要模拟 genericManager 来控制 getSecretModel()函数,但这不起作用。
运行测试时,System.out.println(位于getAttribsBySecret中)会打印以下消息,表明该模拟无法正常工作:
Generic Manager: co.com.bancolombia.commons.secretsmanager.manager.GenericManager@1349883
我知道如果我使用以下代码,则该模拟很好用,但是我不想重新编码它已经在src目录中工作的东西:
@Component
public class DataSourceAttributes {
private GenericManager genericManager; // First change
public DataSourceAttributes () { // Second cahnge
AbstractConnector abstractConnector = new AWSSecretManagerConnector("secretsmanager." + region + ".amazonaws.com", region);
this.genericManager = new GenericManager(abstractConnector);
}
public AWSSecretDB getAttribsBySecret() throws Exception {
System.out.println("Generic Manager: " + genericManager);
AWSSecretDB awsSecretDB;
try {
awsSecretDB = genericManager.getSecretModel(secretName, AWSSecretDB.class);
System.out.println("awsSecretDB: " + awsSecretDB.getEngine());
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw e;
}
return awsSecretDB;
}
}
运行测试时,将打印System.out.println(位于getAttribsBySecret中):
GENERIC MANAGER: genericManagerMock
awsSecretDB: Engine Test
它如何显示,模拟效果很好。所以,这是我的问题:如何在类中使用模拟并避免在主代码中声明新属性和构造函数。我之所以这样问,是因为第一个代码有效,并且我不想编辑它,我认为这不是单元测试的精神。
谢谢!
答案 0 :(得分:2)
要使用单元测试,您需要首先使代码可测试。这可能需要一些代码更改。无法在方法内部模拟局部变量,因此您需要将这些变量作为参数传递给方法,或者在对象内部创建属性,然后将模拟对象传递给构造函数。
@Component
public class DataSourceAttributes {
private AbstractConnector abstractConnector;
private GenericManager genericManager;
@Autowired // to ask Spring to inject dependencies
public DataSourceAttributes(AbstractConnector abstractConnector, GenericManager genericManager) {
this.abstractConnector = abstractConnector;
this.genericManager = genericManager;
}
public AWSSecretDB getAttribsBySecret() throws Exception {
System.out.println("Generic Manager: " + genericManager);
AWSSecretDB awsSecretDB;
try {
awsSecretDB = genericManager.getSecretModel(secretName, AWSSecretDB.class);
System.out.println("awsSecretDB: " + awsSecretDB.getEngine()); // It must be mocked
} catch (Exception e) {
LOGGER.error(e.getMessage(), e);
throw e;
}
return awsSecretDB;
}
}
然后在测试中,创建对象实例,将模拟的依赖项传递给构造函数
答案 1 :(得分:0)
如果您知道该代码有效,那么为什么首先要对其进行单元测试?或者,如果您不打算更改源以修复该错误,为什么还要执行会引发错误的活动?
但是,更严重的是,您似乎陷入了“遗留代码难题”:要修改代码,您宁愿进行测试。要进行测试,您必须修改代码。
要避免这种情况,首先要进行微创更改,这些更改足以创建所需的测试。由于这是一种迁移方法,因此有时甚至可以使用肮脏的技巧来克服这一难题。迈克尔·费瑟斯(Michael Feathers)在“有效使用旧版代码”中提供了许多实用建议,对此进行了详细讨论。
在具有自省性的语言(如Java)中,有时自省可能是一种朝着更好的可测试解决方案发展的解决方案。但是,这属于肮脏的把戏,除了作为一种迁移到更好的解决方案的方式之外,但是,我认为这是不可取的。