6

Say I have a method A which calls a method B and does one additional thing. The B method behaves differently in 10 different cases and I have a broad unit test describing it. And now I want to test my method A. What is the best practice to do that? Should I run the same test with all 10 cases for method A to make sure that proper method is run? It sounds like an overkill. Or maybe I should test only one of that cases? But there's a risk that in future I'll write some method which will be quite similar to B and there will occure a bug which won't be detected by the test. Or maybe there are some ways of checking that my B method was fired just by registering its name? Maybe that's quite simple problem, but I'm new in unit testing and I haven't found any clear answer.

Karol Selak
  • 221
  • 2
  • 8
  • 1
    Possible duplicate of [How should I test the functionality of a function that uses other functions in it?](https://softwareengineering.stackexchange.com/questions/225323/how-should-i-test-the-functionality-of-a-function-that-uses-other-functions-in-i) – gnat May 23 '18 at 10:45

2 Answers2

7

You're question isn't completely clear to me, so I'm making the following assumptions:

  1. Method A and B are both "public",
  2. Method B has 10 execution paths through it and 10 test cases covering those paths,
  3. Method A calls B via at least one of its execution paths.
  4. There are paths through B that are not used by A.

Also, I'm going to have to ignore "But there's a risk that in future I'll write some method which will be quite similar to B and there will occur a bug which won't be detected by the test" as I don't see how it's related to either your question or testing A and B.

The first thing I'd say is do not try to mock B, unless you absolutely have to. This is a classic testing newbie mistake that many folk make. You only want to isolate A from B if the latter has side-effects or is slow. If you mock out B, you'll simply be testing A against the mock functionality, rather than B's real functionality and you'll miss bugs as a result.

The next thing to ask yourself is, can you repurpose some of those B test cases to have them test A and B together? So if you have four paths through B that are used by A, test those paths via testing A. There's a huge caveat here though. Bear in mind that each of those tests will then tightly couple itself to existing functionality of A and B. If you later change A to have that functionality itself for example, you could end up not testing that part of B. Or changes might break tests just because the test is now brittle, not because you broke functionality, etc. As ever, there's a balance to be had between principle (test everything!) and pragmatism (I've got to ship this one day for there to be value in what I'm doing).

David Arno
  • 38,972
  • 9
  • 88
  • 121
  • Deleted my answer after reading yours. Expert. – Dan Rayson May 23 '18 at 11:35
  • 4
    @DanRayson, No, not the dreaded "expert" tag. :) Experts (think) they are masters of their domain; knowing all there is to know. After 35 years of software development, I still strive to be a beginner: there's just still so much to learn and so many good, new ideas coming through all the time. I'll accept "highly opinionated know-it-all", but never "expert" :) And I don't think you should delete your answer either. Let others decide on the relative worth of each. – David Arno May 23 '18 at 11:51
  • @DavidArno, Is it true that as soon as we add an additional dependency into our SUT (method B), we are no longer unit testing, but instead, we are integration testing? I usually create two test projects per dll. A unit testing project, in which I mock out ALL dependencies in the SUT, and an integration testing project, in which I test the SUT with all its dependencies. – Vin Shahrdar May 29 '18 at 16:01
1

I'm new in unit testing and I haven't found any clear answer.

In many cases, there isn't a clear answer: just different trade offs that you need to evaluate for your own circumstances.

The talk you need to watch is Integrated Tests are a Scam, by J.B. Rainsberger. See also his blog.

You might also want to review some wisdom from Kent Beck (2008)

I get paid for code that works, not for tests, so my philosophy is to test as little as possible to reach a given level of confidence

One of the motivations of unit tests is that they constrain your choice of implementations; the idea being that the tests give you a signal that your changes have produced an observable change in the behavior of your system.

So if you have a bunch of tests for B, and assume that those cover A as well, that's fine right up until the point that somebody decides to change the implementation of A.

If A is-a B; meaning that you are supposed to be able to replace a B with an A and still have a correct program, then a potentially useful trick is to write one suite of tests that gets invoked on both A and B.

void verifyThatItWorks(SystemUnderTest sut) {
    // this is where the implementation of the test lives
    // including the setup, activity, and verification
    // aka arrange, act, assert
}

@Test
void testB () {
    verifyThatItWorks(new B())
}

@Test
void testA () {
    verifyThatItWorks(new A())
}

In some programming languages, there are conveniences that allow you to manage parallel copies of large test suites

abstract class AbstractSpecification {
    SystemUnderTest sut

    protected AbstractSpecification(SystemUnderTest sut) {
        this.sut = sut;
    }

    @Test behavior1 () {...}
    @Test behavior2 () {...} 
    // ...
}

class BSpecification extends AbstractSpecification {
    BSpecification () { super (new B()); }
}
class ASpecification extends AbstractSpecification {
    ASpecification () { super(new A()); }
}
VoiceOfUnreason
  • 32,131
  • 2
  • 42
  • 79