BDD 方式的 JUnit 测试应该如何集中
我正在测试一些代码进行练习,并发现了奇怪的情况。
有一个 ChannelRegistry 包含所有通信通道引用,以及 PrimaryConsumer 需要将其自身附加到在调用初始化()时在运行时选择的通道之一。 所以我完成了第一个测试,如下:
@RunWith(MockitoJUnitRunner.class)
public class PrimaryConsumerTest {
private @Mock ChannelsRegistry communicationRegistry;
private PrimaryConsumer consumer;
@Before
public void setup() {
consumer = new PrimaryConsumer(communicationRegistry);
}
@Test
public void shouldAttachToChannel() throws Exception {
consumer.initialize();
verify(communicationRegistry).attachToChannel("channel", consumer);
}
}
我正在检查是否调用了附加方法。为了让它变成绿色,我像这样放置 impl:
public void initialize() {
communicationRegistry.attachToChannel("channel", this);
}
现在下一个测试:按名称获取通道 id 并附加到此特定通道。我希望我的测试描述类的行为而不是其内部结构,所以我不希望我的测试是“shouldGetSpecificChannel”。相反,我检查它是否可以附加到运行时选择的通道:
@Test
public void shouldAttachToSpecificChannel() throws Exception {
String channelName = "channel";
when(communicationRegistry.getChannel("channel_name")).thenReturn(channelName);
consumer.initialize();
verify(communicationRegistry).attachToChannel(channelName, consumer);
}
该测试立即通过,但实现被搞砸了(“通道”硬编码)。
这里有 2 个问题:
可以对这种行为进行 2 次测试吗?也许我应该在第一次测试中立即获取通道?如果是这样,它如何映射到在单个测试中测试单个事物?
如何应对这种情况:测试绿色,impl“硬编码”?我应该用不同的频道名称编写另一个测试吗?如果是这样,我应该在更正 impl 后删除它(因为它变得无用?)
更新: 只是一些澄清。 我在这里对“通道”进行了硬编码,
public void initialize() {
communicationRegistry.attachToChannel("channel", this);
}
只是为了使第一个测试快速通过。但是,当运行第二次测试时,它立即通过。我不验证是否调用了存根方法,因为我认为不应显式验证存根。 罗德尼说测试是多余的,这就是你的意思吗?如果是,我应该在第一次测试的一开始就制作存根吗?
I'm test-driving some code for practice and spotted strange situation.
There is a ChannelRegistry that contains all communication channels references, and PrimaryConsumer who needs to attach itself to one of those channels choosen in runtime when initialize() called.
So I've done my first test as follows:
@RunWith(MockitoJUnitRunner.class)
public class PrimaryConsumerTest {
private @Mock ChannelsRegistry communicationRegistry;
private PrimaryConsumer consumer;
@Before
public void setup() {
consumer = new PrimaryConsumer(communicationRegistry);
}
@Test
public void shouldAttachToChannel() throws Exception {
consumer.initialize();
verify(communicationRegistry).attachToChannel("channel", consumer);
}
}
I'm checking if attaching method is called. To get it green I put impl like that:
public void initialize() {
communicationRegistry.attachToChannel("channel", this);
}
Now next test: get channel id by name and attach to this specific channel. I want my test to describe class' behavior instead of its internals so I don't want my test to be "shouldGetSpecificChannel". Instead I check if it can attach to channel selected in runtime:
@Test
public void shouldAttachToSpecificChannel() throws Exception {
String channelName = "channel";
when(communicationRegistry.getChannel("channel_name")).thenReturn(channelName);
consumer.initialize();
verify(communicationRegistry).attachToChannel(channelName, consumer);
}
This test passes immediately, but implementation is screwed ("channel" hardcoded).
2 questions here:
is it ok to have 2 tests for such behavior? Maybe I should stub getting channel immediately in first test? If so, how does it map to testing single thing in single test?
how to cope with such situation: tests green, impl "hardcoded"? Should I write another test with different channel's name? If so, should I remove it after correcting impl (as it gets useless?)
UPDATE:
Just some clarifications.
I've hardcoded "channel" here
public void initialize() {
communicationRegistry.attachToChannel("channel", this);
}
just to make first test pass quickly. But then, when running second test it passes immediately. I don't verify if stubbed method was called as I think stubs should not be verified explicitly.
Is this what you Rodney mean saying test are redundant? If yes shoud I make stub at the very beginning in the first test?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
更多的测试通常比太少的测试更好,所以两次测试就可以了。更好的问题是这两个测试是否多余:是否存在任何情况或输入组合会使其中一个测试失败,但另一个测试不会失败?那么这两个测试都是需要的。如果它们总是一起失败或成功,那么您可能只需要其中之一。
什么时候您需要为
channelName
使用不同的值?听起来这是一个与这些特定测试无关的配置设置。没关系,也许您会在集成测试中在更高级别上测试该配置。我更关心的是为什么它首先是硬编码的:它应该被注入到你的类中(可能通过构造函数)。然后您可以测试不同的频道名称——也可以不测试。无论哪种方式,您都不想仅仅为了测试而更改代码(如果这意味着在完成后将其更改回来)。More tests is usually preferred to too few, so two tests is fine. A better question is whether the two tests are redundant: is there any situation or combination of inputs that would make one of the tests fail, but not the other? Then the two tests are both needed. If they always fail or succeed together, then you probably need only one of them.
When would you need a different value for
channelName
? It sounds like this is a configuration setting that is irrelevant to these particular tests. That's fine, perhaps you would test that configuration at a higher level, in your integration tests. A bigger concern I would have is why it's hard-coded in the first place: it should be injected into your class (probably via the constructor). Then you can test different channel names -- or not. Either way, you don't want to be changing your code just for testing if it means changing it back when you're done.对于多次测试的问题,基本上与罗德尼相同。根据您的更新,我建议一两件事。
首先,您在两个测试中使用了相同的数据。 Kent Beck 在关于 TDD 的书中提到了“三角测量”的使用。如果您在第二种情况下使用了不同的参考数据,那么如果您不做任何额外的工作,您的代码将无法通过。
另一方面,他还提到消除所有重复,而重复包括代码和测试之间的重复。在这种情况下,您可以将两个测试保持原样,并通过替换被测类中的文字来重构代码中的字符串
"channel"
与测试中的相同字符串之间的重复调用您的communicationRegistry.getChannel()
。经过这次重构之后,您现在将字符串文字放在一个且只有一个位置:测试。两种不同的方法,相同的结果。您使用哪一种取决于个人喜好。在这种情况下,我会采取第二种方法,但这只是我的情况。
提醒查看罗德尼对多次测试问题的回答。我猜你可以删除第一个。
谢谢!
布兰登
Basically ditto Rodney for the question of multiple tests. I would suggest, based on your update, one or two things.
First off, you have used the same data for both tests. In the Kent Beck book on TDD he mentions the use of "Triangulation". If you used different reference data in the second case then your code would not have passed without any additional work on your part.
On the other hand, he also mentions removing all duplication, and duplication includes duplication between the code and the tests. In this scenario you could have left both of your tests as is, and refactored out the duplication between the string
"channel"
in the code and the same in the test by replacing the literal in your class under test with the call to yourcommunicationRegistry.getChannel()
. After this refactoring you now have the string literal in one and only one place: The test.Two different approaches, same result. Which one you use comes down to personal preference. In this scenario I would have taken the second approach, but that's just me.
Reminder to check out Rodney's answer to the question of multiple tests or not. I'm guessing you could delete the first one.
Thanks!
Brandon