简体   繁体   English

如何用f#用模拟编写测试

[英]How to write tests with mocks using f#

I'd like to write F# unit test with mock objects. 我想用模拟对象编写F#单元测试。 I'm using NUnit. 我正在使用NUnit。 But unfortunately I couldn't find any examples. 但不幸的是我找不到任何例子。

Here's an example of the code under test: 以下是测试代码的示例:

type ICustomer = interface
    abstract Id: int with get
    abstract Name: string with get
    abstract CalculateBalanceWithDiscount: decimal -> decimal
end

type Customer = class
    val id: int
    val name: string
    val balance: decimal
    new(id, name, balance) = 
        {id = id; name = name; balance = balance}
    interface ICustomer with
        member this.Id 
            with get () = this.id
        member this.Name
            with get () = this.name
        member this.CalculateBalanceWithDiscount discount =
            this.balance - (discount * this.balance)
    end
end

As a side-note, you can use implicit constructor syntax to make your class declaration a bit nicer. 作为旁注,您可以使用隐式构造函数语法使您的类声明更好一些。 You can also simplify readonly properties, because you can omit with get() : 您还可以简化只读属性,因为您可以with get()省略:

// F# infers that the type is an interface
type ICustomer = 
  abstract Id : int 
  abstract Name : string 
  abstract CalculateBalanceWithDiscount : decimal -> decimal

// Parameters of the implicit constructor are autoamtically
// accessible in the body (they are stored as fields)
type Customer(id:int, name:string, balance:decimal) = 
  interface ICustomer with
    member this.Id = id
    member this.Name = name
    member this.CalculateBalanceWithDiscount(discount) =
      balance - (discount * balance)

Regarding testing - do you have any example of what you're trying to achieve? 关于测试 - 您是否有任何关于您要实现的目标的示例? I'm sure we can help for example with translating code from C#. 我相信我们可以通过翻译C#中的代码来提供帮助。 Or what kind of tests would you like to write using mocking? 或者你想用嘲笑写什么样的测试?

In general, a nice thing about F# and functional languages is that you can usually test code more easily without using any mocks. 一般来说,F#和函数式语言的一个好处是你通常可以在不使用任何模拟的情况下更轻松地测试代码。 Functional programs are written in a different style: 功能程序以不同的风格编写:

In functional programming, a function takes all it's inputs as arguments and the only thing that it does is that it calculates and returns some result. 在函数式编程中,函数将所有的输入作为参数,它唯一能做的就是计算并返回一些结果。 This is also true for methods of immutable object types - they do not modify any state of any objects 对于不可变对象类型的方法也是如此 - 它们不会修改任何对象的任何状态

Mocks are typically used for two purposes: 模拟通常用于两个目的:

  • To verify that the tested operation performed some call to a method of a referenced object eg prod.Update(newPrice) to update the state of the object. 验证测试操作是否对引用对象的方法执行了一些调用,例如prod.Update(newPrice)来更新对象的状态。 However, in functional programming the method should instead return the new state as the result - so you don't need mock object. 但是,在函数式编程中,该方法应该返回新状态作为结果 - 因此您不需要模拟对象。 Just check whether the new returned state is what you expected. 只需检查新返回的状态是否符合预期。

  • To load create a fake component of the application, for example instead of loading data from the database. 加载创建应用程序的假组件,例如,而不是从数据库加载数据。 Again, a purely functional function should take all it's inputs as arguments. 同样,纯函数函数应该将其所有输入作为参数。 This means that you don't need to create a mock object - you just call the function with some test data as argument (instead of data loaded from database). 这意味着您不需要创建模拟对象 - 您只需使用一些测试数据作为参数调用该函数(而不是从数据库加载数据)。

In summary, this means that in a well-designed functional program, you should be able to write all unit tests simply as checks that verify that some function returns the expected result for the expected arguments. 总之,这意味着在一个设计良好的功能程序中,您应该能够将所有单元测试编写为检查,以验证某些函数是否返回预期参数的预期结果。 Of course, this isn't strictly true in F#, because you may need to interoperate with other impure .NET components (but that can be answered only if you give a more specific example). 当然,这在F#中并不严格,因为您可能需要与其他不纯的.NET组件进行互操作(但只有在您提供更具体的示例时才能回答)。

You don't need to create a class in order to create mocks: 您不需要创建类来创建模拟:

/// customer : int -> string -> decimal -> ICustomer
let customer id name balance = 
    {new ICustomer with
        member this.Id = id
        member this.Name = name
        member this.CalculateBalanceWithDiscount discount =
            balance - (discount * balance) }
type ICustomer = interface
    abstract Id: int with get
    abstract Name: string with get
    abstract CalculateBalanceWithDiscount: decimal -> decimal
end

type Customer = class
    val id: int
    val name: string
    val balance: decimal
    new(id, name, balance) = 
        {id = id; name = name; balance = balance}
    interface ICustomer with
        member this.Id 
            with get () = this.id
        member this.Name
            with get () = this.name
        member this.CalculateBalanceWithDiscount discount =
            this.balance - (discount * this.balance)
    end
end

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM