Разработка конструкторов для тестируемости

Это очень нечетно! Существует сообщение в блоге здесь , который описывает, как поведение Cast() было изменено между.NET 3.5 и.NET 3,5 SP1, но это все еще не объясняет InvalidCastException, который Вы даже получаете при перезаписи кода таким образом:

var list = new[] { 1 };
var castedList = from long l in list select l;
Console.WriteLine(castedList.First());

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

var castedList = list.Select(i => (long)i);

, Это работает, но это не объясняет ошибку во-первых. Я пытался бросить список к короткому и плаванию, и те выдали то же исключение.

Редактирование

, Что сообщение в блоге объясняет, почему оно не работает!

Cast() дополнительный метод на IEnumerable, а не IEnumerable. Это означает, что к тому времени, когда каждое значение переходит к сути дела, куда оно бросается, оно было уже упаковано назад в Систему. Объект. В сущности это пытается сделать это:

int i = 1;
object o = i;
long l = (long)o;

Этот код бросает InvalidCastException, который Вы получаете. При попытке бросить интервал непосредственно к длинному, Вы в порядке, но кастинг помещенного в коробку интервала назад к длинному не работает.

, Конечно, причуда!

6
задан Christian 24 September 2009 в 10:01
поделиться

6 ответов

Посмотрите на использование инфраструктуры внедрения зависимостей, такой как Spring . Это применение инверсии управления означает, что каждый из ваших типов может избежать ловушки, которую вы видели, оставив конфигурацию для «связывания» компонентов вместе.

Это введение в Spring (pdf) дает исчерпывающий Обзор Spring. Первой или двух глав должно быть достаточно для объяснения концепций.

Также см. Инверсия управляющих контейнеров и шаблон внедрения зависимостей Мартина Фаулера

7
ответ дан 9 December 2019 в 22:37
поделиться

Ваши конструкторы не являются неправильными, и проблема не в том, когда / где выполняется код, а в том, что все упоминали: внедрение зависимостей. Вам необходимо создать фиктивные объекты SshProperties для создания экземпляра вашего объекта. Самый простой способ (при условии, что класс не помечен как окончательный) - расширить класс:

public class MockSshProperties extends SshProperties {
    // implemented methods
}

Вы можете использовать фиктивные фреймворки, такие как Mockito :

public class Info {

    private final sshProps;
    private final serviceProps;

    public Info() {
        this(new SshProperties(), new ServiceProperties());
    }

    public Info(SshProperties arg1, ServiceProperties arg2) {
        this.sshProps = arg1;
        this.serviceProps = arg2
    }
}

public class InfoTester
{
    private final static SshProperties sshProps = mock(SshProperties.class);
    private final static ServiceProperties serviceProps = mock(ServiceProperties.class);
    static {
        when(sshProps.someGetMethod("something")).thenReturn("value");
    }

    public static void main(String[] args) {
        Info info = new Info(sshProps, serviceProps);
        //do stuff
    }
}
1
ответ дан 9 December 2019 в 22:37
поделиться

Даже фреймворки внедрения зависимостей, такие как Spring, Guice, PicoContainer и т. Д., Нуждаются в каком-то ускорителе, поэтому вам всегда нужно что-то создавать.

Я бы посоветовал вам использовать поставщика / фабрику, которая возвращает настроенный экземпляр вашего класса. Это позволит вам выйти из иерархии «творения».

1
ответ дан 9 December 2019 в 22:37
поделиться

You let the some-other-class have too much knowledge about the Info class and its dependencies. A dependency injection framework would use a provider class. Using generic types one can make a Provider of Info objects:

interface Provider<T> {
  T get();
}

If your some-other-class take a Provider in its constructor your method would look like:

public void useInfo() throws Exception
{
  Info info = infoProvider.get();
  doStuffWithInfo(info);
}

This has removed construction of concrete classes from your code. Next step is to make Info into an interface to make it easier to create a mock for the specific unit-test case.

And yes, this will push and push all the object construction code further and further up. It will lead to some module that only describes how to wire things together. The "rules" for such code is that it should be free of conditional statements and loops.

I recommend reading Misko Heverys blog. All the presentations are useful and printing out the guide to writing testable code as little rulebook once you understand the rules is a good thing.

0
ответ дан 9 December 2019 в 22:37
поделиться

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

0
ответ дан 9 December 2019 в 22:37
поделиться

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

  1. никогда не создавайте и не приобретайте зависимость; всегда программируйте для интерфейсов.

Вы начинаете с правила №1. Вы изменили свой класс Info , чтобы больше не создавались его зависимости. Под «зависимостью» я имею в виду другие классы, данные конфигурации, загруженные из файлов свойств и т. Д. Когда вы зависите от того, как что-то создается, вы привязываете к нему свой класс и затрудняете тестирование, повторное использование и поддержку. Итак, даже если зависимость создается через фабрику или синглтон, не позволяйте вашему классу создавать ее. Попросите что-нибудь еще вызвать create () или getInstance () или что-то еще и передать его.

Итак, вы выбрали «что-то еще» в качестве класса, который использует ваш класс, и понял, что от этого дурной запах. Решение состоит в том, чтобы вместо этого точка входа в ваше приложение создавала экземпляры всех зависимостей. В традиционном Java-приложении это ваш метод main () . если задуматься, создание экземпляров классов и их соединение друг с другом или «соединение» их вместе - это особый вид логики: логика «сборки приложения». Что лучше: распространить эту логику по всему коду или собрать ее в одном месте, чтобы ее было легче поддерживать? Ответ заключается в том, что собрать их в одном месте лучше - не только для поддержки, но и в процессе этого превращает все ваши значимые классы в более полезные и гибкие компоненты.

В вашем main () или эквивалент main () , вы должны создать все необходимые вам объекты, передавая их сеттерам и конструкторам друг друга, чтобы «связать» их вместе. Затем ваши модульные тесты будут подключать их по-другому, передавая имитационные объекты или аналогичные вещи. Акт выполнения всего этого называется «внедрение зависимости». После того, как я сделаю то, что я сказал, у вас, вероятно, будет большой уродливый метод main () . Именно здесь вам может помочь инструмент внедрения зависимостей и, по сути, сделать ваш код бесконечно более гибким. Инструмент, который я бы предложил, когда вы дойдете до этого момента, как предлагали другие, - это Spring.

Менее важное правило № 2 - всегда программировать интерфейсы, все еще очень важно, потому что оно устраняет все зависимости от реализации, делая повторное использование намного проще, не говоря уже об использовании других инструментов, таких как фреймворки имитирующих объектов, ORM-фреймворки и т. д., которые также проще.

2
ответ дан 9 December 2019 в 22:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: