Должны ли все открытые методы быть объявлены в интерфейсе? - PullRequest
1 голос
/ 01 мая 2019

Имеется такой интерфейс:

public interface IFoo
{
    string Bar();
}

И класс, который его реализует, например:

public class Foo : IFoo
{
    public string Bar()
    {
        returns "Bar";
    }

    public string SomeOtherMethod()
    {
        returns "Something else";
    }
}

Есть ли проблема с этим кодом?Должны ли мы добавить всех членов к интерфейсу?

Один пример: представьте себе частный метод, который становится достаточно сложным для проведения модульных тестов.Не могли бы вы сделать его общедоступным (чтобы его можно было вызвать из тестового проекта), но не добавить его в интерфейс (потому что ни один клиент не должен вызывать его)?

Ответы [ 3 ]

2 голосов
/ 02 мая 2019

Как правило, когда класс реализует интерфейс, все члены интерфейса должны быть реализованы, это не наоборот.

Кроме того, при реализации элемента интерфейса соответствующий член класса реализации должен быть открытым, нестатичным и иметь то же имя и сигнатуру параметра, что и элемент интерфейса. Вы можете иметь методы, даже публичные, в классе, которые не определены в интерфейсе.

Кроме того, сам интерфейс не предоставляет никакой функциональности, которую класс или структура могут наследовать так, как он может наследовать функциональность базового класса. Однако, если базовый класс реализует интерфейс, любой класс, производный от базового класса, наследует эту реализацию.

1 голос
/ 02 мая 2019

Если то, что делает класс, достаточно просто, чтобы мы могли одновременно тестировать его публичные и приватные методы, тогда мы можем просто протестировать публичные методы.

Если приватный метод становится настолько сложным, что между публичным и приватным методом нам нужно слишком много комбинаций тестов, тогда пришло время разделить приватный метод на его собственный класс. Публичный публичный метод нарушил бы инкапсуляцию класса. Даже если мы не добавим метод в interface, общедоступный метод класса все равно добавит метод в открытый интерфейс самого класса .

Итак, если у нас есть это:

public class ClassWithComplexPrivateMethod
{

    public void DoSomething(int value)
    {
        PrivateMethodThatNeedsItsOwnTests(value);
    }

    private string PrivateMethodThatNeedsItsOwnTests(int value)
    {
        // This method has gotten really complicated!
        return value.ToString();
    }
}

Мы можем изменить рефакторинг на что-то вроде этого:

public interface IRepresentsWhatThePrivateMethodDid
{
    string MethodThatNeedsItsOwnTests(int value);
} 

public class RefactoredClass
{
    private readonly IRepresentsWhatThePrivateMethodDid _dependency;

    public RefactoredClass(IRepresentsWhatThePrivateMethodDid dependency)
    {
        _dependency = dependency;
    }

    public string DoSomething(int value)
    {
        return _dependency.MethodThatNeedsItsOwnTests(value);
    }
}

А теперь новый класс реализует IRepresentsWhatThePrivateMethodDid.

Теперь, когда мы тестируем реорганизованный класс, мы имитируем IRepresentsWhatThePrivateMethodDid и пишем отдельные модульные тесты для любых классов, которые реализуют IRepresentsWhatThePrivateMethodDid.

Может показаться противоречием утверждение о том, что разоблачение приватного метода как публичного нарушает инкапсуляцию, а разоблачение его как отдельного класса - нет. Есть два отличия:

  • Реорганизованный класс не зависит от нового класса, содержащего то, что раньше было закрытым методом. Это зависит от нового интерфейса.
  • Взаимодействие между рефакторированным классом и интерфейсом все еще скрыто в его методах. Другие классы, которые вызывают его публичные методы, не «знают» о том, как он использует свою зависимость. (На самом деле, другие классы, скорее всего, будут зависеть от абстракций, а не напрямую от переработанного класса.)

Также легко увлечься этим и слишком рано ввести отдельную зависимость, когда мы могли бы протестировать класс - включая его частные методы - с помощью открытых методов. Я делал это много раз, и это может привести к большому количеству ненужных интерфейсов и дополнительных классов. Там нет совершенства, только наши лучшие усилия, чтобы сбалансировать его.


Еще одна мысль: мы склонны использовать интерфейсы для представления зависимостей, но мы не обязаны это делать. Если то, что мы извлекаем, было только одним закрытым методом, то, возможно, мы можем представить его с делегатом или Func вместо этого, например так:

public class RefactoredClass
{
    private readonly Func<int, string> _dependency;

    public RefactoredClass(Func<int, string> dependency)
    {
        _dependency = dependency;
    }

    public string DoSomething(int value)
    {
        return _dependency(value);
    }
}

Или мы можем использовать делегата, который мне нравится больше, чем Func, потому что он указывает, что делает функция.

0 голосов
/ 02 мая 2019

Интерфейс должен представлять только требуемый API, без учета юнит-тестирования и чего-либо еще.


Для тестирования открытых -метод класса вы должны сделать ничего потому что Тест-проект имеет к ним доступ.Вам просто нужно создать экземпляр класса и внедрить все необходимые зависимости, используя Mock.

Пример: тестирование класса без каких-либо зависимостей

Пример: тестирование класса с зависимостью


Для тестирования приватных методов класса необходимо сделать их видимыми для тест-проекта:

  • пометить приватных методов как internal
  • делает внутренний метод видимым для тестового проекта: добавьте к AssemblyInfo.cs директиву [assembly: InternalsVisibleTo ("Project.Tests")] (см. InternalsVisibleToAttribute )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...