One of the first things I always struggle with when learning new languages is the environment. Here is a simple setup for playing with F# and Linux.
Prerequisite: .NET Core with Linux
I won’t go into setting up .NET Core for linux. This should be straightforward either by following Microsoft instructions or, in my case, the Arch Linux homepage. dotnet --info
should return something similar to:
.NET Command Line Tools (2.1.3)
Product Information:
Version: 2.1.3
Commit SHA-1 hash: a0ca411ca5
Runtime Environment:
OS Name: arch
OS Version:
OS Platform: Linux
RID: linux-x64
Base Path: /opt/dotnet/sdk/2.1.3/
Microsoft .NET Core Shared Framework Host
Version : 2.0.5
Build : 17373eb129b3b05aa18ece963f8795d65ef8ea54
Creating a Kata
Let’s create a project for the FizzBuzz Kata.
mkdir fsharp-kata-fizzbuzz
cd fsharp-kata-fizzbuzz
dotnet new classlib -lang f# -o FizzBuzz
dotnet new xunit -lang f# -o FizzBuzz.Tests
cd FizzBuzz.Tests
dotnet add reference ../FizzBuzz/FizzBuzz.fsproj
cd ..
dotnet new sln
dotnet sln add FizzBuzz/FizzBuzz.fsproj
dotnet sln add FizzBuzz.Tests/FizzBuzz.Tests.fsproj
(I really love this new “CLI first” approach! It makes live so much easier for DevOps)
This is our project structure after templating:
tree . -L 4
.
├── FizzBuzz
│ ├── bin
│ │ └── Debug
│ │ └── netstandard2.0
│ ├── FizzBuzz.fsproj
│ ├── Library.fs
│ └── obj
│ ├── Debug
│ │ └── netstandard2.0
│ ├── FizzBuzz.fsproj.nuget.cache
│ ├── FizzBuzz.fsproj.nuget.g.props
│ ├── FizzBuzz.fsproj.nuget.g.targets
│ └── project.assets.json
├── FizzBuzz.Tests
│ ├── bin
│ │ └── Debug
│ │ └── netcoreapp2.0
│ ├── FizzBuzz.Tests.fsproj
│ ├── obj
│ │ ├── Debug
│ │ │ └── netcoreapp2.0
│ │ ├── FizzBuzz.Tests.fsproj.nuget.cache
│ │ ├── FizzBuzz.Tests.fsproj.nuget.g.props
│ │ ├── FizzBuzz.Tests.fsproj.nuget.g.targets
│ │ └── project.assets.json
│ ├── Program.fs
│ └── Tests.fs
└── fsharp-kata-fizzbuzz.sln
The 3 project files (top - down)…
fsharp-kata-fizzbuzz.sln
(nothing new here)
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26124.0
MinimumVisualStudioVersion = 15.0.26124.0
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FizzBuzz", "FizzBuzz\FizzBuzz.fsproj", "{C64F3370-DE54-4D58-BDD4-33C4B02F7290}"
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FizzBuzz.Tests", "FizzBuzz.Tests\FizzBuzz.Tests.fsproj", "{4AA6DACD-EA0E-4938-BB41-7B055A9A0C8C}"
EndProject
[...]
FizzBuzz/FizzBuzz.fsproj
(not relevant here, but keep in mind that fsharp files have to be in the correct order):
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>
</Project>
FizzBuzz.Tests/FizzBuzz.Tests.fsproj
:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<Compile Include="Tests.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
<PackageReference Include="xunit" Version="2.3.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FizzBuzz\FizzBuzz.fsproj" />
</ItemGroup>
</Project>
Running dotnet test
returns
$ dotnet test
Build started, please wait...
Build started, please wait...
Build completed.
Test run for /home/patrick/projects/fsharp-blog-fizzbuzz/fsharp-kata-fizzbuzz/FizzBuzz/bin/Debug/netstandard2.0/FizzBuzz.dll(.NETStandard,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.5.0
Copyright (c) Microsoft Corporation. All rights reserved.
Starting test execution, please wait...
No test is available in /home/patrick/projects/fsharp-blog-fizzbuzz/fsharp-kata-fizzbuzz/FizzBuzz/bin/Debug/netstandard2.0/FizzBuzz.dll. Make sure test project has a nuget reference of package "Microsoft.NET.Test.Sdk" and framework version settings are appropriate and try again.
Test Run Aborted.
Build completed.
Test run for /home/patrick/projects/fsharp-blog-fizzbuzz/fsharp-kata-fizzbuzz/FizzBuzz.Tests/bin/Debug/netcoreapp2.0/FizzBuzz.Tests.dll(.NETCoreApp,Version=v2.0)
Microsoft (R) Test Execution Command Line Tool Version 15.5.0
Copyright (c) Microsoft Corporation. All rights reserved.
Starting test execution, please wait...
[xUnit.net 00:00:00.9263576] Discovering: FizzBuzz.Tests
[xUnit.net 00:00:01.0646319] Discovered: FizzBuzz.Tests
[xUnit.net 00:00:01.0733357] Starting: FizzBuzz.Tests
[xUnit.net 00:00:01.2961789] Finished: FizzBuzz.Tests
Total tests: 1. Passed: 1. Failed: 0. Skipped: 0.
Test Run Successful.
Test execution time: 2.5956 Seconds
Ok, dotnet test
does not recognize which project actually contains tests. But it runs all tests!
Let’s add a test.
The file FizzBuzz.Tests/Tests.fs
(generated by dotnet new xunit...
) looks like this:
module Tests
open System
open Xunit
[<Fact>]
let ``My test`` () =
Assert.True(true)
TDD approach: We will create a failing test first, then implement something.
Replace the content of FizzBuzz.Tests/Tests.fs
with
module Tests
open System
open Xunit
open FizzBuzz
[<Fact>]
let ``Array with Number 1 returns 'one'`` () =
let result = FizzBuzz.Generate [1]
Assert.Equal(result, "one")
We verify 2 aspects:
- we are invoking another library (
FizzBuzz
) from our test class - we are learning to use the test library
This does not compile. Let’s implement the simplest solution:
Replace FizzBuzz/Library.fs
with
module FizzBuzz
let Generate i = "one"
Running dotnet test
should now confirm 1 passing test.
Have fun with F# on Linux!
Get the source code here