We have a few goals of restructuring the codebase that we have agreed on but aren't documented anywhere. This issue serves as a place to document what we plan to do & as a place to document new utils and concepts that are intended to make our lives easier and help us move faster.
Add a new lib folder, to contain all of the shared libraries in Ghost.
Underneath this, a new common folder, which contains the libraries that are used everywhere:
These 4 things need to be exposed to the whole of Ghost including theme helpers, apps & adapters.
They are are all effectively "read only" things - it's not possible to affect behaviour by using these libraries.
Other things that might live under lib, would be an apps folder that contains all the code which _manages_ apps, whereby the top level apps folder contains Ghosts built-in "internal" apps.
There is a second type/set of shared libraries in Ghost, which I would call "services". These cannot be exposed to apps/adapters/themes directly without creating security concerns or permissions problems:
In the near future this might also feature a new url and/or routing service.
Services might end up having some sort of registry in future.
The data folder is a crutch. It's full of all kinds of weird stuff that has always needed a proper home. Not sure how we're going to move forward with the meta folder just yet, but we know this is a nightmare.
Many of the these things will get moved to services over time.
We've bunged an awful lot of stuff in "utils" over the years. Some of it should be there, some of it should probably be in a service (url stuff). Some of it should belong TO a library or service, and some of it can be farmed out to either be a dependency or by using a new dependency.
We want to focus mainly on unit testing and _either_ the route-based functional tests, or the integration tests. There's a whole bunch of tests which can/should be removed or replaced. The aim is to make tests FAST so we can ship FAST.
The utilities & fixtures around tests need to be similarly split up so that different types of test have different types of utilities, and that fixtures for specific bits of data or config are easier to find / write.
Where we have defined data structures, we can move towards using more custom should assertions - E.g. for testing that API responses or resources look how we expect them to look.
There's not going to be any one big push towards doing this, but it is expected that PRs and commits go by that make small movements towards these goals.
(so we know when we can close this)
web folder for all the expressy stuff@kirrg001 loosely related thing that I wanted to have a discussion about, is the structure of tests going forward.
I have a couple of points here which are about doing a little bit of upfront work in order to, long term, significantly reduce time & complexity of testing:
Below is a table I generated from the latest master build.
The numbers in the top are how _many_ of that type of test we have, the numbers in the columns are how long mocha reports the tests took, the total is what Travis says:
| Node | DB | Routes (307) | Module (4) | Unit (1514) | Integration (554) | Total
|---|---|--:|--:|--:|--:|--:|
| v8 | sqlite | 2m | 7s | 15s | 3m | 6:53 |
| v8 | mysql | 3m | 8s | 18s | 3m | 7:19 |
| v6 | sqlite | 3m | 9s | 20s | 3m | 8:52 |
| v6 | mysql | 4m | 12s | 29s | 4m | 8:39 |
| v4 | sqlite | 4m | 11s | 26s | 4m | 11:55 |
| v4 | mysql | 4m | 11s | 21s | 3m | 8:56 |
It is good to see they take less time in newer versions of node, but the fundamental point is this:
We have both concepts of integration AND route tests.
Route tests are slower and more involved than integration tests, but both are ridiculously, significantly slower than unit tests.
IMO, we should focus on writing unit tests - we have many many of these and they still take under a minute to run. IMO we can have up to 1 minute of unit tests and life is still good.
We should then, probably use a single style of deep testing - either integration or route tests. Or we should have both but for very specific things? i.e. API -> Route tests, "database behaviour" (import, export, migrations) -> integration.
I am very much up for deleting a large section of tests if we can identify which ones are not really useful.
imagine if we only had one of the routes/integration tests - we would cut our test time by a half! 馃帀
I think, if we narrow down the kinds of tests we write, we can expand the tools we have.
I did a quick PR here: https://github.com/TryGhost/Ghost/pull/8206 where we discussed some things.
But a short list for me would be:
In short, we need to get out of our own way in the tests. Make them run faster, make them easier to write. Invest in tools so that we can test the RIGHT things FASTER. Don't bother testing the wrong things!
I would like to discuss this a little bit and then maybe start a wiki page that has guidelines that say "this is how we write tests", so that as we move forward we move towards clear goals. It's easier to work that way than to try to change all of the code base & rewrite all of the tests all in one go.
Good summary 馃憤
Route tests are slower and more involved than integration tests, but both are ridiculously, significantly slower than unit tests.
The routing tests are that slow, because each test file (and sometimes even each test case) resets the database and starts/stops the ghost server. That eats lot's of time. This should be improved by changing the way we ensure that the test case can rely on a running ghost server. For example: By default we start the server once, as soon as a test has to restart or clean the database, it tells the test utility to restart the server. Furthermore we should/could use truncate instead of a hard db reset.
We should then, probably use a single style of deep testing - either integration or route tests. Or we should have both but for very specific things? i.e. API -> Route tests, "database behaviour" (import, export, migrations) -> integration.
I think both types of testing are important. Personally, routing tests are more important, because we ensure that Ghost works from a user perspective (e.g. also user flows, url behaviour). Even that they have the disadvantage that if a test fails it's harder to figure out the reason why the test failed, because it affects the whole system. But we have added better logging and since it's easier to find out the reason.
But integration tests are important as well, as soon as we have to test connected components, which are easier to test with integration tests e.g. timezone changes and we expect a specific behaviour. But i agree, we overuse integration tests for e.g. model and api behaviour. Same for routing tests, we used to copy test cases to ensure all kinds of edge cases work.
As both types are equally slow and if we can improve the speed of the routing tests, i would like to suggest that only one type tests the model/api layer. And personally i think the routing tests should cover that. All edge cases belong to unit tests if possible.
I can think of the following rule:
Routing tests
Should ensure the basic functionality of our frontend and JSON api. No edge case testing (e.g. we have 6 routing tests which test the behaviour of the formats property).
Integration tests
Should ensure that components play nicely together. I suggest a minimal usage, because refactorings/architecture changes can easily break these tests.
Here is my suggestion for a task list:
EDITED: List was ported to the issue description.
Each subtask allows to remove duplicated or unimportant tests.
Agree on all of that 馃憤
So we have a plan to move forward in the tests 馃帀 , have a plan is half the problem solved 馃榿
Back in the /server/ folder, I think that all of the stuff to do with express apps (and possibly also middleware) should be grouped together under a top level folder. I've been wondering what to call it cos we can't call it "apps" as they are express apps, not internal ghost apps... and decided web probably makes most sense.
So the /admin/ folder, /site/ (used to be blog) folder, the app.js & routes.js file from/api/ and also the current /server/app.js file, that should probably be parent-app.js all live together.
Note 1: this probably needs a bit more work on the API folder first, cos the stuff that's in index.js should really be middleware and work a bit differently.
Note 2: the top level views folder should really be inside of theme...
@kirrg001 I removed the confusing image from the previous comment and very quickly moved everything around on a branch:

Can take a look here: https://github.com/ErisDS/Ghost/tree/structure-spike/core/server
Not suggesting it's perfect, but it's a rough idea.
I'll work on the agreed test env improvements this and next week 馃憤
FYI: I am first concentrating on the server folder refactoring and then continue with the test env, otherwise i have to tidy up the tests twice 馃槑
I think it's the best if we replace the codebase to use the following lib packages for now.
const common = require('../lib/common)
const lib = require('../lib)
const image = require('../lib/image)
As soon as we drop Node v4, we can simply transform them into 馃檴
const {errors,i18n} = require('../lib/common)
const {common} = require('../lib)
I need to come up with some better folder/packages naming for the folders inside lib, because otherwise we end up with using promise or fs, which is super confusing (see). I think the goal should be to have all the lib files grouped.
Every utility which is used in a single place, belongs to a local utils file. We have local utilities for api, helpers etc.
This has the advantage of reducing the requires from the root directory level of the server and makes a package independent from the rest of the application. There is no need to add a single function to a lib folder (except it fulfils a general functionality for the whole project and is used more than once or it's obvious that it's a useful lib which could be used in multiple places). The disadvantage is that the overview of available (local) utility functions is lower. In worst case it could happen that somebody adds a utility function twice.
libs should be a global place for general function/package sets.
utils (local utils) per package do special, local things for the target package. They can be unit tested and live in the scope of the package.
Really exciting seeing this come to life! Your point about the utils is 馃憤 馃憤 - I only very quickly went through to try to get rid of that folder in the example.
If anyone is interested in helping with the following task
- go over integration model/api tests and figure out which of them are real unit tests (e.g. test passing staticPages with different values is a unit test, because we don't test the database, we test the logic in the model layer) + see if tests are covered by routing tests
I already started with this a little while ago, see PR, but wasn't able to continue on it.
Leave a message here :)
Old table (minus module tests)
| Node | DB | Routes (307) | Unit (1514) | Integration (554) | Total
|---|---|--:|--:|--:|--:|
| v8 | sqlite | 2m | 15s | 3m | 6:53 |
| v8 | mysql | 3m | 18s | 3m | 7:19 |
| v6 | sqlite | 3m | 20s | 3m | 8:52 |
| v6 | mysql | 4m | 29s | 4m | 8:39 |
| v4 | sqlite | 4m | 26s | 4m | 11:55 |
| v4 | mysql | 4m | 21s | 3m | 8:56 |
Updated version, based on this recent build which fixed a broken test.
| Node | DB | Routes (530) | Unit (2106) | Integration (328) | Time
|---|---|--:|--:|--:|--:|
| v10 | sqlite | 4m | 34s | 4m | 11:8m |
| v10 | mysql | 4m | 32s | 3m | 9:09 |
| v8 | sqlite | 4m | 30s | 3m | 10:12 |
| v8 | mysql | 4m | 32s | 3m | 9:14 |
| v6 | sqlite | 5m | 33s | 4m | 10:39 |
| v6 | mysql | 5m | 44s | 4m | 11:25 |
Total time: 18 min 28 sec
Some observations:
The increased time on unit tests would be fine if we were seeing a significant reduction in the route or integration tests. Instead, we've got increases across the board.
I know we have more code and more features, but this is getting out of control. It feels like tests are getting in our way more than they are helping.
Using the Node v10 SQLite build as the gold standard, any test that runs at over 1000ms needs to be rewritten. Any unit tests which use the database must also be rewritten.
Any remaining > 1000ms test, or unit tests using the DB will be deleted on 14th Jan 馃槵
Total time: 18 min 28 sec
If I look at the last passed 10 builds, _the total time to wait is between 11-13mins._
This is not brilliant, absolutely, but:
I know we have more code and more features, but this is getting out of control. It feels like tests are getting in our way more than they are helping.
I just picked two random commits from 2017 (build1, build2)
Total Time 12mins and 13mins 馃榿
We should definitiely spend some time asap to finish up the tasks from this issue to improve:
Using the Node v10 SQLite build as the gold standard, any test that runs at over 1000ms needs to be rewritten.
Agree 馃憤 Added a subtask.
Any unit tests which use the database must also be rewritten.
This is already tracked as task 馃憤
I am currently working on figuring out how our acceptance tests will look like. I am playing locally with a first test which uses a new setup for acceptance tests.
index.js file, this can trouble with other tests, because of event listeners etc. we don't use a separate processNote for re-working regression tests:
We should not keep any tests in regression tests which affect our daily work.
e.g. you add tables to our database schema (!). It is fine if the acceptance test exporting a db and assert number of tables (!)
The regression tests should be independent from general updates to Ghost.
Otherwise, we still need to run yarn test:regression to ensure that we won't break them by accident. And that would be annoying.
Reduced, moved, cleaned acceptance tests 鉁匱ravis is down to 2-3mins.
@gargol will do the same for regression tests.
It's important that we first reduce the tests, before we change our utility or our assertions (less work).
I had a blog running on port 2369 and I was unable to run my tests anymore. I was trying to figure out for 10minutes why it's not working. There was no helpful debug output and I haven't received a proper error.
Would be great if we could resolve this pain 馃ぃ
After cleaning up redundant/slog tests, my current goal is to improve our regression test suite. After some analysis done yesterday here are the conclusions that came up with.
API suite has rather comprehensible structure. Think it should follow similar setup and rules as acceptance tests but with more specific state setup. Will need to be converted once acceptance suite is reworked. Don't see a need to reinvent the wheel for them :)
The rest of the suite has no clear structure and has a very different state setup/assertion techniques. For example, site_spec mocks express and doesn't operate on supertest built-in assertions. Some tests operate strictly on model layer (/models, /migrations), which is very coupled as for regression test. Some suites are using cheerio to check response content but it's inconsistent around what is actually checked.
List of tests non-API suites and possible refactors:
default site fixtures populate the db in expected way. most of it are permission checks. tightly coupled with models layer. probably could be grouped together with importer (will stay as a separate test but grouped for similar style)./settings/routes/yaml endpoint.Let's discuss this ^ on Friday 馃憤
@kirrg001 if it's ok, would be best to talk through it on Thursday morning your time as I'm flying on Friday :wink:
We had a chat about the test env improvements and we realised that we want to much at once and we need to set smaller goals.
Goals for next Monday
We will set tiny goals every week, which are achievable on a Monday.
Had a stab at the reduction of model regression tests today. Was able to fairly easy port settings model suite into unit tests by mocking db access with mock-knex. Tried taking a similar approach with posts model suite, but because the logic is far more complex mocking db layer became complicated quickly. Reduced and ported to API regression tests, some of the cases for posts model regression suite.
Couple thoughts regarding a further reduction of posts model regression suite:
onSaving into smaller functions so it becomes unit testable. This will allow porting validation test cases and possibly other cases. collision detection suite with unit tests for collision detection pluginThis issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Not stale
Most helpful comment
Old table (minus module tests)
| Node | DB | Routes (307) | Unit (1514) | Integration (554) | Total
|---|---|--:|--:|--:|--:|
| v8 | sqlite | 2m | 15s | 3m | 6:53 |
| v8 | mysql | 3m | 18s | 3m | 7:19 |
| v6 | sqlite | 3m | 20s | 3m | 8:52 |
| v6 | mysql | 4m | 29s | 4m | 8:39 |
| v4 | sqlite | 4m | 26s | 4m | 11:55 |
| v4 | mysql | 4m | 21s | 3m | 8:56 |
Updated version, based on this recent build which fixed a broken test.
| Node | DB | Routes (530) | Unit (2106) | Integration (328) | Time
|---|---|--:|--:|--:|--:|
| v10 | sqlite | 4m | 34s | 4m | 11:8m |
| v10 | mysql | 4m | 32s | 3m | 9:09 |
| v8 | sqlite | 4m | 30s | 3m | 10:12 |
| v8 | mysql | 4m | 32s | 3m | 9:14 |
| v6 | sqlite | 5m | 33s | 4m | 10:39 |
| v6 | mysql | 5m | 44s | 4m | 11:25 |
Total time: 18 min 28 sec
Some observations:
The increased time on unit tests would be fine if we were seeing a significant reduction in the route or integration tests. Instead, we've got increases across the board.
I know we have more code and more features, but this is getting out of control. It feels like tests are getting in our way more than they are helping.
Using the Node v10 SQLite build as the gold standard, any test that runs at over 1000ms needs to be rewritten. Any unit tests which use the database must also be rewritten.
Any remaining > 1000ms test, or unit tests using the DB will be deleted on 14th Jan 馃槵