12
I’m practicing TDD simulating an alarm system. Alarm centers work connected to sensors that detect intrusion (opening a door or window, or movement within a room, for example). They have a fixed number of logical partitions (representing different locations to be protected) and slots for sensors. At the installation the sensors are associated to the respective partitions. Then the interface of my alarm center includes the following methods (Obs.: " arming" a partition means protecting it, i.e., the center will trigger the alarm if a partition sensor is triggered, and "disarm" means unprotected, i.e., ignore the drive of the partition sensors):
public interface InterfaceDeCentralDeAlarme {
boolean armarParticao(int numero) throws IllegalArgumentException, IllegalStateException;
boolean desarmarParticao(int numero) throws IllegalArgumentException, IllegalStateException;
void associarSensorAParticao(int sensor, int particao) throws IllegalArgumentException, IllegalStateException;
void desassociarSensorDeParticao(int sensor, int particao) throws IllegalArgumentException, IllegalStateException;
...
}
Here are some of the possible unit tests:
@Test(expected=IllegalStateException.class)
public void particaoSemSensoresAssociadosNaoPodeArmar() {
InterfaceDeCentralDeAlarme central = criarCentralDeAlarme();
central.armarParticao(1);
}
@Test(expected=IllegalStateException.class)
public void particaoJaArmadaNaoPodeArmar() {
InterfaceDeCentralDeAlarme central = criarCentralDeAlarme();
central.associarSensorAParticao(1, 1);
central.fecharSensor(1);
Assert.assertTrue(central.armarParticao(1));
central.armarParticao(1);
}
The method armarParticao()
delegates the arme to a class called Particao
. So far, no big deal:
@Override
public boolean armarParticao(int numero) throws IllegalArgumentException, IllegalStateException {
Particao particao = particoes.get(numero);
if (particao == null) {
throw new IllegalArgumentException();
}
return particao.armar();
}
In class Particao
created a public method armar()
that can cast IllegalStateException
for two different reasons, that is, two different situations in which the object is in an improper state. One is when the partition is already armed; another is when the partition has no sensors associated with it (i.e., there is no way to detect intrusion):
public class Particao {
private final Map<Integer, Sensor> sensores = new HashMap<>();
private boolean armada = false;
public boolean armar() throws IllegalStateException {
if (armada) {
throw new IllegalStateException("Partição já se encontra armada");
}
if (sensores.isEmpty()) {
throw new IllegalStateException("Partição não possui sensor associado");
}
for (Sensor sensor : sensores.values()) {
if (sensor.isAberto() && false == sensor.isInibido()) {
return false;
}
}
this.armada = true;
return true;
}
...
}
From the point of view of unit tests and TDD, is that bad? A colleague said yes, because if the method launches a IllegalStateException
I’ll never be sure if it’s because of one reason or because of the other. The exception thrown may mask a different situation than the one I’m trying to test.
But I’m not sure that’s a problem. Maybe it is if I don’t have tests for every possible situation (including the two situations where IllegalStateException
can be launched). But if I have, maybe this overlap doesn’t get to be a problem. In practice, when writing unit tests or doing TDD people have the habit of avoiding it (throw the same exception in a method in two different situations)?
Second question: if I have to use different exceptions, should I create custom exceptions (new classes) for each of the situations, or is this a more or less arbitrary decision? A(s) exception(s) created(s) shall be subclasses of IllegalStateException
?
That recommendation "If it’s going to change a small detail, it might be too much, but if it’s something unrelated, it might be the case" It sounded a little vague, because of the very question, which did not show a concrete case. I will go into more detail the scenario to, if you think it is the case, leave the answer more targeted. But I am already quite satisfied with it.
– Piovezan
It’s actually complicated what you did because despite having kept the subject, the question is quite another. Tomorrow I take a look.
– Maniero
It was not my intention to change the question, just to specify the situation. All right, tomorrow we’ll see what you do with it. And to answer your question, my colleague did not specify how to deal with the exceptions and left that to my charge, just recommended that I make two separate exceptions.
– Piovezan
My question is rhetorical :)
– Maniero
In the end I think the question has not changed so much, I could keep most of the original answer. : ) I understood that an error code may be better for the already armed partition, while the other situation may not even need to exist, in fact it is not required of the class that these associations are dynamic; the class could be initialized with the sensors associated with the partitions already in the construction (I think that’s what you meant, no? ). This abundance of
throws IllegalStateException
on the interface can even be considered a code Smell.– Piovezan
That’s right. But it’s obvious that only you can confirm if this is possible. I think it’s code Smell, not everyone does. Each one has his own vision, right?... : ) Although code Smell don’t mean error, of course. But the most important thing is that if you want to keep the class like this, you can test it. If the question had like this from the beginning, I could have done something closer to the face, I didn’t want to touch much of what I had already written.
– Maniero
Stretching the question a little, and the
IllegalArgumentException
, would you keep? And as for the TDD being wrong, actually I didn’t even do the right TDD, I’m doing an experiment mixing post-design testing with a little bit of TDD and seeing how it affects my design. But it is really controversial to say that "if the TDD is the specification, then it is wrong", because I have the impression that this is exactly what the TDD proposes to be.– Piovezan
I don’t, but I’m a bit radical in the use of exceptions :) I would avoid one of them being cast guaranteed the proper state and the other I would find a way to notify otherwise that it is already armed, but I understand that this is not common in Java, the exception is preferred, People are even more used to it. I am against TDD in almost every situation. Your TDD is wrong :) It specifies something that should not be in the method (publicly). Of course I’m talking about the case of doing what I think you should, not releasing these exceptions. TDD specified them, right?
– Maniero
To
IllegalArgumentException
that I speak in the case is when I callarmarParticao(int)
passing an invalid partition number. And I’m not sure if it’s in the method signature because of the TDD, I don’t think so. TheIllegalStateException
Yes, but I might as well be doing it wrong. I actually think I should stop doing the TDD thing by half. But the discussion and the insights it generates are so productive. :)– Piovezan
But TDD should specify everything that is public, right? This is another reason I don’t like TDD, everyone thinks something about the subject :D
– Maniero
Yes, I should specify everything that is public. I am not saying that what I did was the correct TDD... I did not follow exactly what the TDD proposes. I don’t know if I’m ready to embrace TDD. I think it’s kind of religious, this whole "go three steps without question and your code will reach enlightenment" thing. It’s kind of magical that the design goes right and the productivity increases in the long term although it decreases in the short. But we try it right.
– Piovezan
I had interpreted that in the case of the already armed partition I should use error code because it is a business rule. Reading more on the subject I saw that actually this is not a problem in itself, the error code is actually recommended because it is a predicted situation to happen. Business exceptions are acceptable as long as they are exception and not rule, and may include improve code expressiveness in streams with many steps.
– Piovezan