Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/275.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 重构服务层类_C#_Unit Testing_Refactoring - Fatal编程技术网

C# 重构服务层类

C# 重构服务层类,c#,unit-testing,refactoring,C#,Unit Testing,Refactoring,我的公司正在进行单元测试,而我在重构服务层代码方面遇到了一些麻烦。下面是我编写的一些代码的示例: public class InvoiceCalculator:IInvoiceCalculator { public CalculateInvoice(Invoice invoice) { foreach (InvoiceLine il in invoice.Lines) { UpdateLine(il); } //do

我的公司正在进行单元测试,而我在重构服务层代码方面遇到了一些麻烦。下面是我编写的一些代码的示例:

public class InvoiceCalculator:IInvoiceCalculator
{
   public CalculateInvoice(Invoice invoice)
   {
      foreach (InvoiceLine il in invoice.Lines)
      {
          UpdateLine(il);
      }
      //do a ton of other stuff here
   }

   private UpdateLine(InvoiceLine line)
   {
      line.Amount = line.Qty * line.Rate;
      //do a bunch of other stuff, including calls to other private methods
   }
}
在这种简化的情况下(它从一个1000行的类减少到只有1个公共方法和~30个私有方法),我的老板说我应该能够分别测试CalculateInvoice和UpdateLine(UpdateLine实际上调用了3个其他私有方法,并执行数据库调用)。但是我该怎么做呢?他建议的重构对我来说似乎有点复杂:

//Tiny part of original code
public class InvoiceCalculator:IInvoiceCalculator
{
   public ILineUpdater _lineUpdater;

   public InvoiceCalculator (ILineUpdater lineUpdater)
   {
      _lineUpdater = lineUpdater;
   }

   public CalculateInvoice(Invoice invoice)
   {
      foreach (InvoiceLine il in invoice.Lines)
      {
          _lineUpdater.UpdateLine(il);
      }
      //do a ton of other stuff here
   }
}

public class LineUpdater:ILineUpdater
{
   public UpdateLine(InvoiceLine line)
   {
      line.Amount = line.Qty * line.Rate;
      //do a bunch of other stuff
   }
}
我可以看到依赖关系现在是如何被打破的,我可以测试这两个部分,但这也会从我原来的类中创建20-30个额外的类。我们只在一个地方计算发票,所以这些零件不会真正重复使用。这是做出改变的正确方式,还是你会建议我做些不同的事情

谢谢大家!


杰斯是的,不错。我不确定InvoiceLine对象是否也包含一些逻辑,否则您可能还需要IInvoiceLine。 我有时也有同样的问题。一方面,您希望做正确的事情并对代码进行单元测试,但当涉及到数据库调用和文件写入时,它会导致大量额外的工作,以使用所有TestObject设置第一次测试,这些TestObject将在文件写入和数据库io即将发生时介入,接口,断言,并且您还希望测试数据层是否不包含任何错误。因此,一个比“单元”更“过程”的测试通常更容易构建。 如果您有一个项目(将来)会发生很多变化,并且该代码有很多依赖项(可能其他程序读取文件或数据库数据),那么最好对代码的所有部分进行可靠的单元测试,而且投资时间是值得的。 但是如果这个项目像我的最新客户所说的那样“让我们把它上线,也许明年我们会做一些调整,明年会有一些新的东西”,那么我就不会对自己太难让所有的单元测试都启动并运行了


Michel

在我看来,这一切都取决于UpdateLine()方法的“重要性”。如果它只是一个实现细节(例如,它可以很容易地内联到CalculateInvoice()方法中,并且它们唯一会影响可读性),那么您可能不需要将它与主类分开进行单元测试

另一方面,如果UpdateLine()方法对业务逻辑有一些价值,如果您可以想象需要独立于类的其余部分更改此方法(并因此单独测试)的情况,那么您应该继续将其重构为单独的LineUpdater类


您可能不会以这种方式得到20-30个类,因为大多数私有方法实际上只是实现细节,不值得单独测试。

好吧,您的老板在单元测试方面做得更正确:
他现在可以在不测试UpdateLine()函数的情况下测试CalculateInvoice()。他可以传递模拟对象而不是真正的LineUpdater对象,并且只测试CalculateInvoice(),而不是一大堆代码。
是这样吗?视情况而定。你的老板想做真正的单元测试。第一个例子中的测试不是单元测试,而是集成测试

集成测试之前的单元测试有哪些优点?
1) 单元测试只允许测试一个方法或属性,而不受其他方法/数据库等的影响。
2) 第二个优点-单元测试执行得更快(例如,您说过UpdateLine使用数据库),因为它们不会测试所有嵌套方法。嵌套方法可以是数据库调用,因此如果您有数千个测试,那么您的测试可能会运行得很慢(几分钟)。
3) 第三个优点:如果您的方法进行数据库调用,那么有时您需要设置数据库(用测试所需的数据填充数据库),这并不容易——也许您需要编写几页代码来为测试准备数据库。通过单元测试,可以将数据库调用与正在测试的方法分离(使用模拟对象)

但是!我不是说单元测试更好。他们只是不同而已。正如我所说,单元测试允许您在隔离和快速的情况下测试单元。集成测试更容易,允许您测试不同方法和层的联合工作的结果。老实说,我更喜欢集成测试:)

另外,我还有一些建议给你:
1) 我不认为有数量字段是个好主意。似乎Amount字段是额外的,因为它的值可以基于其他两个公共字段进行计算。如果您仍要执行此操作,我会将其作为一个只读属性执行,该属性返回Qty*Rate。
2) 通常,拥有一个包含1000行的类可能意味着它的设计很糟糕,应该进行重构


现在,我希望你能更好地了解情况并做出决定。另外,如果你了解情况,你可以和你的老板谈谈,然后一起做出决定。

你老板的例子在我看来是合理的

在为任何场景进行设计时,我尝试记住的一些关键注意事项包括:

  • 单一责任原则

    一个类只应该因为一个原因而改变

  • 每一个新的类都证明了它的存在吗

    类是仅仅为了它而创建的,还是它们封装了逻辑中有意义的部分

  • 您能够单独测试每一段代码吗

  • 在您的场景中,只要看一下这些名称,就会发现您似乎偏离了单一的职责——您有一个IInvoiceCalculator,而这个类也负责更新InvoiceLines。您不仅使测试更新行为变得非常困难,而且现在还需要在计算业务规则更改和更新规则更改时更改InvoiceCalculator类

    还有一个问题是ab
    line.Amount = line.Qty * line.Rate;
    
      var amount = line.CalculateAmount();