Skip to content

Commit

Permalink
challenge: update tdd-kata song list example
Browse files Browse the repository at this point in the history
- simplify namespace used, song-list rather than recent-song-list
- add mkdocs styling
- add banner image
  • Loading branch information
practicalli-johnny committed Jun 4, 2023
1 parent fd49e2d commit ac9bc0d
Showing 1 changed file with 118 additions and 39 deletions.
157 changes: 118 additions & 39 deletions docs/simple-projects/tdd-kata/recent-song-list.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
# TDD Kata Recent Song-list

![Song List banner](https://github.com/practicalli/graphic-design/blob/live/code-challenges/songs-playlist.png?raw=true){loading=lazy}

Create a recent song list to hold a unique set of songs that have been played.

The most recently played song is at the start of the list, the least recently played song is the last in the list.

* A recently-used-list is initially empty.
* Songs in the list are unique, so repeatedly played songs should only appear once in the list
* Songs can be looked up by index, which counts from zero.
* The song list can be transitory (starting from empty each time) or persistent within a REPL session (examples use a transitory approach)

Optional extras:

Expand All @@ -18,56 +21,59 @@ Optional extras:
Create a new project using clj-new

```bash
clojure -T:project/create practicalli/recent-song-list
clojure -T:project/create practicalli/song-list
```

## Run REPL

Start a Clojure REPL via [a Clojure editor](/clojure/clojure-editors/) or via the command line from the root of the project directory

```shell title="Start rich terminal UI Clojure REPL"
clojure -M:repl/rebel
```
!!! EXAMPLE "Start rich terminal UI Clojure REPL"
```shell
clojure -M:repl/rebel
```

## Unit Tests

`clojure.test` library is part of Clojure standard library and is the most common way to write unit tests in Clojure

Open `test/playground/recent_song_list_test.clj` file in your editor and update the namespace definition to include `clojure.test`
Open `test/playground/song_list_test.clj` file in your editor and update the namespace definition to include `clojure.test`

```clojure
(ns practicalli.recent-song-list-test
(:require [clojure.test :refer [deftest is testing]]
[playground.recent-song-list :as song-list]))
```
!!! EXAMPLE "Require clojure.test namespace"
```clojure title="test/playground/song_list_test.clj"
(ns practicalli.song-list-test
(:require [clojure.test :refer [deftest is testing]]
[playground.song-list :as song-list]))
```

## Run Tests

Evaluate the `practicalli.recent-song-list` and `practicalli.recent-song-list-test` namespaces to load their code into the REPL
Evaluate the `practicalli.song-list` and `practicalli.song-list-test` namespaces to load their code into the REPL

Call the `run-tests` function in the REPL to get a report back on all of the tests in our current namespace (`recent-song-list`)
Call the `run-tests` function in the REPL to get a report back on all of the tests in our current namespace (`song-list`)

=== "Kaocha test runner"
[:fontawesome-solid-book-open: Practicall Clojure CLI Config](/clojure/clojure-cli/practicalli-config/) provides the `:test/run` alias to run the Kaocha test runner.

Open a command line in the root directory of the project and run the following command.
```shell
clojure -X:test/run
```

!!! EXAMPLE "Kaocha test runner"
Open a command line in the root directory of the project and run the following command.
```shell
clojure -X:test/run
```
Kaocha runs all the tests, stopping should a test fail.

Use the `:test/watch` alias to automatically run tests when ever a file is saved
```shell
clojure -X:test/run
```
!!! EXAMPLE "Kaocha test runner with file watch"
Use the `:test/watch` alias to automatically run tests when ever a file is saved
```shell
clojure -X:test/run
```

=== "clojure.test runner"
Evaluate the project code and evaluate the `run-tests` function from `clojure.test` from within the REPL
```clojure
(run-tests)

```
!!! EXAMPLE "clojure.test runner"
```clojure
(run-tests)
```

## Test song-list exists

Expand All @@ -79,58 +85,62 @@ This is an opportunity to think about what kind of data structure you want to us
??? EXAMPLE "Test song-list exists"
A simple test that checks for a `recent-songs` list
```clojure
```clojure title="src/playground/song_list.clj"
(deftest song-list-exists-test
(testing "Does a recent song list exist"
(is (vector? song-list/recent-songs))))
```

```
`recent-songs` should be defined in `src/playground/recent-song-list.clj` before running the test, otherwise a compilation error will be returned.


## Define a recent song list

Edit `src/playground/recent-song-list.clj` and define a name for the collection of recent songs
Edit `src/playground/song_list.clj` and define a name for the collection of recent songs

Use an empty collection to start with. Which collection type will you use though (hash-map `{}`, list `()`, set `#{}`, vector `[]`)?

??? EXAMPLE "recent-songs collection"
Define a recent-song name for an empty vector
```clojure
```clojure title="src/playground/song_list.clj"
(def recent-songs [])
```
??? HINT "Test First Approach"
For a strict test first approach, a `recent-songs` name (symbol) would be defined that returns `false` or a falsy value, e.g. `nil`

A name (symbol) must be defined for use in the test so that the Clojure code can compile

```

## Test song-list is empty

The recent song list should be empty to start with.

??? EXAMPLE "Check song list is empty"
A simple test that compares an empty vector with the value of `recent-songs`
```clojure
```clojure title="src/playground/song_list.clj"
(deftest song-list-empty-test
(testing "Is song list empty if we haven't added any songs"
(is
(= [] song-list/recent-songs))))
```
```

Here is the same test using the `empty?` function instead of the `=` function.

```clojure
(deftest song-list-empty-test-2
```clojure title="src/playground/song_list.clj"
(deftest song-list-empty-test
(testing "Is song list empty if we haven't added any songs"
(is
(empty? song-list/recent-songs))))
```

Either of these tests could replace the test that the song list exists, as these tests would fail if the song list did not exist.


## Test adding a song to the list

Add a song to the collection, for example `Tubular Bells - Mike Oldfield`

??? EXAMPLE "Test adding a song to the list"
```clojure
(deftest adding-songs-test
```clojure title="test/playground/song_list_test.clj"
(deftest add-songs-test

(testing "add song returns a song list with entries"
(is
Expand All @@ -148,6 +158,7 @@ Add a song to the collection, for example `Tubular Bells - Mike Oldfield`

Other songs are avialbe and Practicalli makes no recommendation as to what songs should be used or listened too.


## Function to add song

Create a function to add a song to the start of the song list.
Expand All @@ -161,7 +172,74 @@ Create a function to add a song to the start of the song list.

The results of the `remove` expression are then passed to the `cons` expression as its last argument. The `cons` expression simply adds the new song to the start of the list, making it the most recent song.

```clojure
```clojure title="src/playground/song_list.clj"
(def recent-songs [])

(defn add-song [song song-list]
(cons song song-list))
```

`recent-songs` is passed into the `add-song` function as an argument, `song-list` to keep the design of `add-song` function pure (no side-effects). This design also provides greater scope to using the `add-song` function, as any song list can be added to, rather than hard-coding `recent-songs` list.


## Test song added to top of list

As the song list shows recently played songs, new songs added should be at the top of the list.

The list should not contain duplicate entries for a song.


??? EXAMPLE "Test songs added to top of list"
```clojure title="test/playground/song_list_test.clj"
(deftest recently-added-song-first-test

(testing "most recent song should be first in the list when empty list"
(is (=
(first (add-song "Daft Punk - Get Lucky" recent-songs))
"Daft Punk - Get Lucky")))

(testing "most recent song should be first in list when adding multiple songs"
(is (=
(first
(->> recent-songs
(add-song "Daft Punk - Get Lucky")
(add-song "Pharrell Williams - Happy")))
"Pharrell Williams - Happy")))

(testing "most recent song should be first in list when adding a repeated song"
(is (=
(first
(->> recent-songs
(add-song "Pharrell Williams - Happy")
(add-song "Daft Punk - Get Lucky")
(add-song "Pharrell Williams - Happy")))
"Pharrell Williams - Happy")))

(testing "most recent song should be first in list when adding a repeated song"
(is (not=
(last
(->> recent-songs
(add-song "Pharrell Williams - Happy")
(add-song "Daft Punk - Get Lucky")
(add-song "Pharrell Williams - Happy")))
"Pharrell Williams - Happy"))))
```


## Add song to start of list

Create a function to add a song to the start of the song list.

??? EXAMPLE "Function to add song to list"
The `add-song` function takes the name of a song and the song list to which it will be added.

A Thread-last macro `->>` is used to pass the song list over two functions.

The `song-list` is first passed to the `remove` expression as its last argument. This expression will remove any occurrence of the new song we want to add from the `song-list`.

The results of the `remove` expression are then passed to the `cons` expression as its last argument. The `cons` expression simply adds the new song to the start of the list, making it the most recent song.

```clojure title="src/playground/song_list.clj"
(def recent-songs [])

(defn add-song [song song-list]
Expand All @@ -171,3 +249,4 @@ Create a function to add a song to the start of the song list.
```

`recent-songs` is passed into the `add-song` function as an argument, `song-list` to keep the design of `add-song` function pure (no side-effects). This design also provides greater scope to using the `add-song` function, as any song list can be added to, rather than hard-coding `recent-songs` list.

0 comments on commit ac9bc0d

Please sign in to comment.