Как писать тесты с макетами, используя f # - PullRequest
4 голосов
/ 29 марта 2010

Я бы хотел написать модульный тест F # с фиктивными объектами. Я использую NUnit. Но, к сожалению, я не смог найти никаких примеров.

Вот пример тестируемого кода:

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

Ответы [ 3 ]

8 голосов
/ 29 марта 2010

В качестве дополнительного примечания вы можете использовать неявный синтаксис конструктора, чтобы сделать объявление класса немного более приятным. Вы также можете упростить свойства только для чтения, потому что можете опустить 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)

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

В целом, хорошая вещь в F # и функциональных языках заключается в том, что вы обычно можете тестировать код легче, не используя при этом какие-либо насмешки. Функциональные программы написаны в другом стиле:

В функциональном программировании функция принимает все свои входные данные в качестве аргументов, и единственное, что она делает, - это то, что она вычисляет и возвращает некоторый результат. Это также верно для методов неизменяемых типов объектов - они не изменяют состояние каких-либо объектов

Моки обычно используются для двух целей:

  • Чтобы убедиться, что тестируемая операция выполнила некоторый вызов метода ссылочного объекта, например, prod.Update(newPrice) чтобы обновить состояние объекта. Однако в функциональном программировании метод должен вместо этого возвращать новое состояние в качестве результата, поэтому вам не нужен фиктивный объект. Просто проверьте, является ли новое возвращенное состояние ожидаемым.

  • Для загрузки создайте поддельный компонент приложения, например, вместо загрузки данных из базы данных. Опять же, чисто функциональная функция должна принимать все свои входные данные в качестве аргументов. Это означает, что вам не нужно создавать фиктивный объект - вы просто вызываете функцию с некоторыми тестовыми данными в качестве аргумента (вместо данных, загруженных из базы данных).

Таким образом, это означает, что в хорошо разработанной функциональной программе вы должны иметь возможность писать все модульные тесты просто как проверки, которые проверяют, что какая-то функция возвращает ожидаемый результат для ожидаемых аргументов. Конечно, это не совсем верно в F #, потому что вам может потребоваться взаимодействие с другими нечистыми компонентами .NET (но на это можно ответить, только если вы приведете более конкретный пример).

5 голосов
/ 31 марта 2010

Вам не нужно создавать класс для создания макетов:

/// 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) }
0 голосов
/ 29 марта 2010
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
...