apiaryio / dredd

Language-agnostic HTTP API Testing Tool
https://dredd.org
MIT License
4.2k stars 280 forks source link

Future Dredd proposal #1186

Open honzajavorek opened 5 years ago

honzajavorek commented 5 years ago

Dredd reads the API description document, executes HTTP requests it finds, and validates the HTTP responses it receives.


I think Dredd's architecture should get more modular, decoupled. This is my idea on how it should look like:

Direction

Support

Architecture

IMG_3495

IMG_3494

Gardening

artem-zakharchenko commented 5 years ago

Thanks once more for sharing these insights. Giving my feedback below.

Hooks

All hooks are provided with the real transaction(s) to modify

What implications do you see if we would allow instead to return the next transaction from a hook?

Hooks API

I completely support Hooks API improvements you've mentioned.

Here's how the current API looks like:

const hooks = require('hooks');

hooks.before('Articles > Publish an article', (transaction) => {
  transaction.request.headers.Authorization = 'Basic: YWxhZGRpbjpvcGVuc2VzYW1l';
});

Get rid of a hacky require('hooks') and let hooks interface be explicitly imported (from Dredd, or a dedicated package), or implicitly injected.

Since hooks can be written in different languages, I don't have a sufficient level of expertise to suggest a proper dependency injection pattern. However, I think it would be less confusing to remove the require call and execute a hook within a certain context. Just like any other testing framework does with injection of their before/after/etc. hooks. This also brings the surface of injection "closer" to Dredd, making it less troublesome for the end users to maintain.

// If this is not installed I'm getting confused.
// IDE may complain module not found.
// What if there is an actual package called "hooks"?
const hooks = require('hooks')

hooks.before()
hooks.afterAll()
// No explicit import, but I'm aware this is a hook file
// and I've worked with other testing frameworks, so
// I understand these hook functions are injected for me. 
before()
afterAll()

Such hook functions are easier to type as well, as we need to ship a type definition module and the end users may simply include it to state those functions are known:

// tsconfig.json
{
  "compilerOptions": {
    // What type definitions to incliude
    "types": ["dredd/hooks"]
  },
  // Affected files
  "include": "hooks.js",
}

Typing something like require('hooks') would mean an introduction of type definitions for a module, which may conflict if such module actually exists.

Change how scenarios may be referenced in the API description document.

I am not sure if it's currently possible to declare something like beforeAll hook with Dredd to trigger a certain logic before a group of scenarios. Generally, I'm open to any kind of API reogranization at this point.

Other

The HTML reporter should render the same locally as the Apiary reporter and apiary.io should share the same (React) components for this

This effectively means re-creating the Tests page from Apiary and shipping it as a part of reporter.

  1. How would we be able to import the UI components (are they open-sourced)?
  2. Who will maintain this implementation? There is a build pipeline needed for such reporter (React app) to be built. It sounds like having reporters in separate packages may be a viable option.

describe API in TypeScript annotations

I would absolutely love to do that.

Tests

The changes you've described above affect if not all test suites. I often find the cost of adjusting test suites unjustified and taking more time than actual development. Do you consider to write tests from scratch (TDD, BDD) for this proposal? I know it looks like a monstrous effort at first, but from my practice I believe we would save much more time writing them (properly) from scratch than trying to adjust (and inevitably refactor) existing tests.

Dredd is tested on CircleCI for speed and usability, together with AppVeyor for verifying Windows compatibility.

Perhaps by the time we get to this state CircleCI will already support testing on Windows machines.

Diagram (flow)

  1. If TransactionRunner should accept any recorded traffic as an input that means its input signature must be the same for such recorded traffic and for the list of transactions emitted from the TransactionCompiler. I think we should make a recorded traffic an input to TransactionCompiler so the flow would look like this:
-- traffic --> TransactionCompiler -- transactions --> TransactionRunner

This makes their responsibilities properly scoped:

I now see there is a dotted arrow pointing from HAR to "Parsing". But there is still a primary error pointing to the TransactionRunner, so I thought to clarify this.


Overall, if we adopt a monorepo pattern together with a clear vision of responsibilities and restrictions of each module, we can start developing this incrementally at a good rate.

I can play around with type definitions for those modules, and brainstorm proper responsibilities of each.

honzajavorek commented 5 years ago

JS Hooks

What implications do you see if we would allow instead to return the next transaction from a hook?

A few very significant implications - all current Dredd users would need to rewrite their existing hook files, and perhaps all hooks handlers for other languages should do a similar change to be aligned with the interface change. Rolling out such change is a delicate thing which could significantly harm adoption of Dredd by its current users. Streamlined interface? Yes. But for me the price is too high and benefit too low. I think it would make more sense to:

Get rid of a hacky require('hooks')

I think having require('dredd/hooks') has been discussed in https://github.com/apiaryio/dredd/issues/1058#issuecomment-399091816 and would be a desired change, but again, this breaks all people's hooks, so require('hooks') should probably somehow work for people as well as a fallback.

Allow wildcard

This is already supported by PHP hooks and there is an issue for that: https://github.com/apiaryio/dredd/issues/248

However, in long-term, I don't think using anything like "path" to address HTTP transactions is a good idea. After many design sessions and research, I'd propose the transaction names to become independent on structure of the API description document and rather becoming a "query language" for the list of transactions (e.g. 'gimme all transactions with HTTP 200 response' or 'all JSON body requests') possibly addressing more than one (which is a generalization of the wildcard idea). Syntax of such transaction names is TBD, but I think something has been written down as a proposal internally in Apiary whitepapers.


👉 Check out existing flaws of the JS hooks and their suggested solutions.

Other

This effectively means re-creating the Tests page from Apiary and shipping it as a part of reporter. How would we be able to import the UI components (are they open-sourced)?

I think it means re-thinking the test report page from Apiary and re-implementing it from scratch in React components as part of the Dredd repository, i.e. open sourced. Then re-using these components in Apiary SaaS.

There is a build pipeline needed for such reporter (React app) to be built. It sounds like having reporters in separate packages may be a viable option.

That sounds like implementation detail at this stage. If Dredd becomes a monorepo, it doesn't matter much if it lives in another package (directory).

Tests

Do you consider to write tests from scratch (TDD, BDD) for this proposal?

We discussed this in person pretty much, but I'll repeat my thoughts here. I'm not a fan of rewriting tests, it's dangerous and it doesn't bring any/much user value. However, the test suite as it is now is not sustainable. I believe following approach could work:

Diagram (flow)

TransactionCompiler and TransactionRunner

That's kind of TBD, that's why the arrows are confusing and dotted. Now I tend to think it would be cleaner architecture if all input traffic went through the Fury toolchain and Dredd could consume it as API Elements, no matter what was it originally:

API Blueprint -> [ Fury ] -> API Elements -> [ Dredd ]
HAR -> [ Fury ] -> API Elements -> [ Dredd ]

Dredd needs four things to validate a transaction: expected request, actual request, expected response, actual response. The only difference between the API description formats and the "recorded traffic" formats will be in which elements of the 4-tuple they provide. If some elements are missing, Dredd will need to somehow acquire them before it can perform the validation:

API Blueprint
    -> (expected request, ???, expected response, ???)

In this case, Dredd needs to infer an actual request and to perform the request in order to acquire an actual response. Then it can validate.

API Blueprint
    -> (expected request, ???, expected response, ???)
HAR
    -> (???, actual request, ???, actual response)

In this case, sharp reader will notice that the tuples zip together. Dredd doesn't need to infer actual requests and it doesn't need to do any HTTP communication to acquire actual responses. It only needs to pair the actual traffic to the corresponding expectations and continue with validation. (The Dredd run in this case would be super-fast as apart from reading & parsing the source files there's no IO.) It will still run some hooks though (beforeValidation, beforeEachValidation, ...).

The architecture should be ready for this. That means Dredd needs to know what kind of source it is parsing (API description with expectations or actual traffic) and then combine what's available and/or infer what's missing (compile & perform HTTP request). Sounds like we'll need a lot of decoupling :)

honzajavorek commented 5 years ago

IMG_3613

mathieulaude commented 1 year ago

@artem-zakharchenko @honzajavorek @abtris Do you plan to implement the future any soon ? Or should we (users) consider this tool as "not maintained anymore" ?

honzajavorek commented 1 year ago

@mathieulaude Nobody from the original team works on Dredd anymore. AFAIK there's no dedicated team as of now, thus no future.

@kuba-kubula at least still works in the company behind Dredd, so might have some info. But he might not be legally or process-wise allowed to make public announcements.