Suppose I have a class Manager derived from a base class Employee, and that Employee has a method getEmail() that is inherited by Manager. Should I test that the behaviour of a manager’s getEmail() method is in fact the same as an employee’s?
At the time these tests are written the behaviour will be the same, but of
course at some point in the future someone might override this method, change its behaviour, and therefore break my application. However it seems a bit strange to essentially test for the absence of meddling code.
(Note that testing Manager::getEmail() method does not improve code coverage (or indeed any other code quality metrics (?)) until Manager::getEmail() is created/overridden.)
(If the answer is “Yes”, some information about how to go about managing tests that are shared between base and derived classes would be useful.)
An equivalent formulation of the question:
If a derived class inherits a method from a base class, how do you express (test) whether you’re expecting the inherited method to:
- Behave in exactly the same way as the base does right now (if the behaviour of the base changes, the behaviour of the derived method doesn’t);
- Behave exactly the same way as the base for all time (if the behaviour of the base class changes, the behaviour of the derived class changes as well); or
- Behave however it wants to (you don’t care about the behaviour of this method because you never call it).
3
I’d take the pragmatic approach here: If someone, in the future, overrides Manager::getMail, then it is that developer’s responsibility to provide test code for the new method.
Of course that’s only valid if Manager::getEmail
really has the same code path as Employee::getEmail
! Even if the method is not overridden, it might behave differently:
Employee::getEmail
could call some protected virtualgetInternalEmail
which is overridden inManager
.Employee::getEmail
could access some internal state (e.g. some field_email
), which can differ in the two implementations: For example, the default implementation ofEmployee
could ensure that_email
is always[email protected]
, butManager
is more flexible in assigning mail addresses.
In such cases, it is possible that a bug manifests itself only in Manager::getEmail
, even though the implementaion of the method itself is the same. In that case testing Manager::getEmail
separately could make sense.
0
I would.
If you’re thinking “well, it’s really only calling Employee::getEmail() because I don’t override it, so I don’t need to test Manager::getEmail()” then you’re not really testing the behavior of Manager::getEmail().
I would think of what only Manager::getEmail() should do, not whether or not it’s inherited or overridden. If the behavior of Manager::getEmail() should be to return whatever Employee::getMail() returns, then that’s the test. If the behavior is to return “[email protected]”, then that’s the test. It doesn’t matter whether it’s implemented by inheritance or overridden.
What does matter is that, if it changes in the future, your test catches it and you know something either got broken or needs to be reconsidered.
Some people may disagree with the seeming redundancy in the check, but my counter to that would be you’re testing the behavior Employee::getMail() and Manager::getMail() as distinct methods, whether or not they’re inherited or overridden. If a future developer needs to change the behavior of Manager::getMail() then they need to also update the tests.
Opinions may vary though, I think Digger and Heinzi gave reasonable justifications for the opposite.
3
Don’t unit test it. Do functional/acceptance test it.
Unit tests should test every implementation, if you are not providing a new implementation then stick by the DRY principle. If you want to spend some effort here then you can enhance the original unit test. Only if you override the method should you write a unit test.
At the same time, functional/acceptance testing should make sure that at the end of the day all of your code does what it is supposed to, and will hopefully catch any weirdness from inheritance.
0
Robert Martin’s rules for TDD are:
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
If you follow these rules, you wouldn’t be able to get into a position to ask this question. If the getEmail
method needs to be tested, it would have been tested.
0
Yes, you should test inherited methods because in the future they may get overridden. Also, inherited methods may call virtual methods that are overridden, which would change the behavior of the non-overridden inherited method.
The way I test this is by creating an abstract class to test the (possibly abstract) base class or interface, like this (in C# using NUnit):
public abstract class EmployeeTests
{
protected abstract Employee CreateInstance(string name, int age);
[Test]
public void GetEmail_ReturnsValidEmailAddress()
{
// Given
var sut = CreateInstance("John Doe", 20);
// When
string email = sut.GetEmail();
// Then
Assert.IsTrue(Helper.IsValidEmail(email));
}
}
Then, I have a class with tests specific to the Manager
, and integrate the employee’s tests like this:
[TestFixture]
public class ManagerTests
{
// Other tests.
[TestFixture]
public class ManagerEmployeeTests : EmployeeTests
{
protected override Employee CreateInstance(string name, int age);
{
return new Manager(name, age);
}
}
}
The reason I can so this is Liskov’s substitution principle: the tests of Employee
should still pass when passed a Manager
object, since it derives from Employee
. So I have to write my tests only once, and can verify they work for all possible implementstions of the interface or base class.
2
Should I test that the behaviour of a manager’s getEmail() method is
in fact the same as an employee’s?
I would say no as it would be a repeated test in my opinion I would test once in the Employee tests and that would be it.
At the time these tests are written the behaviour will be the same,
but of course at some point in the future someone might override this
method, change its behaviour
If the method is overridden then you would need new tests to check the overridden behaviour. That is the job of the person implementing the overriding getEmail()
method.
No, you do not need to test inherited methods. Classes and their test cases which rely on this method will break anyway if the behaviour changed in Manager
.
Think of the following scenario: The email address is assembled as [email protected]:
class Employee{
String firstname, lastname;
String getEmail() {
return firstname + "." + lastname + "@example.com";
}
}
You have unit tested this and it works fine for your Employee
. You also have created a class Manager
:
class Manager extends Employee { /* nothing different in email generation */ }
Now you have a class ManagerSort
which sorts managers in a list based on their email address. Of yourse you suppose that the email generation is the same as in the Employee
:
class ManagerSort {
void sortManagers(Manager[] managerArrayToBeSorted)
// sort based on email address omitted
}
}
You write a test for your ManagerSort
:
void testManagerSort() {
Manager[] managers = ... // build random manager list
ManagerSort.sortManagers(managers);
Manager[] expected = ... // expected result
assertEquals(expected, managers); // check the result
}
Everything works fine. Now someone comes and overrides the getEmail()
method:
class Manager extends Employee {
String getEmail(){
// managers should have their lastname and firstname order changed
return lastname + "." + firstname + "@example.com";
}
}
Now, what happens? Your testManagerSort()
will fail because the getEmail()
of Manager
was overridden. You will investigate in this issue and will find the cause. And all without writing a seperate testcase for the inherited method.
Therefore, you do not need to test inherited methods.
Otherwise, e.g. in Java, you would have to test all methods inherited from Object
like toString()
, equals()
etc. in every class.
2