首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >介绍Elm使用单元测试的基本知识

介绍Elm使用单元测试的基本知识
EN

Code Review用户
提问于 2017-08-30 11:34:30
回答 1查看 215关注 0票数 3

我是一个榆树初学者,我想为我的同事提供一个榆树实用课程。

我将做一个简短的介绍榆树,然后我们将转移到实际经验教训。

第一课是一起阅读以下文件,以发现elm的基础和语法:

Basis.elm

代码语言:javascript
复制
{-
Here we will discover some elm basis and syntax.

You can hack this file and run the tests by running in your terminal:
elm reactor

And go to http://localhost:8000/
Then select Basis.elm file
______________________________________________________________________

First of all, we are going to import some testing packages.

We import the Expect package so we can use Expect package functions.
For example we will be able to call Expect.equal to check equality.
-}

import Expect

{-
Then we import Test.Runner.Html and expose TestProgram to our program.
Since we exposed TestProgram we can call this function without prefixing it with the package name.
-}

import Test.Runner.Html exposing (TestProgram)

-- Finally we import Test and expose every functions of the package
import Test exposing (..)


{-
Let's talk about functions as it's a key component of elm.

This is a function:
-}

add x y = x + y
{-  ^ ^   ^^^^^
    | |     |
    | |     |--- there is no return keyword, everything is a returning expression
    | |
    |-|--------- first and second arguments

To call a function, you write:
myResult = myFunction myFirstArgument mySecondArgument
-}

result = add 4 5

{-
To write an anonymous function we use the following syntax
(\myFirstArgument mySecondArgument -> myFirstArgument * mySecondArgument)

so we can probably write something like that:
-}

resultComputedByAnonymousFunction = (\a b -> a + b) 4 5

{-
The signature of our add function is:
number -> number -> number

If translated in Javascript, our function looks like:
function add (x) {
    return function(y) {
        return x + y;
    }
}

It's a function taking one number (x) returning
a function expecting another number (y) returning
a number which is the result of the operation x + y.

Functions are curried,
so we can create a new function from our add function:
-}

add3 = add 3

seven = add3 4

{-
We are going to unit test our previously written adding function.

The signature of the test function is:
String -> (() -> Expectation) -> Test

It takes a String and a function returning an Expectation and finally returns a Test.

Let's test the add function.
-}

testAddFunction = test "add function should addition the arguments" (\_ -> Expect.equal 4.12 (add 2 2.12))


-- Now let's write a dividing function
divide x y =
    x / y

{-
We want a function that divide by 2, so we need to flip the arguments of the function.
So we can use a core function for that purpose:
    http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Basics#flip
-}

divideBy2 = flip divide 2

-- let's compute ( 4 + 6 ) / 2
mean = divideBy2 (add 4 6)


testFunctionComposition = test "composition of divideBy2 and add functions should return the mean value" (\_ -> Expect.equal 5 mean)

{-
Finally let's find a more natural way to express what we are doing here.

We can use backward/forward function application:
http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Basics#|>
-}

meanByPipe = add 4 6 |> divideBy2

testPipe = test "composition and pipe should return the same value" <|
    \_ -> Expect.equal mean meanByPipe


{-
Strings are always double-quoted.
Single quote is used for Char.

The operator to append a String is ++.
-}

-- So this is a Char
myChar = 'l'

-- and those are Strings
myString = "abc"
mySecondString = "def"

testAppendOperator = test "append operator should append strings"  <|
    \_ -> Expect.equal "abcdef" (myString ++ mySecondString)

{-
A record is a kind of logic-less object.

This is a record:
-}

johnDoe =
    { firstName = "John"
    , lastName = "Doe"
    }

-- We can access a record field with the dot notation
testRecordAccessor = test "accessor .firstName should return the firstName of a record" <|
    \_ -> Expect.equal "John" johnDoe.firstName

-- To get an updated copy of a record we use the pipe operator
lioDoe = { johnDoe | firstName = "Lio" }

testUpdateRecord = test "Pipe operator should 'update' the firstName of the record" <|
    \_ -> Expect.equal "Lio" lioDoe.firstName

-- With the pipe operator we can change multiple fields at once
lioDa =
    { johnDoe |
        firstName = "Lio",
        lastName = "Da"
    }

testUpdateMultipleFieldsRecord =
    test "Pipe operator should 'update' multi fields" <|
        \_ -> Expect.equal {firstName = "Lio", lastName = "Da"} lioDa

{-
A record is a type, so johnDoe, lioDoe and lioDa are typed { firstName: String, lastName: String }.
We can clarify typing with type annotation.
-}

janeDoe : { firstName: String, lastName: String }
janeDoe = { firstName = "Jane", lastName = "Doe" }


{-
And to simplify type usage we can alias a type.
Let's define an alias to our type { firstName: String, lastName: String }.
-}

type alias Identity =
    { firstName : String
    , lastName : String
    }

saraDoe : Identity
saraDoe = { firstName = "Sara", lastName = "Doe" }

-- Identity is also a record constructor
claireDoe : Identity
claireDoe = Identity "Claire" "Doe"

testRecordConstructor = test "Record constructor should return an record" <|
    \_ -> Expect.equal "Claire" claireDoe.firstName

{-
Since we discovered some basic types, let's move to union type.

Union type is a flexible feature used for many things in elm.

For example, Bool is the union type of True and False.

Bool = True | False
 ^      ^       ^
 |      |       |
 |      |-------|--- constructors
 |
 |------------------ the union type


With union types we can naturally express things such as an answer to a survey:
-}

type Answer = Yes | No | Other String

yes : Answer
yes = Yes

no : Answer
no = No

iDoNotUnderstand : Answer
iDoNotUnderstand = Other "i don't understand the question"

{-
We can do pattern matching with the case of instruction.
-}

testPatternMatching =
    test "yes should be Yes" (\_ ->
        case yes of
            Yes -> Expect.pass
            No -> Expect.fail "yes is not a No"
            Other response -> Expect.fail "yes is not an Other"
        )

main : Test.Runner.Html.TestProgram
main =
    [ testAddFunction
    , testAppendOperator
    , testPipe
    , testRecordAccessor
    , testUpdateRecord
    , testUpdateMultipleFieldsRecord
    , testRecordConstructor
    , testPatternMatching
    ]
        |> concat
        |> Test.Runner.Html.run

elm-package.json

代码语言:javascript
复制
{
    "version": "1.0.0",
    "summary": "helpful summary of your project, less than 80 characters",
    "repository": "https://github.com/user/project.git",
    "license": "BSD3",
    "source-directories": [
        "."
    ],
    "exposed-modules": [],
    "dependencies": {
        "elm-community/elm-test": "4.1.1 <= v < 5.0.0",
        "elm-community/html-test-runner": "1.0.3 <= v < 2.0.0",
        "elm-lang/core": "5.1.1 <= v < 6.0.0",
        "elm-lang/html": "2.0.0 <= v < 3.0.0"
    },
    "elm-version": "0.18.0 <= v < 0.19.0"
}

然后我们将编写一些单元测试来发现列表和元组,编写一个简单的应用程序来发现Elm体系结构,最后用命令构建一个更复杂的应用程序。

您能回顾一下Basis.elm文件来改进这一课吗?

EN

回答 1

Code Review用户

回答已采纳

发布于 2017-09-08 21:35:44

我不知道有一种榆树语--你的帖子把我介绍给了它。

代码语言:javascript
复制
resultComputedByAnonymousFunction = (\a b -> a + b) 4 5

在白板上阅读时,人们必须能够发音。显然,这里正确的发音会涉及到"lambda a b“。我觉得这一解释有点惊讶。我认为( \ a b ...也会同样工作;这与为换行符编写\n完全不同。我不知道您的听众是否对希腊字母过敏,但是您可能需要稍微扩展匿名函数部分。

代码语言:javascript
复制
meanByPipe = add 4 6 |> divideBy2

这很好,你是一次只介绍一件事,在这种情况下,一个新的操作符。但在下一行中,您将<|弹到我们身上。您可能需要定义两次testPipe,显示带有和不带<|的等效函数。

我建议用这样的措辞:单引号用于单个字符。

对于johnDoe /别名/ main,我认为您认为在行开始时使用逗号可以提高可读性或最小化差异。我不同意你的观点(比如lioDa),但那没什么。Python让我很不爽,因为它允许用逗号尾随,所以所有的行都是相同格式的。

yes = Yes之前,您可以明确指出它们是两个不同的标识符。

main给我的印象是有点脆弱,因此很容易添加testFoo,而忽略了将其包括在列表中。如果有一些内省方法来获取标识符列表,我鼓励您在这里使用它。

总的来说,你的语言和说明文都很清楚,适合刚开始的听众。

票数 2
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/174377

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档