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

Add a bunch of integration tests for Metric, Funnel, and MultiAnalysis #127

Merged
merged 21 commits into from
Nov 7, 2017

Conversation

baumatron
Copy link
Contributor

@baumatron baumatron commented Oct 20, 2017

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.

@coveralls
Copy link

Coverage Status

Coverage increased (+13.03%) to 70.355% when pulling 1e3fc36 on increase_coverage_2 into b820c2e on master.

@coveralls
Copy link

Coverage Status

Coverage increased (+13.1%) to 70.446% when pulling d29f8a7 on increase_coverage_2 into b820c2e on master.

Copy link
Contributor

@masojus masojus left a 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)
Copy link
Contributor

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?

Copy link
Contributor Author

@baumatron baumatron Nov 3, 2017

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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>());
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

From when I fixed Issue #50 in commit 70fba95 I'd expect it to get used, at least maybe when deserializing FunnelResultStep but I'd have to go back and look more carefully. Does it not get hit in the Funnel_ValidTimeframeInSteps_Success() test?

var actualQueries = await client.GetQueries();

Assert.AreEqual(expectedQueries.Count, actualQueries.Count());
foreach (var expectedQuery in expectedQueries)
Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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])));

Copy link
Contributor Author

@baumatron baumatron Nov 2, 2017

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());
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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()
Copy link
Contributor

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.

Copy link
Contributor Author

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.

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 there's an OrderedDictionary class or something similar if that's what we need, and then you'd still have O(1) lookup.

Copy link
Contributor Author

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())
Copy link
Contributor

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.....

Copy link
Contributor

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)
Copy link
Contributor

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 :/

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 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)
Copy link
Contributor

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?

Copy link
Contributor Author

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.

@coveralls
Copy link

Coverage Status

Coverage increased (+12.7%) to 70.054% when pulling b68f3dd on increase_coverage_2 into b820c2e on master.

@masojus
Copy link
Contributor

masojus commented Nov 6, 2017

Looks good to me, and after re-running, the Travis builds succeeded, so merge away!

@baumatron baumatron merged commit 676e52f into master Nov 7, 2017
@baumatron baumatron deleted the increase_coverage_2 branch November 7, 2017 00:50
This was referenced Nov 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants