-
Notifications
You must be signed in to change notification settings - Fork 23
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
Add a bunch of integration tests for Metric, Funnel, and MultiAnalysis #127
Conversation
Also make tests indifferent to order of query string parameters
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks awesome! I left comments, none of which are major enough to block merging this, so use your own discretion and file issues for stuff you deem out of scope for this change.
@@ -312,18 +312,28 @@ internal class Queries : IQueries | |||
return o; | |||
} | |||
|
|||
public async Task<IDictionary<string, string>> MultiAnalysis(string collection, IEnumerable<MultiAnalysisParam> analysisParams, IQueryTimeframe timeframe = null, IEnumerable<QueryFilter> filters = null, string timezone = "") | |||
string SerializeMultiAnalysisQueryParameter(IEnumerable<MultiAnalysisParam> analysisParams) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe ...Params
or ...Parameters
plural since it's a collection of them?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Parameter
here is referring to the analyses
parameter. In context it seems to make sense:
parms.Add(KeenConstants.QueryParmAnalyses, SerializeMultiAnalysisQueryParameter(analysisParams));
I'll have a look though and see if I can come up with a better name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I hate the abbreviation parms
. We should change that someday.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, yeah that makes sense. I don't like parms
either--I'd always rather spell it out or go with params
which seems like a more commonly accepted convention. If I ctr+shift+F I'll always try "param" and then I'll find either param
or params
or parameter
/parameters
but not parms
.
@@ -46,7 +46,7 @@ public override void WriteJson(JsonWriter writer, object value, JsonSerializer s | |||
// absolute timeframe. | |||
if (JTokenType.String == jsonToken.Type) | |||
{ | |||
return QueryRelativeTimeframe.Create(jsonToken.ToString(Formatting.None)); | |||
return QueryRelativeTimeframe.Create(jsonToken.Value<string>()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you happen to test this against the real back end?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was a while ago that I made this change, but I think so.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, I highly doubt this is actually used anywhere in the SDK for deserialization. What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
var actualQueries = await client.GetQueries(); | ||
|
||
Assert.AreEqual(expectedQueries.Count, actualQueries.Count()); | ||
foreach (var expectedQuery in expectedQueries) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could maybe do something like AssertSequenceEquals()
here or something based on LINQ's SequenceEqual()
, the stuff at MoreLINQ will give better failure messages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should also be making use of either the classic model CollectionAssert helpers or the newer Collection Constraints stuff where applicable. In general we don't use much of the constraints-based stuff in these tests, but now that we're on a newer NUnit, we may as well use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems way better, I'll change that throughout.
{ | ||
var parameters = queryParameters.GetQueryParameters(); | ||
StringBuilder queryStringBuilder = new StringBuilder(); | ||
foreach (var parameter in parameters) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this better or worse than what we do elsewhere:
var parmVals = (parms == null) ?
"" : string.Join("&", from p in parms.Keys
where !string.IsNullOrEmpty(parms[p])
select string.Format("{0}={1}",
p,
Uri.EscapeDataString(parms[p])));
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really like either that much, but I found another way to do what I want with something like this:
// Create an empty NameValueCollection
var expectedQueryStringCollection = new NameValueCollection();
// Add all expected parameters to the collection
foreach (var parameter in queryParameters.GetQueryParameters())
{
if (null != parameter.Value)
{
expectedQueryStringCollection[parameter.Key] = parameter.Value;
}
}
which seems more readable and isn't creating the superfluous query string to then be parsed later.
|
||
Assert.AreEqual(expectedPath, actualPath); | ||
|
||
var expectedQueryStringCollection = HttpUtility.ParseQueryString(queryStringBuilder.ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there good reason to build a StringBuilder
and then parse it rather than directly build up a NameValueCollection
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not anymore, I've changed this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great, thanks!
|
||
internal virtual string GetResourceName() => Analysis; | ||
|
||
internal virtual List<KeyValuePair<string, string>> GetQueryParameters() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why a list of KVPs instead of a Dictionary<>
? It seems like it just makes the notation more verbose.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initially I was relying on the keys being in a particular order. Can probably change it to a Dictionary now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's an OrderedDictionary
class or something similar if that's what we need, and then you'd still have O(1) lookup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No need for having it ordered anymore as I've changed the tests to more properly ignore any difference in ordering of query string parameters.
Assert.AreEqual(expectedResults.Count(), actualResults.Count()); | ||
var expectedEnumerator = expectedResults.GetEnumerator(); | ||
var actualEnumerator = actualResults.GetEnumerator(); | ||
while (expectedEnumerator.MoveNext() && actualEnumerator.MoveNext()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there some form of Zip()
and/or All()
to avoid grabbing the actual enumerators? Something like expectedResults.Zip(actualResults, (expected, actual) => Assert.AreEqual(expected.timeframe.Start....
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also if we implemented equality for some of these types (like QueryRelativeTimeframe
here) these sorts of checks would be easier, which I've been waiting for a reason to do anyway. Maybe file an issue?
|
||
Assert.AreEqual(expectedResults.Count(), actualResults.Result.Count()); | ||
var actualEnumerator = actualResults.Result.GetEnumerator(); | ||
foreach (var expected in expectedResults) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sort of iteration implies ordering...do we have reason to expect ordering will always be maintained? We saw that in a version to version change in Java where a particular collection was optimized and no longer maintained a certain ordering that some of the tests relied on, so it succeeded in Java 6 and 7 but broke when we tested on JDK 8 :/
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually an ordered collection, so this should be fine.
|
||
Assert.AreEqual(expectedResults.Count(), actualResults.Count()); | ||
var actualResultsEnumerator = actualResults.GetEnumerator(); | ||
foreach (var expectedResult in expectedResults) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can JToken.DeepEquals()
help in these cases so as to not manually delve into the entire JSON structure, or is it too rigid in that we want to ignore some extraneous properties or something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe, but we'll have to write just as much code probably to transform the actual result into something that will actually be comparable. For example, the SDK returns results as strings while the JSON returned is a number.
Looks good to me, and after re-running, the Travis builds succeeded, so merge away! |
This tests a bunch of code that previously wasn't tested in automation. For this round I focused on getting all types of queries tested with some basic tests. More tests still need to be added to cover other parts of the SDK, but this was a major area that needed work. Takes code coverage from 57% to 70%.
It's interesting that some of the abstractions that came around like QueryParameters reflect some of the ideas that have been discussed around reshaping the SDK surface by getting rid of all of the different overloads of Metric, etc. I'd like to see something even more generic, and I'd bet we can reduce Queries.cs to possibly even a single generic method that takes a QueryParameters type object and uses Json.Net to deserialize results into concrete types where we use JsonConverters where those types need to be transformed in some way.