【问题标题】:good software design patterns, constructor overloading for unit testing良好的软件设计模式,单元测试的构造函数重载
【发布时间】:2019-07-15 21:39:23
【问题描述】:

我正在用 C# 开发一个 Windows 窗体应用程序。实例化表单时,我在构造函数中初始化了一些硬件类。 hardware1, Hardware2, Hardware3 代表我在软件中实例化的硬件类。

public partial class Form1 {
    Hardware1 a;
    Hardware2 b;
    Hardware3 c;

    public Form1() {
        // initialize hardware1,2 and 3 using native dll calls of a,b and c
    }

我想在我的主窗体中对此方法进行单元测试。

public int MyMethod(int percent, ref double power) {
    for(int i =0; i<3; i++)
        {
            if(a.isValid)
            {
                b.DoThing1(percent);
                b.DoThing2(true);
                c.DoSomething1();
                double val = C.DoSomething2();
                if (val != -500)
                {
                    power = val;
                    return 0;
                }
            }
            else
                return 1;

        }
        return 2; 
    }

困难在于 a.IsValid 是通过一些我必须模拟的 dll 调用设置的。 b.DoThing1 和 C.DoThing2 还调用了一些我也必须模拟的 dll 函数。一种可能性是重载表单方法构造函数。然后我可以模拟每个类 dll 的

Form1 (Hardware1 a, Hardware2 b, Hardware3 c) {
    this.a = a;
    this.b = b;
    this.c = c;
} 

或者我应该有 Hardware1 a、Hardware2 b 和 Hardware3 c 的接口吗?我读到通常最好有接口来代替具体的类。

对 Form1 myMethod 进行单元测试的良好软件实践是什么?

【问题讨论】:

标签: c# visual-studio unit-testing


【解决方案1】:

使用接口的最大优点是您的单元测试可以定义自己的接口实现,这允许您的单元测试在没有外部依赖的情况下执行。

我建议您做的是从您的表单中删除您的 MyMethod 并将其放入它自己的类中。一般来说,将业务与演示分开总是一个好主意。

这是我的建议:

public interface IHardware1 { }
public interface IHardware2 { }
public interface IHardware3 { }

public class Hardware1 : IHardware1 { }
public class Hardware2 : IHardware2 { }
public class Hardware3 : IHardware3 { }


public class MyClass
{
    private IHardware1 _a;
    private IHardware2 _b;
    private IHardware3 _c;

    public MyClass( IHardware1 hardware1, IHardware2 hardware2, IHardware3 hardware3 )
    {
        _a = hardware1;
        _b = hardware2;
        _c = hardware3;
    }

    public int MyMethod( int percent, ref double power )
    {
        ...
            _b.DoThing1( percent );
            _b.DoThing2( true );
            _c.DoSomething1();
        ...
    }
}

然后在您的表单中(以及在您的单元测试项目中)您只需使用:

MyClass _myClass = new MyClass( a, b, c);
_myClass.MyMethod( x, y );

【讨论】:

  • 之所以在主窗体中是因为hardware1 2和3类必须在MyMethod函数中访问,该函数只能在主窗体中使用
【解决方案2】:

我建议在 C# 中使用 NUnit 和 NSubstitute 进行单元测试。

当您说针对接口而不是实际实现进行测试要好得多时,您是对的。 使用 NSubstitute,您可以模拟接口并设置该接口中声明的所有方法的返回值:

//Test
var someType = Substitute.For<ISomeType>();
var someParameter = 42;
var someReturnValue = 1;
someType.SomeMethod(someParameter).Returns(someReturnValue);
var sut = new SomeClass(someType); //sut means system under test
var result = sut.AMethod();
//This would test positive as you set the return value of SomeMethod to 1 
//in case it gets called with a value of 42
Assert.That(result, Is.EqualTo(someReturnValue));
//Test end

//This would be the class you want to unit test
public class SomeClass
{
    private ISomeType someType;

    public SomeClass(ISomeType someType)
    {
        this.someType = someType;
    }

    public int AMethod()
    {
        return someType.SomeMethod(42);
    }
}

这里发生的情况是,如果使用 42 的参数调用它,您的测试会模拟 SomeType 并将其方法 SomeMethod 的返回值设置为 1

通过这种方式,您可以轻松控制在调用某个方法时从模拟服务中获取哪些值。

【讨论】:

  • 嘿,谢谢你的回答!我目前在使用 MSTest 时使用 Moq 进行单元测试 c#
猜你喜欢
  • 2015-02-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-09-15
  • 2010-11-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多