Рефакторинг кода, который использует новый оператор, чтобы быть более тестируемым - PullRequest
1 голос
/ 12 декабря 2010

У меня есть следующий код, и я пытаюсь выполнить его модульное тестирование: <br> public override IRenderable GetRenderable()<br> {<br> var val = SomeCalculationUsingClassMemberVariables();<br> return new EquationRenderable(val);<br> }

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

Ответы [ 3 ]

1 голос
/ 13 декабря 2010

Хороший вопрос.

Прежде всего, чувство соблазна использовать AbstractFactories везде пахнет немного, поскольку DI-контейнер не используется «правильным» способом, или дизайн может быть улучшен.

Но иногда я тоже сталкивался с этой проблемой. Я вижу следующее:

  1. используя AbstractFactory / Factory и внедрите его. Для C # вы должны иметь преимущество в том, что вы можете передавать делегатов, выступая в качестве интерфейса для создания экземпляра.
  2. 'new' в порядке, просто проверьте вывод 'new'.
  3. Заглушить вызов 'new' внутри извлеченного метода (хакерский !!)

Инъекция уже упоминалась, поэтому я не буду повторяться. Я больше разбираюсь в Java, поэтому прошу прощения за некоторые синтаксические ошибки.

Проверьте вывод 'new'

Я часто использую это, если «новые» созданные экземпляры являются объектами предметной области, а не службами. Поскольку он возвращается непосредственно в методе, я могу проверить прямой вывод с помощью своего теста.

Prod-Code:
...
public override IRenderable GetRenderable()
{
  var val = SomeCalculationUsingClassMemberVariables();
  return new EquationRenderable(val);
} 

Test Case:
...
[Test]
public void test_new()
{
  SUT sut = ...;
  IRenderable r = sut.GetRenderable();
  assertTrue(r instanceof EquationRenderable);
}

Заглушите зов самого "нового"

Тестирование прямого вывода сверху возможно только в том случае, если вы каким-то образом получаете его в качестве возвращаемого значения. Ситуация усложняется, если «побочным эффектом» вашего кода являются косвенные выходные данные, которые вы не можете непосредственно определить по возвращаемому значению. Если так, то я часто извлекаю метод нового создания, а затем проверяю его. Это отвратительно, и я больше использую его, чтобы безопасно пройти тест и позже начать проводить рефакторинг (DI и фабрики). Иногда я делаю это в унаследованном коде, где сервисы создаются с «новым» напрямую, а рефакторинг для DI слишком рискован без тестов.

Prod-Code:
...
public override IRenderable GetRenderable()
{
  var val = SomeCalculationUsingClassMemberVariables();
  return createEquationRenderable();
} 

public IRenderable createEquationRenderable() 
{
   return new EquationRenderable(val);
}


Test Case:
...

class Stubbed : SUT 
{
   boolean called = false;

   public override EquationRenderable createEquationRenderable() 
   {
      called=true;
      return MyMock();
   }
}

[Test]
public void test_new()
{
  Stubbed sut = new Stubbed();
  sut.GetRenderable();
  assertTrue(sut.called);
  // do further stuff on MyMock
}

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

Может быть, у вас есть более подробный пример?

1 голос
/ 12 декабря 2010

В зависимости от однородности ваших конкретных конструкторов IRdederable вы можете использовать следующий шаблон для создания фабрики

public IRenderable CreateInstance<T>(object calculation) where T : IRenderable 
{
   Activator.CreateInstance<T>(new[] { calculation });
}

или если у вас много разных конструкторов, вы можете использовать ключевое слово params для передачи произвольного количества аргументов

public IRenderable CreateInstance<T>(params object[] args) where T : IRenderable 
{
   Activator.CreateInstance<T>(args);
}

Чтобы иметь возможность выполнить какую-либо проверку во время выполнения аргументов, которые вы используете этот код, перед вызовом Activator.CreateInstance

var types = args.Select(o => o.GetType()).ToArray();

var c = typeof(T).GetConstructor(types);
if (c == null)
{
   throw new InvalidOperationException("No matched constructor")
}
0 голосов
/ 13 декабря 2010

Лучшим способом может быть простое модульное тестирование кода как вместо его рефакторинга.Технически это можно сделать с помощью подходящего инструмента для насмешек, такого как TypeMock Isolator или Microsoft Moles (есть третий, который я сейчас не помню).

...