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

TDD: Любой шаблон для постоянного тестирования?

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

Жизнь крутая.

Ну, это обещание. Реальность иногда другая:

  • Вы изменяете значение константы LogCompleteFileName от L:\LOGS\MyApp.log до \\Traces\App208.txt и получаете два файла: \\Traces\App208.txt для трасс и \\traces\App208.txt.log для журналов...
  • Вы меняете TransactionTimeout с 2 до 4 минут, и вы по-прежнему получаете таймаут через 2 минуты (после того, как вы провели день, вы обнаружите, что вам также необходимо изменить тайм-аут СУБД и тайм-аут менеджера транзакций...).
  • Вы заменяете SleepTimeInMinutes от 1 до 10, и вы не видите никаких изменений (через час или около того вы обнаружите, что имя константы вводит в заблуждение: зернистость - это не минута, а миллисекунда...).
  • Еще более тонкий: вы меняете CompanyName от, скажем Yahoo до Microsoft, но автоматические оповещения по почте все равно отправляются на [email protected]...

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

Ничего меньше.

Конечно, вам нужно проверить, что вы не вводите в заблуждение своих читателей. Вы должны убедиться, что подразумеваемый контракт прав.

Как вы достигаете этого с помощью TDD? Я просто застрял с этим. Единственный способ, которым я могу проверить изменение для константного (!) Значения, - это установить постоянную настройку приложения... Должен ли я заключить, что ключевое слово const следует избегать, когда я думаю, что значение может и изменится

Как вы тестируете свои (так называемые) константы, используя TDD?

Большое спасибо заранее:)

4b9b3361

Ответ 1

Единственный способ, которым я могу проверить изменение для значения константы (!), - это постоянная настройка приложения

Для всех применений, которые вы указали в вопросе, звучат как настройки приложения, а не константы. Константа - это постоянное значение, например:

const decimal LITERS_PER_HOGSHEAD = 238.480942392;

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

public class AppSettings
{
    public const decimal GILLS_PER_HOMER = 1859.771248601;

    public string HelpdeskPhone
    {
        get { // pulled from config and cached at startup }
    }

    public int MaxNumberOfItemsInAComboBox
    {
        get { return 3; }
    }
}

Ответ 2

Существует два вида констант:

1) Константы для удобства/удобочитаемости

При написании кода с использованием TDD каждая строка производственного кода должна существовать, потому что сначала был неудачный тест, требующий, чтобы этот код был написан. И когда вы реорганизуете код, некоторые магические значения будут продвигаться в константы. Некоторые из них также могут быть хорошими в качестве параметров приложения, но для удобства (меньше кода) они были сконфигурированы в коде вместо внешнего файла конфигурации.

В этом случае способ, которым я пишу тесты, как производственный, так и тестовый, будет использовать одни и те же константы. В тестах указывается, что константы используются как ожидалось. Но тесты не будут повторять значение константы, например, в "assert MAX_ITEMS == 4", потому что это будет дублированный код. Вместо этого в тестах будет проверяться, что какое-то поведение корректно использует константы.

Например, здесь является примером приложения (написанным мной), которое напечатает Longcat указанной длины. Как вы можете видеть, Longcat определяется как последовательность констант:

public class Longcat {

    // Source: http://encyclopediadramatica.com/Longcat

    public static final String HEAD_LINES = "" +
            "    /\\___/\\         \n" +
            "   /       \\         \n" +
            "  |  #    # |         \n" +
            "  \\     @   |        \n" +
            "   \\   _|_ /         \n" +
            "   /       \\______   \n" +
            "  / _______ ___   \\  \n" +
            "  |_____   \\   \\__/ \n" +
            "   |    \\__/         \n";
    public static final String BODY_LINE = "" +
            "   |       |          \n";
    public static final String FEET_LINES = "" +
            "   /        \\        \n" +
            "  /   ____   \\       \n" +
            "  |  /    \\  |       \n" +
            "  | |      | |        \n" +
            " /  |      |  \\      \n" +
            " \\__/      \\__/     \n";
...

В тестах проверяется правильность использования констант, но они не дублируют значение константы. Если я изменю значение константы, все тесты автоматически будут использовать новое значение. (И если искусство Longcat ASCII выглядит правильно, его нужно проверить вручную. Хотя вы даже можете автоматизировать это с помощью приемочного теста, что было бы целесообразно для более крупного проекта.)

    public void test__Longcat_with_body_size_2() {
        Longcat longcat = factory.createLongcat(2);
        assertEquals(Longcat.BODY_LINE + Longcat.BODY_LINE, longcat.getBody());
    }

    public void test__Fully_assembled_longcat() {
        Longcat longcat = factory.createLongcat(2);
        assertEquals(Longcat.HEAD_LINES + longcat.getBody() + Longcat.FEET_LINES, longcat.toString());
    }

2) Универсальные константы

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

    public void test__Identity_conversion() {
        int feet1 = 10000;
        int feet2 = FEET.from(feet1, FEET);
        assertEquals(feet1, feet2);
    }

    public void test__Convert_feet_to_meters() {
        int feet = 10000;
        int meters = METERS.from(feet, FEET);
        assertEquals(3048, meters);
    }

    public void test__Convert_meters_to_feet() {
        int meters = 3048;
        int feet = FEET.from(meters, METERS);
        assertEquals(10000, feet);
    }

И производственный код выглядит следующим образом:

public enum LengthUnit {

    METERS("m", 1.0), FEET("ft", 0.3048), PETRONAS("petronas", 451.9), LINES("lines", 0.009);

    private final String name;
    private final double lengthInMeters;
...

    public int from(int length, LengthUnit unit) {
        return (int) (length * unit.lengthInMeters / this.lengthInMeters);
    }
}

Обратите внимание, что я не писал никаких тестов для высоты Petronas Twin Towers, потому что эта информация декларативная (и декларативные данные редко получают и я уже написал тесты для логики преобразования (см. выше). Если добавятся более похожие константы (Eiffel Tower, Empire State Building и т.д.), Они будут автоматически расположены приложением и будут работать как ожидалось.

Ответ 3

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

Ответ 4

Это потому, что все эти вещи не являются константами... Вещи, которые на самом деле:

  • G (6.67300 × 10-11 м3 кг-1 с-2)
  • c (299 792 458 м/с)
  • pi (3.1415926535897931)
  • L (6.0221415 × 1023 моль-1)
  • ...

Если какая-либо из этих вещей когда-либо изменится, не волнуйтесь, ваше аварийное завершение приложения будет последним, что имеет значение xD

Ответ 5

Несколько вещей.

Во-первых, TDD и возникающий дизайн, который продвигает TDD, - это разделение ваших обязанностей, применение DRY и инъекции зависимостей.

В unit test const легко и возможно немного бессмысленно.

Но для проверки другой единицы evalutation этой константы, на мой взгляд, не unit test. Это интеграционный тест. Тестирование значения const в других единицах будет покрываться макетом или заглушкой.

Во-вторых, ваши примеры разнообразны:

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

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

Смена имени компании прекрасна, поскольку она относится к названию компании. Доменное имя должно и должно быть другим const.

Как уже упоминалось, передача класса конфигурации вокруг меня может быть полезной для mock/stub при тестировании других классов.

Ответ 6

Мне кажется, что вы используете константы в основном для иллюстрации настроек конфигурации. Это идеально подходит для ConfigurationManager, но это также сложно использовать для тестирования.

Я бы рекомендовал следующее использование:

const String SomeValue = "TESTCONSTANT";

static class ConfigurationSettings
{
    static String SomeProperty
    {
        get
        {
           var result = SomeValue;
           if (ConfigurationManager.AppSettings["SOMEKEY"] != null)
               result = ConfigurationManager.AppSettings["SOMEKEY"];
           return result;
        }
    }
}

Ответ 7

Если вы создаете Unit Test, я не верю, что что-то не так:

[Microsoft.VisualStudio.TestTools.UnitTesting.TestMethod]
public void DefaultValue_Equals_8()
{
     Assert.AreEqual<int>(8, MyNamespace.MyClass.DefaultValue);
}

который проверяет:

namespace MyNamespace
{
    public class MyClass
    {
        public const int DefaultValue = 8;
    }
}

Это определит, была ли изменена константа.

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