mirror of
https://github.com/penpot/penpot-docs.git
synced 2024-07-06 05:41:47 +00:00
Merge pull request #229 from penpot/hiru-doc-unit-tests
Document new unit tests
This commit is contained in:
commit
3e9068b272
|
@ -228,3 +228,258 @@ This macro enables you to have assertions on production code, that
|
|||
generate runtime exceptions when failed (make sure you handle them
|
||||
appropriately).
|
||||
|
||||
## Unit tests
|
||||
|
||||
We expect all Penpot code (either in frontend, backend or common subsystems) to
|
||||
have unit tests, i.e. the ones that test a single unit of code, in isolation
|
||||
from other blocks. Currently we are quite far from that objective, but we are
|
||||
working to improve this.
|
||||
|
||||
### Running tests with kaocha
|
||||
|
||||
Unit tests are executed inside the [development environment](/technical-guide/developer/devenv).
|
||||
|
||||
We can use [kaocha test runner](https://cljdoc.org/d/lambdaisland/kaocha/), and
|
||||
we have prepared, for convenience, some aliases in `deps.edn` files. To run
|
||||
them, just go to `backend`, `frontend` or `common` and execute:
|
||||
|
||||
```bash
|
||||
# To run all tests once
|
||||
clojure -M:dev:test
|
||||
|
||||
# To run all tests and keep watching for changes
|
||||
clojure -M:dev:test --watch
|
||||
|
||||
# To run a single tests module
|
||||
clojure -M:dev:test --focus common-tests.logic.comp-sync-test
|
||||
|
||||
# To run a single test
|
||||
clojure -M:dev:test --focus common-tests.logic.comp-sync-test/test-sync-when-changing-attribute
|
||||
```
|
||||
|
||||
Watch mode runs all tests when some file changes, except if some tests failed
|
||||
previously. In this case it only runs the failed tests. When they pass, then
|
||||
runs all of them again.
|
||||
|
||||
You can also mark tests in the code by adding metadata:
|
||||
|
||||
```clojure
|
||||
;; To skip a test, for example when is not working or too slow
|
||||
(deftest ^:kaocha/skip bad-test
|
||||
(is (= 2 1)))
|
||||
|
||||
;; To skip it but warn you during test run, so you don't forget it
|
||||
(deftest ^:kaocha/pending bad-test
|
||||
(is (= 2 1)))
|
||||
```
|
||||
|
||||
Please refer to the [kaocha manual](https://cljdoc.org/d/lambdaisland/kaocha/1.91.1392/doc/6-focusing-and-skipping)
|
||||
for how to define custom metadata and other ways of selecting tests.
|
||||
|
||||
**NOTE**: in `frontend` we still can't use kaocha to run the tests. We are on
|
||||
it, but for now we use shadow-cljs with `package.json` scripts:
|
||||
|
||||
```bash
|
||||
yarn run test
|
||||
yarn run test:watch
|
||||
```
|
||||
|
||||
#### Test output
|
||||
|
||||
The default kaocha reporter outputs a summary for the test run. There is a pair
|
||||
of brackets `[ ]` for each suite, a pair of parentheses `( )` for each test,
|
||||
and a dot `.` for each assertion `t/is` inside tests.
|
||||
|
||||
```bash
|
||||
penpot@c261c95d4623:~/penpot/common$ clojure -M:dev:test
|
||||
[(...)(............................................................
|
||||
.............................)(....................................
|
||||
..)(..........)(.................................)(.)(.............
|
||||
.......................................................)(..........
|
||||
.....)(......)(.)(......)(.........................................
|
||||
..............................................)(............)]
|
||||
190 tests, 3434 assertions, 0 failures.
|
||||
```
|
||||
|
||||
All standard output from the tests is captured and hidden, except if some test
|
||||
fails. In this case, the output for the failing test is shown in a box:
|
||||
|
||||
```bash
|
||||
FAIL in sample-test/stdout-fail-test (sample_test.clj:10)
|
||||
Expected:
|
||||
:same
|
||||
Actual:
|
||||
-:same +:not-same
|
||||
╭───── Test output ───────────────────────────────────────────────────────
|
||||
│ Can you see this?
|
||||
╰─────────────────────────────────────────────────────────────────────────
|
||||
2 tests, 2 assertions, 1 failures.
|
||||
```
|
||||
|
||||
You can bypass the capture with the command line:
|
||||
|
||||
```bash
|
||||
clojure -M:dev:test --no-capture-output
|
||||
```
|
||||
|
||||
Or for some specific output:
|
||||
|
||||
```clojure
|
||||
(ns sample-test
|
||||
(:require [clojure.test :refer :all]
|
||||
[kaocha.plugin.capture-output :as capture]))
|
||||
|
||||
(deftest stdout-pass-test
|
||||
(capture/bypass
|
||||
(println "This message should be displayed"))
|
||||
(is (= :same :same)))
|
||||
```
|
||||
|
||||
### Running tests in the REPL
|
||||
|
||||
An alternative way of running tests is to do it from inside the REPL you can
|
||||
use in the backend and common apps in the [development environment](/technical-guide/developer/devenv).
|
||||
|
||||
We have a helper function `(run-tests)` that refresh the environment (to avoid
|
||||
having [stale tests](https://practical.li/clojure/testing/unit-testing/#command-line-test-runners))
|
||||
and runs all tests or a selection. It is defined in `backend/dev/user.clj` and
|
||||
`common/dev/user.clj`, so it's available in the REPL without importing anything.
|
||||
|
||||
```clojure
|
||||
;; To run all tests
|
||||
(run-tests)
|
||||
|
||||
;; To run all tests in one namespace
|
||||
(run-tests 'some.namespace)
|
||||
|
||||
;; To run a single test
|
||||
(run-tests 'some.namespace/some-test)
|
||||
|
||||
;; To run all tests in one or several namespaces,
|
||||
;; selected by a regular expression
|
||||
(run-tests #"^backend-tests.rpc.*")
|
||||
```
|
||||
|
||||
### Writing unit tests
|
||||
|
||||
We write tests using the standard [Clojure test
|
||||
API](https://clojure.github.io/clojure/clojure.test-api.html). You can find a
|
||||
[guide to writing unit tests](https://practical.li/clojure/testing/unit-testing) at Practicalli
|
||||
Clojure, that we follow as much as possible.
|
||||
|
||||
#### Sample files helpers
|
||||
|
||||
An important issue when writing tests in Penpot is to have files with the
|
||||
specific configurations we need to test. For this, we have defined a namespace
|
||||
of helpers to easily create files and its elements with sample data.
|
||||
|
||||
To make handling of uuids more convenient, those functions have a uuid
|
||||
registry. Whenever you create an object, you may give a `:label`, and the id of
|
||||
the object will be stored in the registry associated with this label, so you
|
||||
can easily recover it later.
|
||||
|
||||
You have functions to create files, pages and shapes, to connect them and
|
||||
specify their attributes, having all of them default values if not set.
|
||||
|
||||
Files also store in metadata the **current page**, so you can control in what
|
||||
page the `add-` and `get-` functions will operate.
|
||||
|
||||
```clojure
|
||||
(ns common-tests.sample-helpers-test
|
||||
(:require
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.ids-map :as thi]
|
||||
[app.common.test-helpers.shapes :as ths]
|
||||
[clojure.test :as t]))
|
||||
|
||||
(t/deftest test-create-file
|
||||
(let [;; Create a file with one page
|
||||
f1 (thf/sample-file :file1)
|
||||
|
||||
;; Same but define the label of the page, to retrieve it later
|
||||
f2 (thf/sample-file :file2 :page-label :page1)
|
||||
|
||||
;; Set the :name attribute of the created file
|
||||
f3 (thf/sample-file :file3 :name "testing file")
|
||||
|
||||
;; Create an isolated page
|
||||
p2 (thf/sample-page :page2 :name "testing page")
|
||||
|
||||
;; Create a second page and add to the file
|
||||
f4 (-> (thf/sample-file :file4 :page-label :page3)
|
||||
(thf/add-sample-page :page4 :name "other testing page"))
|
||||
|
||||
;; Create an isolated shape
|
||||
p2 (thf/sample-shape :shape1 :type :rect :name "testing shape")
|
||||
|
||||
;; Add a couple of shapes to a previous file, in different pages
|
||||
f5 (-> f4
|
||||
(ths/add-sample-shape :shape2)
|
||||
(thf/switch-to-page :page4)
|
||||
(ths/add-sample-shape :shape3 :name "other testing shape"
|
||||
:width 100))
|
||||
|
||||
;; Retrieve created shapes
|
||||
s1 (ths/get-shape f4 :shape1)
|
||||
s2 (ths/get-shape f5 :shape2 :page-label :page3)
|
||||
s3 (ths/get-shape f5 :shape3)]
|
||||
|
||||
;; Check some values
|
||||
(t/is (= (:name f1) "Test file"))
|
||||
(t/is (= (:name f3) "testing file"))
|
||||
(t/is (= (:id f2) (thi/id :file2)))
|
||||
(t/is (= (:id (thf/current-page f2)) (thi/id :page1)))
|
||||
(t/is (= (:id s1) (thi/id :shape1)))
|
||||
(t/is (= (:name s1) "Rectangle"))
|
||||
(t/is (= (:name s3) "testing shape"))
|
||||
(t/is (= (:width s3) 100))
|
||||
(t/is (= (:width (:selrect s3)) 100))))
|
||||
```
|
||||
|
||||
Also there are functions to make some transformations, like creating a
|
||||
component, instantiating it or swapping a copy.
|
||||
|
||||
```clojure
|
||||
(ns app.common-tests.sample-components-test
|
||||
(:require
|
||||
[app.common.test-helpers.components :as thc]
|
||||
[app.common.test-helpers.files :as thf]
|
||||
[app.common.test-helpers.shapes :as ths]))
|
||||
|
||||
(t/deftest test-create-component
|
||||
(let [;; Create a file with one component
|
||||
f1 (-> (thf/sample-file :file1)
|
||||
(ths/add-sample-shape :frame1 :type :frame)
|
||||
(ths/add-sample-shape :rect1 :type :rect
|
||||
:parent-label :frame1)
|
||||
(thc/make-component :component1 :frame1))]))
|
||||
```
|
||||
|
||||
Finally, there are composition helpers, to build typical structures with a
|
||||
single line of code. And the files module has some functions to display the
|
||||
contents of a file, in a way similar to `debug/dump-tree` but showing labels
|
||||
instead of ids:
|
||||
|
||||
```clojure
|
||||
(ns app.common-tests.sample-compositions-test
|
||||
(:require
|
||||
[app.common.test-helpers.compositions :as tho]
|
||||
[app.common.test-helpers.files :as thf]))
|
||||
|
||||
(t/deftest test-create-composition
|
||||
(let [f1 (-> (thf/sample-file :file1)
|
||||
(tho/add-simple-component-with-copy :component1
|
||||
:main-root
|
||||
:main-child
|
||||
:copy-root))]
|
||||
(ctf/dump-file f1 :show-refs? true)))
|
||||
|
||||
;; {:main-root} [:name Frame1] # [Component :component1]
|
||||
;; :main-child [:name Rect1]
|
||||
;;
|
||||
;; :copy-root [:name Frame1] #--> [Component :component1] :main-root
|
||||
;; <no-label> [:name Rect1] ---> :main-child
|
||||
```
|
||||
|
||||
You can see more examples of usage by looking at the existing unit tests.
|
||||
|
||||
|
|
|
@ -296,20 +296,6 @@ msgstr[1] "%s projects"
|
|||
;; => "1 project"
|
||||
```
|
||||
|
||||
## Unit Tests
|
||||
|
||||
Unit tests have to be compiled first, and then run with node.
|
||||
|
||||
```bash
|
||||
npx shadow-cljs compile tests && node target/tests.js
|
||||
```
|
||||
|
||||
Or run the watch (that automatically runs the test):
|
||||
|
||||
```bash
|
||||
npx shadow-cljs watch tests
|
||||
```
|
||||
|
||||
## Integration tests
|
||||
|
||||
### Setup
|
||||
|
@ -330,7 +316,7 @@ Ensure your development environment docker image is up to date.
|
|||
./manage.sh start-devenv
|
||||
```
|
||||
|
||||
**NOTE** You can learn more about how to set up, start and stop our development environment [here](http://localhost:8080/technical-guide/developer/devenv/#getting-started)
|
||||
**NOTE** You can learn more about how to set up, start and stop our development environment [here](/technical-guide/developer/devenv)
|
||||
|
||||
### Running the integration tests
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user