Java vs Go: Impressive solutions for invented problems
Tags: java, go, architecture, codequality
Published: 2020-06-19 11:06:15


Originally [published on my personal blog](https://jawher.me/java-go-impressive-solutions-invented-problem/)

## Let's start with the Shadoks

![3 Shadoks](https://dev-to-uploads.s3.amazonaws.com/i/uykj935k9gayhjhccwi1.png)

*Les Shadoks* is a cartoon created by [Jacques Rouxel](https://en.wikipedia.org/wiki/Jacques_Rouxel_(animator)) which was first broadcast in France between 1968 and 1974.

Les Shadoks series had a lasting cultural impact, and introduced some lovely phrases/expressions that, to this day, are used in the day to day discussions:

> When one tries continuously, one ends up succeeding. Thus, the more one fails, the greater the chance that it will work

or:

> Every advantage has its disadvantages and vice versa

or even:

> If there is no solution, it is because there is no problem.

But most importantly:

> Why do it the easy way when you can do it the hard way?

## Why the Shadoks reference ?

I've been writing Java professionally for the last 13 years.
Luckily, I also work with other languages, mostly Go and Javascript.

Working with Go made me notice a pattern in the Java ecosystem:
it is a wide and rich one, with very solid & technically impressive libraries & frameworks.

But the thing is, many of those libraries & frameworks are impressive solutions for a problem which didn't need exist in the first place: trying to use a declarative approach everywhere.

## Case in hand: JUnit tests

In the following, I'll be using a *(unit)* test written in Java and showcasing [JUnit](https://junit.org/junit5/), which is arguably the most popular and used testing framework in the Java land, in its latest version at the time this post was written, version 5 codenamed Jupiter.

**There will be a couple of code snippets, so please bear with me till I make my point at the end.**

### In java, everything is a class

The same applies to tests:


```java
import org.junit.jupiter.api.Test;

public class ExampleTest {

    @Test
    void test_login() {
        assertThat(login("username", "p@$$w0rd")).isTrue();
    }
}
```

As can be seen in the code snippet above, a test is:

* a class
* with a method
* annotated with `@Test` to tell JUnit that this is the test code we'd like to exercise.

### Test cases

What if we'd like to test our `login` logic against multiple `username` and `password` combinations ?

One way to do it would be to create a new test method for every combination:


```java
import org.junit.jupiter.api.Test;

public class ExampleTest {

    @Test
    void accepts_correct_login1() {
        assertThat(
                login("username", "p@$$w0rd")
        ).isTrue();
    }

    @Test
    void rejects_incorrect_username() {
        assertThat(
                login("incorrect-username", "p@$$w0rd")
        ).isFalse();
    }

    @Test
    void rejects_incorrect_password() {
        assertThat(
                login("username", "incorrect-password")
        ).isFalse();
    }
}
```

This quickly gets unwieldy when:

* The test method body is long, forcing us to duplicate it
* There are a lot of test cases, e.g. dozens of username/password combinations for example
* Dynamically generated test cases, e.g. random values, fuzzing, ...

### Parameterized tests

That's why JUnit offers a way to write the test method only once, and invoke it as many times as we provide test cases:


```java
import org.junit.jupiter.api.Test;

public class ExampleTest {

    @ParameterizedTest
    @MethodSource("loginTestCases")
    void test_login(String username, String password, boolean expected) {
        assertThat(
                login(username, password)
        ).isEqualTo(expected);
    }

    private static Stream<Arguments> loginTestCases() {
        return Stream.of(
                Arguments.of("username", "p@$$w0rd", true),
                Arguments.of("incorrect-username", "p@$$w0rd", false),
                Arguments.of("username", "incorrect-p@$$w0rd", false)
        );
    }
}
```

Lots of noise, but basically:

* `loginTestCases` returns the list of test cases
* `test_login` is a parametrized test method that gets fed the different combinations

![Shadok](https://dev-to-uploads.s3.amazonaws.com/i/8r0051o3se1z2q28trru.png)

### Test case naming

In the testing report, and by default, parameterized test methods will get a dynamically generated name which is the combination of all the parameters it receives.

This can be tweaked using a templated string:

```java
@ParameterizedTest(name = "login({0}, {1}) => {2}")
```

Where `{0}`, `{1}`, ... get replaced by the method argument in the corresponding position.


### Execution order

Say we have 2 cases that must run in a specific order, e.g.:

* first test method logs the user in and obtains a *token*
* second test method uses that token to test the system further

The code would look like this:

```java
public class ExampleTest {
    private String token;

    @Test
    void testLogin() {
        String token = login("username", "p@$$w0rd")
        assertThat(token).isNotNull();

        this.token = token;
    }

    @Test
    void testApiCall() {
        int res = apiCall(this.token);

        assertThat(res).isEqualTo(42);
    }
}
```

Except this won't work as expected:

* JUnit doesn't guarantee the test method execution order
* JUnit doesn't guarantee that the same test instance would be reused between the 2 test methods (needed to share the `token` field)

But not to worry: more annotations to the rescue:

```java
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class ExampleTest {
    private String token;

    @Test
    @Order(1)
    void testLogin() {
        String token = login("username", "p@$$w0rd")
        assertThat(token).isNotNull();

        this.token = token;
    }

    @Test
    @Order(2)
    void testApiCall() {
        int res = apiCall(this.token);

        assertThat(res).isEqualTo(42);
    }
}
```

![Shadok](https://dev-to-uploads.s3.amazonaws.com/i/i2px7aldeja3k48ax4nt.png)

### Conditional tests

What if we want to only run a specific test method when a certain condition is set ?
Say for example we have a slow test we would like to only run on a beefy CI agent, based on `E2E` env var for example.


```java
    @Test
    @EnabledIfEnvironmentVariable(named = "E2E", matches = "true")
    void testSlowApiCall() {
        int res = apiCall(this.token);

        assertThat(res).isEqualTo(42);
    }
```

![Shadok](https://dev-to-uploads.s3.amazonaws.com/i/rdtkxwmt1nwn51cvjqtd.png)

## What's the Go way of doing the same ?

### Test cases

Go has:

* slices/arrays
* for loops


```go
func TestLogin(t *testing.T) {
	type Case struct {
		Username string
		Password string
		Expected bool
	}

	for _, tt := range []Case{
		{"username", "p@$$w0rd", true},
		{"incorrect-username", "p@$$w0rd", false},
		{"username", "incorrect-p@$$w0rd", false},
	} {
		t.Run(fmt.Sprintf("login(%s, %s) => %v", tt.Username, tt.Password, tt.Expected), func(t *testing.T) {

			require.Equal(t, tt.Expected, login(tt.Username, tt.Password))
		})
	}
}
```

I make use of [testify](https://github.com/stretchr/testify) toolkit to simplify assertions.

### Order, keeping state

Go executes instructions from top to bottom.
Unless you exit the current scope, the variables keep their values.

```go
func TestLoginAPICall(t *testing.T) {
	token := ""
	t.Run("login", func(t *testing.T) {
		token = login("username", "p@$$w0rd")
		require.NotEmpty(t, token)
	})

	t.Run("api call", func(t *testing.T) {
		res := apiCall(token)
		require.Equal(t, 42, res)
    })
}
```

### Conditional execution

Go has `if` switch:

```go
func TestLogin(t *testing.T) {
	token := ""
	t.Run("login", func(t *testing.T) {
		token = login("username", "p@$$w0rd")
		require.NotEmpty(t, token)
	})

	if os.Getenv("E2E")!="" {
		t.Run("slow api call", func(t *testing.T) {
			res := slowApiCall(token)
			require.Equal(t, 42, res)
		})
    }
}
```

## Closing words

Something went very wrong in the Java ecosystem:

for some reason, we as a community, collectively decided that:

* we should only be using the language's constructs (`if`, `for`, ...) in the business/functional code
* for anything else, e.g. tests, configuration, frameworks, etc., we instead have to invent a half-assed declarative language, preferably annotation based

It is most impressive that we got so far with these self-imposed limitations.

I am not picking on JUnit.
On the opposite: what they are able to achieve is simply impressive.
Everything is very extensible and configurable, and it must have taken lots of time & effort to reach this point.

Yet, the Go testing library achieves the same level of power/flexibility while being much simpler and with a tinier surface area simply by choosing an imperative model (`t.Run()` vs `@Test`), making it possible to use the full power of the host language.