Skyramp Feature Highlight: Using Dynamic Python for Tests and Mocks
Tags: python, testing, kubernetes, microservices
Published: 2023-10-03 17:15:31

Python developers, rejoice! [Skyramp](https://www.skyramp.dev) now supports Python code for dynamically testing and mocking your distributed applications. Previously, Skyramp supported only JavaScript for dynamic requests and responses within test and mock description files. In this blog post, we'll walk you through Skyramp's new capability of employing dynamic Python code in requests and responses for testing and mocking microservices, right from a running Kubernetes cluster.

## Unpacking Skyramp Tests

Before diving into the step-by-step example, let's first lay the foundation by understanding what constitutes a Skyramp test. At the heart of every Skyramp test is a test description file. This file can either be auto-generated using `skyramp tester generate` or created manually as a `.yaml` file.

### What's Inside a Test Description?

Within a Skyramp test description file, you define the essential components of your test. This includes the scenarios for testing, the services to be tested, the requests you'll make to them, the expected responses in the form of asserts, and any custom logic or handlers to process these interactions. Let's dissect the important elements.

Here's a simple scenario in a test description:

```yaml
scenarios:
  - name: scenario1
    steps:
    - request: request1
    - asserts: requests.request1.res.message == "abc"
```

The scenario above simply tests if the message returned from a service is equal to "abc". Below is an example request to support the scenario above. We can assume this hypothetical service takes an `id` and returns a `message`. Both of these YAML sections are contained within a Skyramp test description file. 

```yaml
requests:
  - name: request1
    endpoint: our-service
    method: GetMessage
    requestValue:
      blob: |
        {
            "id": "123"
        }
```

Notice the block under `requestValue`. It is configured as a static `blob` in this example, but it can also be configured as dynamic code that the Skyramp Worker will execute during a test. The language used in a dynamic handler had been limited to JavaScript, but now you can use Python! Let's look at how.

## A Hands-on Example: Testing with Python Code

To illustrate how Python can now be harnessed in Skyramp tests, let's embark on a simple journey you can follow yourself. We'll clone a sample project from a Skyramp-provided repo on GitHub, which conveniently contains microservices that can be swiftly deployed to a Kubernetes cluster, all primed and ready for testing.

### Paving the Way with the Skyramp CLI

First, make sure you have the Skyramp CLI installed. The Skyramp CLI enables the [Deployer](https://www.skyramp.dev/docs/deployer/), [Mocker](https://www.skyramp.dev/docs/mocker/), and [Tester](https://www.skyramp.dev/docs/tester/) tools. You can follow [these easy steps](https://www.skyramp.dev/docs/get-started/install-client/) from the Skyramp Docs for installation.

### Cloning the Project

Start with cloning Skyramp's `letsramp/sample-microservices` repo, which is a sample e-commerce system that includes a number of microservices. See the [GitHub repo Readme](https://github.com/letsramp/sample-microservices) for additional information. The Skyramp repo is based on Google Cloud's [Online Boutique](https://github.com/GoogleCloudPlatform/microservices-demo), adding test demos and support for REST and Thrift APIs.

From a terminal, you can execute this command:
```bash
git clone https://github.com/letsramp/sample-microservices.git
```
### Start your Cluster

Now that the project is in place, you can navigate to the `grpc-demo` directory in your terminal and bring up a local cluster for testing by executing these three commands:

```bash
cd sample-microservices/skyramp/grpc-demo
skyramp cluster create --local
skyramp deployer up checkout-system
```

### Putting Python to the Test

With the system-under-test up and running, let's edit one of the tests to show where we can insert Python code that will be run dynamically during testing. The `tests/checkout-test.yaml` test description file contains a `requests` section with a static blob. Edit the section under `requestValue` for `request1` as follows:

```yaml
requests:
  - name: request1
    endpoint: cart-service
    method: AddItem
    requestValue:
      python: |
        def handler():
          uid = "abcde"
          qty = 5 * 5
          pid = "OLJCE"
          pid += "SPC7Z"
          request_value = {
            "userId": uid,
            "item": {
                "productId": pid,
                "quantity": qty
            }
          }
          return SkyrampValue(value=request_value)
```

You can see we replaced the `blob` attribute with `python` and are now creating the request values in code. To learn more about the specific details of how you can use Python in test descriptions, visit the Skyramp Docs that cover [Python Dynamic Requests](https://www.skyramp.dev/docs/tester/test-description/#python-dynamic-requests).

You will notice that the `tests/checkout-test.yaml` test description file has two requests. The first request, shown above as `request1`, adds an item to the cart. The second request, `request2`, places an order. We will leave the `requestValue` in `request2` as a static `blob`.

For our test scenario under `scenarios`, we first make a request to `request1` and then make a request to `request2`. The existing assert checks that the `productId` matches the first item returned in the response from `request2`. Let's also add an `asserts` line in the same file  to check the `quantity` value returned. This should match the `quantity` we set in `request1`. Notice the type is a `number` and not a `string`.

```yaml
scenarios:
  - name: scenario1
    steps:
      - request: request1
      - request: request2
      - asserts: requests.request2.res.order.items[0].item.productId == "OLJCESPC7Z"
      - asserts: requests.request2.res.order.items[0].item.quantity == 25
```

We are almost ready to run our new test scenario with Python, but we are missing one final piece. We know our checkout depends on the payment service, but it is not running in our system-under-test. Luckily, we have a mock for that.

### Yes, Mocks Too!

Not only is Python available for dynamic requests in the test description, but Python is also available for dynamic responses in the mock description.

Open `mocks/payment-service-k8s.yaml` for editing, which is also under the `skyramp/grpc-demo` folder in the `letsramp/sample-microservices` repo.

Change the section under `responseValues` to include the `python` attribute and the code shown below:

```yaml
responseValues:
    - name: ChargeResponse
      python: |
        def handler(req):
          key = "transaction_id"
          value = "default-string"
          return SkyrampValue(value={key: value})
```

Here, we now return values from Python-generated variables instead of a static `blob` as before. To learn more about the specific details of how you can use Python in mock descriptions, visit the Skyramp Docs that cover 
[Python Dynamic Responses](https://www.skyramp.dev/docs/mocker/mock-description/#python-dynamic-response)

### Mocking and Testing with Skyramp

With our new test description and mock description updated to include dynamic Python, we can proceed with our test session. Skyramp makes running tests a breeze.

First, apply the mock that was updated with Python code:
```bash
skyramp mocker apply -n test-grpc-demo
```
You should see this output in the terminal:

```bash
Applying mock config     [##############################################################] 100 %
Successfully applied mock configurations.
```

Next, run the test that was updated with Python code:
```bash
skyramp tester start checkout-test -n test-grpc-demo
```

The test results should produce the following output:

```bash
 - pattern0.scenario1.2.assert
    [Status: finished] [Started at: N/A]
    Assert: requests.request2.res.order.items[0].item.productId == "OLJCESPC7Z"
    Passed: true
  - pattern0.scenario1.3.assert
    [Status: finished] [Started at: N/A]
    Assert: requests.request2.res.order.items[0].item.quantity == 25
    Passed: true
```

Nice job, you did it! This was just a simple taste to show how to use Python for tests and mocks, but there is much more to explore. Additional capabilities include: 
* Utilizing the `req` parameter for incoming requests ([see docs](https://www.skyramp.dev/docs/tester/test-description/#python-dynamic-requests))
* Using the `pythonPath` attribute for external source code files ([see docs](https://www.skyramp.dev/docs/tester/test-description/#python-dynamic-requests))
* [Installing NPM-based packages](https://www.skyramp.dev/docs/tester/test-description/#installing-npm-based-packages)
* [Chaining and Overrides](https://www.skyramp.dev/docs/tester/test-description/#chaining-and-overrides)

## Wrapping Up

That concludes our exploration of using Python code in Skyramp tests and mocks. Now Python developers can leverage their skills to create comprehensive and robust testing scenarios. We hope you've enjoyed this glimpse into using Python for dynamic requests and responses in testing distributed applications with Skyramp.

Stay tuned for more updates and enhancements as Skyramp continues to evolve, enabling developers to optimize their testing processes like never before. Happy testing!