Подтвердить что ты не робот

Как протестировать или исключить частный недостижимый код из покрытия кода

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

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

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

Я использую DotCover для расчета покрытия в настоящий момент.

Примечание: Это всего лишь пример кода, но я думаю, что он иллюстрирует довольно распространенный шаблон.

public class Tester
{
    private enum StuffToDo
    {
        Swim = 0, 
        Bike,
        Run
    }

    public void DoSomeRandomStuff()
    {
        var random = new Random();
        DoStuff((StuffToDo)random.Next(3));
    }

    private void DoStuff(StuffToDo stuff)
    {
        switch (stuff)
        {
            case StuffToDo.Swim:
                break;
            case StuffToDo.Bike:
                break;
            case StuffToDo.Run:
                break;
            default:
                // How do I test or exclude this line from coverage?
                throw new ArgumentOutOfRangeException("stuff");
        }
    }
}
4b9b3361

Ответ 1

Частные методы всегда являются кандидатами, которые необходимо извлечь в свои классы. Особенно, если они содержат сложную логику, как и ваша. Я предлагаю вам создать класс StuffDoer с DoStuff() в качестве общедоступного метода и ввести его в класс Tester. Тогда у вас есть:

  • A DoStuff(), доступный по тестам
  • A DoSomeRandomStuff(), который может быть проверен с помощью макета вместо того, чтобы полагаться на результат DoStuff(), тем самым делая его истинным unit test.
  • НЕТ нарушения SRP.
  • В целом, хороший, четкий и SOLID-код.

Ответ 2

Вы можете использовать директиву перед компилятором, используя DEBUG, как это (или создать свой собственный):

#if DEBUG
    public void DoStuff(StuffToDo stuff)
#else
    private void DoStuff(StuffToDo stuff)
#endif
    {
        switch (stuff)
        {
            case StuffToDo.Swim:
                break;
            case StuffToDo.Bike:
                break;
            case StuffToDo.Run:
                break;
            default:
                // How do I test or exclude this line from coverage?
                throw new ArgumentOutOfRangeException("stuff");
        }
    }

Ответ 3

Просто выполните следующее:

private static int GetRandomStuffInt()
{
    var random = new Random();
    DoStuff((StuffToDo)random.Next(Enum.GetNames(typeof(StuffToDo)).Length));
}

Это гарантирует, что возвращаемое число является частью перечисления и поэтому никогда не достигнет переключателя default:. (Ссылка)

Ответ 4

Обновление:, учитывая ответы здесь, кажется, необходимы некоторые важные разъяснения.

  • перечисление не помешает вам получить предложение по умолчанию, это действительный способ его достижения: DoStuff((StuffToDo)9999)
  • предложение switch не требуется в инструкции switch. В этом случае код просто будет продолжать работу после переключения
  • Если метод был внутренним, вы можете использовать InternalsToVisible для unit test непосредственно
  • Вы можете реорганизовать код таким образом, чтобы стратегия, выполняемая в коммутаторе, могла быть достигнута модульными тестами, например, @Morten упоминает о своем ответе
  • Случайные даты и аналогичные данные являются типичной проблемой для модульных тестов, и вы должны изменить код таким образом, чтобы их можно было заменить во время модульных тестов. Мой ответ - всего лишь один из способов сделать это. Я показываю, как вы можете вводить его через член поля, но это может быть через конструктор, и при необходимости он может быть интерфейсом.
  • Если перечисление не было закрытым, мой пример кода ниже мог бы выставить Func<StuffToDo>, что сделало бы тесты более читабельными.

Вы все равно можете сделать это и получить 100%:

public class Tester
{
    private enum StuffToDo
    {
        Swim = 0, 
        Bike,
        Run
    }
    public Func<int> randomStuffProvider = GetRandomStuffInt;

    public void DoSomeRandomStuff()
    {
        DoStuff((StuffToDo)randomStuffProvider());
    }
    private static int GetRandomStuffInt()
    {
        //note: don't do this, you're supposed to reuse the random instance
        var random = new Random();
        return random.Next(3);
    }

    private void DoStuff(StuffToDo stuff)
    {
        switch (stuff)
        {
            case StuffToDo.Swim:
                break;
            case StuffToDo.Bike:
                break;
            case StuffToDo.Run:
                break;
            default:
                // How do I test or exclude this line from coverage?
                throw new ArgumentOutOfRangeException("stuff");
        }
    }
}

пс. да, он предоставляет RandomStuffProvider, чтобы вы могли использовать его в unit test. Я не сделал это Func<StuffToDo>, потому что это частное.

Ответ 5

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

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

private void DoStuffMyWay(StuffToDo stuff)
{
    StuffToDo[] accountedStuff = new StuffToDo[] { StuffToDo.Bike, StuffToDo.Run, StuffToDo.Swim };

    if (accountedStuff.Contains(stuff))
    {
        if (stuff == accountedStuff[0])
        {
            //do bike stuff
        }
        else if (stuff == accountedStuff[1])
        {
            //do run stuff
        }
        else if (stuff == accountedStuff[2])
        {
            //do swim stuff
        }
    }
}

Это, конечно, не слишком элегантно, но я думаю, что я не элегантный программист.

Насколько возможно вы предпочитаете другой тип подхода к проблеме, здесь у вас есть другая альтернатива; он менее эффективен, но выглядит лучше:

private void DoStuffWithStyle(StuffToDo stuff)
{
    Dictionary<StuffToDo, Action> accountedStuff = new Dictionary<StuffToDo, Action>();
    accountedStuff.Add(StuffToDo.Bike, actionBike);
    accountedStuff.Add(StuffToDo.Run, actionRun);
    accountedStuff.Add(StuffToDo.Swim, actionSwim);

    if (accountedStuff.ContainsKey(stuff))
    {
        accountedStuff[stuff].Invoke();
    }
}

private void actionBike()
{
    //do bike stuff
}

private void actionRun()
{
    //do run stuff
}

private void actionSwim()
{
   //do swim stuff
}

С помощью этого второго варианта вы даже можете создать словарь как глобальную переменную и заполнить его вместе с перечислением.

Ответ 6

Я согласен с Nate здесь выше. Это, наверное, самый простой (StuffToDo.None). В качестве альтернативы вы также можете перегрузить метод следующим образом:

    private void DoStuff(StuffToDo stuff)
    {
        DoStuff(stuff.ToString());
    }

    private void DoStuff(string stuff)
        switch (stuff)
        {
            case "Swim":
                break;
            case "Bike":
                break;
            case "Run":
                break;
            default:
                throw new ArgumentOutOfRangeException("stuff");
        }
    }

Затем вы можете протестировать ситуацию, которую вы используете в своей заявке по умолчанию.

Ответ 7

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

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

Попробуйте использовать более конкретный метод, в котором четко указано, что он принимает в качестве входных данных и как его модифицировать.

Ответ 8

Напишите тесты, проверяющие значения вне диапазона и ожидающие исключения.

Ответ 9

Это недостижимый код, потому что тип корпуса коммутатора - Enum, и вы закодировали все возможные случаи. Никогда не будет "Default".

Вы можете добавить 'Unknown' или некоторое дополнительное значение enum, которое не должно иметь соответствующий регистр case, тогда это предупреждение исчезнет.

private enum StuffToDo
{
    Swim = 0, 
    Bike,
    Run,
    // just for code coverage
    // will never reach here
    Unknown
}

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

Code Coverage думает так, вы добавили все возможности существующего перечисления, нет способа вызвать по умолчанию. Вот как их правило настроено, вы не можете изменить свое правило.

Ответ 10

My Resharper также "жалуется" на недостижимый код, который мне не нравится во время программирования. Назовите меня анальным, но мне нравится избавляться от мертвого кода с того момента, как я его вижу.

Для вашего случая, я выбрал бы один из этих случаев как случай по умолчанию и поместил бы его примерно так:

    private void DoStuff(StuffToDo stuff)
    {
        switch (stuff)
        {
            case StuffToDo.Swim:
                break;
            case StuffToDo.Bike:
                break;
            case StuffToDo.Run:
            default:
                break;
        }
    }

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

И если вы действительно хотите быть уверенным, вы все равно можете его угадать:

    private void DoStuff(StuffToDo stuff)
    {
        switch (stuff)
        {
            case StuffToDo.Swim:
                break;
            case StuffToDo.Bike:
                break;
            case StuffToDo.Run:
            default:
                if (stuff != StuffToDo.Run) throw new ArgumentOutOfRangeException("stuff");
                break;
        }
    }