Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use test builder. #1871

Merged
merged 1 commit into from
Jul 21, 2017
Merged

Use test builder. #1871

merged 1 commit into from
Jul 21, 2017

Conversation

timoreimann
Copy link
Contributor

@timoreimann timoreimann commented Jul 17, 2017

This change introduces the builder pattern to the Marathon unit tests in order to simplify and reduce the amount of testing boilerplate.

Additional changes:

  • Add missing unit tests.
  • Make all tests look consistent.
  • Use dedicated type for task states for increased type safety.
  • Remove obsoleted getApplication function.

Note: I pushed the branch to the containous repo because I intend to submit another PR based onto this one.

@containous/marathon @containous/traefik PTAL.

@timoreimann
Copy link
Contributor Author

/cc @marco-jantke happy to have your review too. :-)

Copy link
Contributor

@m3co-code m3co-code left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some minor comments. PR looks great to me, uniformity FTW :D

BTW the validation make target is currently still failing.

},
}),
),

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary newline.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Argh, damn you unintentional blank lines!

Removed. :-)

Ports: []int{},
},
desc: "port definition available",
application: application(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't you use the ipAddrPerTask here to simulate the same behaviour or did you remove it by intend?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did remove it intentionally for two reasons (should have commented this in the first place, sorry):

  1. My original thinking was that the IP-per-task setup would serve as a safeguard to running into an undesirable happy path due to a bug in the code (i.e., false positive). On closer inspection, this seems highly unlikely since we can't really produce the expected port result of 443 without configuring it somewhere, which we only do for the test case at hand that we intend to cover.
  2. Configuring both port definitions and IPs-per-task should actually not be allowed: go-marathon explicitly clears the former when setting the latter because the Marathon API would otherwise reject our API request. In that sense, it makes even less sense to set up both in the test (though I wouldn't relax the production code logic because of that because you never know how the Marathon API may evolve over time).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Np, SGTM.

provider := &Provider{}
actual := provider.getPassHostHeader(c.application)
if actual != c.expected {
t.Fatalf("actual %q, expected %q", actual, c.expected)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think t.Errorf be sufficient here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and almost everywhere else -- updated.

{
desc: "label missing",
application: application(),
expected: "NetworkErrorRatio() > 1",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just out of curiosity. This means there is always a CircuitBreaker in place for Backends provided by Marathon, but the expression should never evaluate to true? Is this intended? It looks like it would only add one middleware which can not have any effect.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part is somewhat confusing: While we do return a default value, we only include circuit-breaker settings when the label is actually set.

It might make sense to change the default return value to an empty string to improve clarity. Let's consider that for a future PR.

want string
cases := []struct {
desc string
value *string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICS its not necessary anymore to use *string as we only dereference the pointer when passing it to AddLabel. Or do I miss something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right: There's still one test left where we want to distinguish set/unset/omitted, but not this one.

Simplified.

want string
cases := []struct {
desc string
value *string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as previous comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Simplified too.

@timoreimann
Copy link
Contributor Author

@marco-jantke all comments addressed.

Copy link
Contributor

@m3co-code m3co-code left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing. LGTM 👍

@ldez
Copy link
Contributor

ldez commented Jul 18, 2017

@timoreimann could you fix the linting?

Errors from golint:
provider/marathon/marathon.go:31:6: exported type TaskState should have comment or be unexported

@timoreimann
Copy link
Contributor Author

@ldez should be good now.

Copy link
Contributor

@ldez ldez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you do the same approach with backends and frontends:

expectedFrontends: map[string]*types.Frontend{
	"frontend-app": {
		Backend: "backend-app",
		Routes: map[string]types.Route{
			"route-host-app": {
				Rule: "Host:app.docker.localhost",
			},
		},
	},
},
expectedBackends: map[string]*types.Backend{
	"backend-app": {
		Servers: map[string]types.Server{
			"server-task": {
				URL:    "http://localhost:80",
				Weight: 0,
			},
		},
		CircuitBreaker: nil,
	},
},

}
})
}
}

func stringp(s string) *string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe move this into testhelpers like Intp?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, done.

application: marathon.Application{
Labels: &map[string]string{}},
desc: "label missing",
application: application(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid the ambiguity of names, I rather name builders like anApplication(...) and if necessary options like withLabels(...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, I'd prefer not to use longer names in order to keep things concise since builder functions are used very frequently. IMHO, we shouldn't have extra verbs on builder operations.

anApplication looks quite weird to my eyes. :) How about createApp and createTask instead?

Copy link
Contributor

@ldez ldez Jul 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create prefix is not fluent syntax 😉 (builders must be fluent, I think)
@vdemeester do that like me http://vincent.demeester.fr/posts/2017-01-01-go-testing-functionnal-builders/

But in this case, I prefer you don't change anything instead of use a create prefix.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, now I'm confused: The blog post uses a/an and with prefixes. However, our Docker builder comes with just two with functions and otherwise uses no prefixes. ¯_(ツ)_/¯

@vdemeester: can you help me out here? :)

Copy link
Contributor Author

@timoreimann timoreimann Jul 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vdemeester's out-of-band feedback is that he more often than not drops the with prefix in his builders. :-)

So I just added the create prefix. Not sure if it violates fluent builder syntaxes. I did this a lot back in my Java days; maybe that can be considered a sin of the past. ;)

}
}

func labels(l map[string]string) func(*marathon.Application) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really show the need to use a map here.
Why not use a simple function like this:

func label(key, value string) {
	return func(app *marathon.Application) {
		app.AddLabel(k, v)
	}
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was leaning towards such an implementation too at some point, so I made the change. 👍

@timoreimann
Copy link
Contributor Author

Re: backend/frontend refactoring: I'd like to leave that for a separate PR as this one is already fairly big. Plus, we need to "builderize" the configuration in numerous places (server, kubernetes, etc.), so I'd rather do it in one focused wash (which will also make it easier to see how we need to design that builder).

Copy link
Contributor

@vdemeester vdemeester left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few comments but overall great work !
LGTM 🦁

func Stringp(s string) *string {
return &s
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

one \n too much 😝

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Begone!

},
},
desc: "simple application",
application: application(appPorts(80)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You seem to use appPorts(80) a lot, maybe it should be a default value in the application (and setting it only when you actually care about the value in your test) 👼

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should already only be setting it when I actually need it. (There are a number of application()-only invocations where I don't pass the appPorts builder function.)

I thought about setting default ports on the app and task but dropped the idea eventually: We have a lot of different cases where we sometimes need just the Marathon port and sometimes the task port; sometimes they need to be equal and sometimes they don't.

So if I added a default, I'd also need to override it explicitly sometimes. In the end, this was too intransparent for me, so I went down the slightly more explicit and but also more clear route.

label(types.LabelBackendLoadbalancerMethod, "drr"),
label(types.LabelBackendCircuitbreakerExpression, "NetworkErrorRatio() > 0.5"),
),
task: localhostTask(taskPorts(80)),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for taskPorts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above. :)

@timoreimann
Copy link
Contributor Author

Rebased and addressed all comments for now.

PTAL again.

Copy link
Contributor

@ldez ldez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@juliens juliens left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


// Functions related to building applications.

func createApplication(ops ...func(*marathon.Application)) marathon.Application {
Copy link
Contributor

@ldez ldez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I approve but I don't agree with the create prefix.

This change introduces the builder pattern to the Marathon unit tests in
order to simplify and reduce the amount of testing boilerplate.

Additional changes:

- Add missing unit tests.
- Make all tests look consistent.
- Use dedicated type for task states for increased type safety.
- Remove obsoleted getApplication function.
@timoreimann
Copy link
Contributor Author

I only added the prefix because I understood you asked for it. Will happily remove it again in another PR. :-)

@ldez ldez deleted the marathon-test-builder branch July 22, 2017 15:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants