...

Text file src/github.com/datawire/ambassador/v2/python/tests/README.md

Documentation: github.com/datawire/ambassador/v2/python/tests

     1Ambassador Unit Tests
     2=====================
     3
     4Ambassador is tested with [KAT](../../kat/README.md). **You are strongly encouraged to add tests when you add features.** However, KAT is fairly complex, and how you actually add will depend a bit on what kind of thing you're adding. Here we'll talk a bit about how Ambassador uses KAT, and how to work within that
     5
     6The Ambassador KAT class hierarchy looks a bit like this:
     7
     8```
     9Node
    10  ServiceType (abstract_tests.py)
    11    HTTP -- generic HTTP echo backend (abstract_tests.py)
    12    AHTTP -- authentication service using HTTP (abstract_tests.py)
    13    etc.
    14  Test
    15    AmbassadorTest (abstract_tests.py)
    16      TCP (test_ambassador.py)
    17      Plain (test_ambassador.py)
    18      TracingTest (t_tracing.py)
    19      etc.
    20    MappingTest (abstract_tests.py)
    21      SimpleMapping (test_ambassador.py)
    22    OptionTest (abstract_tests.py)
    23      AddRequestHeaders (test_ambassador.py)
    24      AddResponseHeaders (test_ambassador.py)
    25      etc.
    26```
    27
    28Broadly speaking:
    29
    30- an `AmbassadorTest` is a test that instantiates its own Ambassador, and can therefore safely modify things like the Ambassador `Module` and such;
    31- a `MappingTest` defines a `Mapping` that will be added to both the `Plain` and `TCP` `AmbassadorTests`;
    32- an `OptionTest` defines an option that will be added to all `MappingTests`; and
    33- a `ServiceType` is a kind of backend service you can talk to.
    34
    35Historically, we started with all the tests lumped into `test_ambassador.py`. As this has become more and more unwieldy, we've split out into the `t_*.py` files. If you want to create a new file for your test, great! but don't give it a name starting with `test_` because that can confuse things.
    36
    37All the way at the bottom of `test_ambassador.py` you'll find
    38
    39```
    40main = Runner(AmbassadorTest)
    41```
    42
    43which is what actually uses KAT to instantiate and run all subclasses of `AmbassadorTest` as tests.
    44
    45### `OptionTest`
    46
    47If you're lucky, your test can be an `OptionTest`: this is appropriate if you're adding an option to the `Mapping` class. Just check out an existing test (like `AddRequestHeaders`) and you'll be good to go.
    48
    49Within an `OptionTest`:
    50- the `config` method should just `yield` the string you want to be added to the `Mapping` configuration for your option
    51- the `queries` method only needs to `yield` queries that the parent `MappingTest` won't already be doing. So, for example, the `AddRequestHeader` test doesn't have a `queries` method -- it will have an effect on all the queries that the parent is already performing.
    52- the `check` method can assert about `self.parent.results` to check things about queries made by the parent, or about `self.results` if the `queries` method added more queries.
    53
    54### `MappingTest`
    55
    56A `MappingTest` adds an entirely new `Mapping` to an `AmbassadorTest`'s Ambassador. `MappingTest`s are a bit more challenging to work with than `OptionTest`s.
    57
    58- To initialize a `MappingTest`, you must pass it a `target`, which must be an instance of (a subclass of) `ServiceType` at minimum, and you may also pass it a tuple of `OptionTest`s (and of course a `name`). The minimal instantiation is something like
    59
    60    `MappingTest(HTTP())`
    61
    62  to create a `MappingTest` with our generic HTTP echo service for its `target`. The `target` is accessible in the `MappingTest` as `self.target`.
    63
    64- The `variants` class method must `yield` valid instances of the `MappingTest`. The `WebSocketMapping` test is a good example:
    65
    66    ```
    67    class WebSocketMapping(MappingTest):
    68        ...
    69
    70        @classmethod
    71        def variants(cls):
    72            for st in variants(ServiceType):
    73                yield cls(st, name="{self.target.name}")
    74    ```
    75
    76  Here we define `WebSocketMapping` as a subclass of `MappingTest`. It varies with `ServiceType`s -- for every kind of `ServiceType` out there, we take the instantiated `ServiceType` and instantiate a new `WebSocketMapping` with the instantiated `ServiceType` as the `target`. We need to change the `name` of our new instance, too, so that we don't have duplicates.
    77
    78- `queries` and `check` should generate and check a new set of queries. Again, it's OK to look at `self.parent.results` in `check`.
    79
    80### `AmbassadorTest`
    81
    82An `AmbassadorTest` is the place that new Ambassadors actually get created. If your test doesn't need a new Ambassador, don't create a new `AmbassadorTest`! Each of these requires a new pod in the test cluster; they're somewhat expensive. On the other hand, you _need_ an `AmbassadorTest` if you need to play with the `Ambassador` `Module`, or TLS contexts, or things like that.
    83
    84- By default, an `AmbassadorTest` uses an `ambassador_id` of its name. You can override this by setting `ambassador_id` on your instance, but why bother?
    85
    86- By default, an `AmbassadorTest` will appear in namespace `default`. You can override this by setting `namespace` on your instance.
    87
    88- If you set `single_namespace` on your instance, that will restrict your Ambassador to only looking in its namespace.
    89
    90- The `config` method will likely need to `yield` tuples: `yield self, configstr` to set configuration on the Ambassador itself, or `yield self.target, configstr` to set configuration on a service you've saved as `self.target`.
    91
    92- If you need to change `manifests`, you will almost certainly need to include a call to `super().manifests()` too. The common pattern here is
    93
    94    def manifests(self):
    95        return manifeststr + super().manifests()
    96
    97  so that you can be that the manifest in `manifeststr` takes effect before the Ambassador is created.
    98
    99- A fairly common pattern for a single-purpose `AmbassadorTest` is to create a target as `self.target` in the `init` method (not `__init__` -- leave that alone!).
   100
   101### More coming soon!

View as plain text