Unit testing 在哪里测试私有方法返回的对象?
我刚开始练习TDD,在编写看似简单的单元测试时,我面临着一个设计问题 被测试的方法做两件事:Unit testing 在哪里测试私有方法返回的对象?,unit-testing,junit,mocking,tdd,Unit Testing,Junit,Mocking,Tdd,我刚开始练习TDD,在编写看似简单的单元测试时,我面临着一个设计问题 被测试的方法做两件事: 调用将对象a转换为对象B的私有方法 调用另一个将对象B作为参数传递的私有方法 大概是这样的: public void doStuff(对象A){ B objectB=convertToB(objectA); 过程B(对象B); } 现在,我在哪里测试转换是否正确完成? 流行的观点认为使用TDD没有必要使用PowerMock或其他库来测试私有方法。我引用JUnit和Mockito书籍中的实用单元测试: 你
public void doStuff(对象A){
B objectB=convertToB(objectA);
过程B(对象B);
}
现在,我在哪里测试转换是否正确完成?
流行的观点认为使用TDD没有必要使用PowerMock或其他库来测试私有方法。我引用JUnit和Mockito书籍中的实用单元测试:
你能做的,也许应该做的第一件事就是避免这种情况。怎么用?按照
TDD方法。考虑一下当您首先进行代码测试时,私有方法是如何产生的。答案
也就是说,它们是在重构阶段创建的,这意味着它们的内容完全由测试覆盖
(假设您确实遵循TDD规则,并且仅在测试失败时编写代码)。这样
在某些情况下,“未经测试的私有方法应该以某种方式进行测试”不存在问题,因为
这种方法根本不存在
我的下一个想法是使用ArgumentCaptor并验证是否使用正确的参数调用了processB()。但是,同样,processB()
也是私有的,所以不能这样做
当然,为了使我的类可测试,可以使用很多技巧—在类字段中保存objectB
,或者将我的一个私有方法公开。但这将恶化,而不是改善我的设计
因此,在这种情况下,我的问题是:测试转换方法的正确方法是什么?什么样的设计改进可以使此代码可测试
编辑:添加真实示例以更好地了解问题:
public class EmailSender {
public EmailResult send(Email email) {
MultivaluedMapImpl formData = prepareFormData(email);
EmailResult emailResult = processEmailRequest(formData);
}
private MultivaluedMapImpl prepareFormData(Email email) {
MultivaluedMapImpl formData = new MultivaluedMapImpl();
formData.add(FROM_KEY, email.getSender());
email.getRecipients().stream().forEach((recipient) -> {
formData.add(TO_KEY, recipient);
});
formData.add(SUBJECT_KEY, email.getSubject());
formData.add(TEXT_KEY, email.getText());
return formData;
}
private EmailResult processEmailRequest(MultivaluedMapImpl formData ) {
Client client = Client.create();
client.addFilter(new HTTPBasicAuthFilter("api", "API_KEY"));
WebResource webResource = client.resource(API_URL);
ClientResponse clientResponse = webResource.type(MediaType.APPLICATION_FORM_URLENCODED).
post(ClientResponse.class, formData);
String resultString = clientResponse.getStatusInfo().getFamily().toString();
EmailResult emailResult = resultString.equals("SUCCESSFUL") ? EmailResult.SUCCESS : EmailResult.FAILED;
return emailResult;
}
}
这里,prepareFormData()
对应于前面示例中的转换方法。我试图测试的是转换是否正确。TDD背后的想法是测试功能,而不是方法。所以问题来了
测试转换方法的正确方法是什么
如果你想使用TDD方法,你不应该问这个问题
什么样的设计改进可以使此代码可测试
这是一个更好的问题。要对doStuff(对象a)
进行适当的测试,您需要回到规范:doStuff
应该做什么?事实上,它的返回类型是void,这使得事情更难可视化,但让我们假设它执行以下操作之一:
- doStuff(A1)应产生结果C1
- doStuff(A2)应产生结果C2
- 等等
convertToB()
被破坏,则测试结果不应与预期值相对应,并且失败。因此,您的转换方法将包含在测试中
编辑:
我将使用您提供的真实示例来说明我的观点
1) 首先,Client
是一个外部依赖项,因此必须对其进行模拟以进行适当的单元测试。为此,您需要去掉静态依赖项Client=Client.create()`并用构造函数或setter注入替换它。这里是一个详细的山楂做这件事
2) 现在我们可以模拟客户端:
Client mockClient = Mockito.mock(Client.class, Mockito.RETURN_DEEP_STUBS);
WebResource mockWebResource = Mockito.mock(WebResource.class);
Mockito.doReturn(mockWebResource).when(mockClient).resource(Mockito.anyString()); //assuming API_URL is a string
EmailSender sender = new EmailSender(mockClient);
3) 准备一个具体的测试用例:
// actual email details
Email email = new Email();
email.setSender("john@domain.com");
email.setRecipients("chris@domain.com", "bob@domain.com");
//etc.
4) 执行测试代码
sender.send(email);
5) 验证结果
// capture parameter
ArgumentCaptor<MultivaluedMapImpl> argument = ArgumentCaptor.forClass(MultivaluedMapImpl.class.class);
Mockito.verify(mockWebResource, Mockito.times(1)).post(Mockito.any(Class.class), argument.capture());
Assert.assertEqual(email.getSender(), argument.getValue().get(FROM_KEY);
Assert.assertEqual(email.getRecipients(), argument.getValue().get(TO_KEY);
// etc.
//捕获参数
ArgumentCaptor参数=ArgumentCaptor.forClass(多值MapImpl.class.class);
Mockito.verify(mockWebResource,Mockito.times(1)).post(Mockito.any(Class.Class),argument.capture());
Assert.assertEqual(email.getSender(),argument.getValue().get(从_键);
Assert.assertEqual(email.getRecipients(),argument.getValue().get(TO_键);
//等等。
请注意,您返回的SUCCESS
或FAILED
结果与此无关,因为它不是EmailSender
类的责任,而是Client
类的责任,因此不应在EmailSenderTest
中对其进行测试。这里是一组完整(且有效)的测试(未修改)EmailSender
class:
import javax.ws.rs.core.*;
import com.sun.jersey.api.client.*;
import com.sun.jersey.api.client.WebResource.Builder;
import static email.EmailSender.*;
import mockit.*;
import static org.junit.Assert.*;
import org.junit.*;
public class EmailSenderTest {
@Tested EmailSender emailSender;
@Mocked Client emailClient;
@Mocked ClientResponse response;
Email email;
@Before
public void createTestEmail() {
email = new Email();
email.setSender("john@domain.com");
email.setRecipients("chris@domain.com", "bob@domain.com");
email.setSubject("Testing");
email.setText("Just a test");
}
@Test
public void successfullySendEmail() {
new Expectations() {{
response.getClientResponseStatus(); result = ClientResponse.Status.OK;
}};
EmailResult result = emailSender.send(email);
new Verifications() {{
// Verifies correct API URL and media type:
Builder bldr = emailClient.resource(API_URL).type(
MediaType.APPLICATION_FORM_URLENCODED);
// Verifies correct form data:
MultivaluedMap<String, String> formData;
bldr.post(ClientResponse.class, formData = withCapture());
assertEquals(email.getSender(), formData.getFirst(FROM_KEY));
assertEquals(email.getRecipients(), formData.get(TO_KEY));
assertEquals(email.getSubject(), formData.getFirst(SUBJECT_KEY));
assertEquals(email.getText(), formData.getFirst(TEXT_KEY));
}};
assertSame(EmailResult.SUCCESS, result);
}
@Test
public void failToSendEmail() {
new Expectations() {{
response.getClientResponseStatus();
result = ClientResponse.Status.NOT_FOUND;
}};
EmailResult result = emailSender.send(email);
// No need to repeat here the verification for URL, form data, etc.
assertSame(EmailResult.FAILED, result);
}
}
导入javax.ws.rs.core.*;
导入com.sun.jersey.api.client.*;
导入com.sun.jersey.api.client.WebResource.Builder;
导入静态电子邮件。EmailSender.*;
输入mockit.*;
导入静态org.junit.Assert.*;
导入org.junit.*;
公共类EmailSenderTest{
@测试邮件发送者;
@模拟客户机;
@嘲弄的回应;
电子邮件;
@以前
public void createTestEmail(){
电子邮件=新电子邮件();
电子邮件。setSender(“john@domain.com");
email.setRecipients(“chris@domain.com", "bob@domain.com");
电子邮件。setSubject(“测试”);
setext(“只是一个测试”);
}
@试验
public void成功发送邮件(){
新期望(){{
response.getClientResponseStatus();result=ClientResponse.Status.OK;
}};
EmailResult结果=emailSender.send(电子邮件);
新的核查(){{
//验证正确的API URL和媒体类型:
Builder bldr=emailClient.resource(API_URL).type(
MediaType.APPLICATION\u FORM\u URLENCODED);
//验证是否正确