简体   繁体   中英

Public methods outside of an interface in a class

Example:

public interface IFoo
{
    bool DoSomething();
}

public class Foo:IFoo
{
    public bool DoSomething()
    {
        var result = DoOtherThing();
        ...
        return result;
    }

    public bool DoOtherThing()
    {
        ...
    }
}

My normal TDD approach would be to write unit tests on both the DoSomething() and DoOtherThing() methods. But this would be very hard to do if DoOtherThing was a private method. I have also read that testing private methods is a no-no.

Is it considered acceptable to have public methods on a class in order to facilitate code coverage and testing, even if the purpose of the class is only to be accessed through its (IFoo) interface? Normally I would put methods that are outside of the scope of the interface as private methods, but this does not allow you to efficiently test all of your code. Making the method public allows you to right tests on the Foo class, but it doesn't seem correct, at least to me, to have public methods that aren't called from outside the class. Is this way considered best for TDD or is there a better way?

Public methods are public because they should be accessible. Make it private if it's not intended to be invoked from the outside.

If you fail to get code coverage you might want to break up your class into multiple classes and use composition instead.

Things that are hard to test usually indicates a design flaw.

Update

ok, so lets say you have a method for sending an email. Step 1 is generating a MailMessage class and populating it. Step 2 is sending the email

Thats two responsibilites (SRP). Composing an email and actually send it. I would not do that in the same class. It will also be code duplication if all your email classes compose their messages and then send them. How do you handle network failure? Do you duplicate those checks too?

Do something like:

public class SendWelcomeEmailComposer
{
    MailMessage Compose(User user)
}

public class EmailSender
{
    void SendEmail(MailMessage);
}

public class EmailService
{
    void SendWelcomeEmail(User user)
    {
        // compose email
        // and send using the classes above.
    }
}

Update 2

The reason to why you should not test private methods is as far as I concern a quality measure. It's likely that you are breaking some fundamental principles (SOLID) if you get low test coverage.

So it's better to take time and reflect over the class design instead of trying to test private methods.

The trick here is to use

[assembly: InternalsVisibleTo("NameOfYourTestAssembly")] 

in the AssemblyInfo.cs file.

This allows you to make your testable methods internal, which means they are only accessible in the assembly you are writing, AND the assemblies in this attribute.

(If you have a Mycode.dll and a Mycode.Tests.dll then you add the attribute to MyCode/Properties/AssemblyInfo.cs)

I think you need to concentrate on what to test and you'll see that the issue of how to design an object (or objects) that pass the tests is only a matter of preference and convenience. There is no one right answer to your problem.

Since you decided to break up your global operation in 2 steps, here's what you need to test :

  1. Verify that the first part ( DoSomething() ) behaves as expected. This might include testing if it calls the right dependencies, places the Foo object in the right state, etc.

  2. Verify that the first step is followed by the second one, in other words that DoSomething() calls DoOtherThing() , if needed with the right parameters.

  3. Verify that the second step ( DoOtherThing() ) behaves as expected. Again, this might include it talking to its dependencies correctly, producing the right output, and so on.

No on to the how . While #1 is quite straightforward to test and implement since we have the prerequisite that DoSomething() is public, #2 and #3 leave your implementation and testing options much more open. There are basically 2 things you could do :

  • Leave the 2 responsibilities in a single class. This option in turn breaks down in many possibilities : making DoOtherThing() public (easy to test but not safe, as we might not want to expose the internal substeps of the operation to the outside), making it internal as @AlSki points out, making it protected virtual and using a partial mock in your test to the verify the collaboration between the 2 methods. The list certainly goes on.

  • Give each step its own class. This is especially relevant if they are really 2 distinct responsibilities dealing with different parts of the system or talking to different layers. Your usual mocking and collaboration testing applies here.

Side note 1 : Should you have failed to distinguish between the 2 responsibilities in the operation and kept the two steps together in a single method, things would have been quite different since your test would have really been an integration test instead of a unit test. This can pose some problems, like only concentrating on what happens at each end of the pipe and failing to verify the correctness of all intermediate jobs. As a consequence, I think it is always better to break up large operations in as many sensible steps as possible (like in the email sending example where creating the right MailMessage data structure is clearly a different responsibility than sending it) and test each one of them.

Side note 2 : The fact that your class implements IFoo is only remotely related to all this. It basically affects the other side of the coin - defining the entry point to your class from other classes. If you want to test things in isolation, you'll probably have to create IFoo mocks in IFoo consumers' tests and verify that these consumer classes call DoSomething() properly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM