使用 new 运算符重构代码以使其更具可测试性

发布于 2024-10-07 09:47:54 字数 315 浏览 2 评论 0原文

我有以下代码并尝试对其进行单元测试: <代码>
公共重写 IRenderable GetRenderable()
{
var val = SomeCalculationUsingClassMemberVariables();
返回新的 EquationRenderable(val);
} 。

看来我想在这里使用一个工厂,这样我就可以将 IRenderable 的创建与此类分开 问题是我有许多这样的类,它们创建了以不同方式构造的不同 IRenderable,因此我需要为每个类实现一个新的工厂方法。解决这个问题的最佳方法是什么?

I have the following code and am trying to unit test it:

public override IRenderable GetRenderable()
{
var val = SomeCalculationUsingClassMemberVariables();
return new EquationRenderable(val);
}

It seems like I want to use a factory here so I can separate the creation of the IRenderable from this class. The problem is that I have many of these classes that create different IRenderables that are constructed in different ways, so I would need to implement a new factory method for each one. What is the best way to solve this problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

放赐 2024-10-14 09:47:54

好问题。

首先,由于 DI 容器没有以“正确”的方式使用或者设计可以改进,所以在任何地方都想使用 AbstractFactories 的感觉有点难闻。

但有时我也遇到过这个问题。我看到以下内容:

  1. 使用 AbstractFactory/Factory 并注入它。对于 C#,您必须具有可以传递委托的优势,充当创建实例的接口。
  2. 'new' 就可以了,只需测试'new' 的输出即可。
  3. 在提取的方法中存根“新”的调用(hacky!!)

注入已经被提及,所以我不会重复。我更喜欢 Java,所以请原谅一些语法错误。

测试“新”的输出

如果“新”创建的实例是域对象而不是服务,我经常使用它。因为它是直接在方法中返回的,所以我可以用我的测试来测试直接输出。

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);
}

存根“new”本身的调用

只有当您以某种方式将其作为返回值时,才可以测试上面的直接输出。如果代码的“副作用”是间接输出(您无法通过返回值直接感知),事情会变得更加复杂。如果是这样,我经常提取新创造的方法,然后在我的测试中控制它。这很糟糕,我更多地使用它来保证测试的安全,并在以后开始更多的重构(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
}

我知道,这个例子有点矫枉过正,有点毫无意义,它只是为了描述这个想法。我确信上面的内容可以通过 C# 的模拟框架来简化。无论如何,在这里测试返回值直接输出是更简单、更好的方法。

也许你有更详细的例子?

Good question.

First of all, feeling tempted to use AbstractFactories everywhere smells a bit as DI container is not used the "right" way or design could be improved.

But sometimes I've also come across this problem. I see following:

  1. using AbstractFactory/Factory and Inject it. For C# you have to advantage that you can pass delegates, acting as interface for the creation of instance.
  2. 'new' is OK, simply test the output of 'new'.
  3. Stub the call of 'new' inside extracted method (hacky!!)

Injection got mentioned already so I won't repeat. I am more into Java so please excuse some syntax errors.

Test the output of 'new'

I often use this, if the 'new' created instances are domain-objects and not services. Because it is returned directly in method I can test the direct output with my test.

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);
}

Stub the call of 'new' itself

Testing direct output from above is only possible if you somehow get it as return value. Things get more complicated if the "sideeffect" of your code are indirect outputs, which you can't sense directly by the return value. If so I often extract-method of the new-creation and then have it under control in my test. This is yucky and I more use it to go safe with my test and start more refactoring later (DI and factories). I sometimes do this in legacy code where services are created with 'new' directly and refactoring to DI is too risky without tests.

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
}

I know, the example is overkill and a bit senseless, it is just for describing the idea. I am sure above could be shortcutted with mocking-frameworks for C#. Anyway testing the return-value direct output is more trivial and better approach here.

Maybe you have a more detailed example?

岁月苍老的讽刺 2024-10-14 09:47:54

根据具体 IRenderable 构造函数的一致性,您可以使用以下模式进行工厂创建

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")
}

depending on the uniformity of your concrete IRenderable constructors you can use the following pattern for factory creating

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

or if you have many different constructors you can use the params keyword to pass arbitrary amounts of arguments

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

To be able to do some kind of runtime check of the arguments you use this code before calling the 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")
}
撩人痒 2024-10-14 09:47:54

更好的方法可能是简单地按原样对代码进行单元测试,而不是重构它。从技术上讲,这可以通过使用合适的模拟工具来完成,例如 TypeMock Isolator 或 Microsoft Moles(还有第三个,我现在不记得了)。

A better way may be to simply unit test the code as is instead of refactoring it. Technically, this can be done by using a suitable mocking tool such as TypeMock Isolator or Microsoft Moles (there is a third one which I don't remember now).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文