You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tinkerpop.apache.org by jo...@apache.org on 2017/11/30 09:19:59 UTC

[15/33] tinkerpop git commit: Gherkin-based test runner

Gherkin-based test runner


Project: http://git-wip-us.apache.org/repos/asf/tinkerpop/repo
Commit: http://git-wip-us.apache.org/repos/asf/tinkerpop/commit/cee68591
Tree: http://git-wip-us.apache.org/repos/asf/tinkerpop/tree/cee68591
Diff: http://git-wip-us.apache.org/repos/asf/tinkerpop/diff/cee68591

Branch: refs/heads/TINKERPOP-1827
Commit: cee68591c944fb23f1439b471db531f2823a8f9f
Parents: fe7b175
Author: Jorge Bay Gondra <jo...@gmail.com>
Authored: Mon Sep 25 17:27:13 2017 +0200
Committer: Jorge Bay Gondra <jo...@gmail.com>
Committed: Thu Nov 30 10:00:07 2017 +0100

----------------------------------------------------------------------
 .../Gherkin/Attributes/BddAttribute.cs          |  37 ++
 .../Gherkin/Attributes/GivenAttribute.cs        |  33 ++
 .../Gherkin/Attributes/ThenAttribute.cs         |  33 ++
 .../Gherkin/Attributes/WhenAttribute.cs         |  33 ++
 .../Gherkin/CommonSteps.cs                      | 156 +++++++++
 .../Gherkin/GherkinTestRunner.cs                | 335 +++++++++++++++++++
 .../Gherkin/StepDefinition.cs                   |  39 +++
 .../Gherkin/TraversalTranslations.cs            | 102 ++++++
 .../Gremlin.Net.IntegrationTest.csproj          |  13 +-
 .../RemoteConnectionFactory.cs                  |  18 +-
 10 files changed, 790 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/BddAttribute.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/BddAttribute.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/BddAttribute.cs
new file mode 100644
index 0000000..1e9a242
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/BddAttribute.cs
@@ -0,0 +1,37 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+
+namespace Gremlin.Net.IntegrationTest.Gherkin.Attributes
+{
+    internal class BddAttribute : Attribute
+    {
+        public string Message { get; }
+
+        internal BddAttribute(string message)
+        {
+            Message = message;
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/GivenAttribute.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/GivenAttribute.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/GivenAttribute.cs
new file mode 100644
index 0000000..7266145
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/GivenAttribute.cs
@@ -0,0 +1,33 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+namespace Gremlin.Net.IntegrationTest.Gherkin.Attributes
+{
+    internal class GivenAttribute : BddAttribute
+    {
+        public GivenAttribute(string message) : base(message)
+        {
+
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/ThenAttribute.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/ThenAttribute.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/ThenAttribute.cs
new file mode 100644
index 0000000..25e1932
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/ThenAttribute.cs
@@ -0,0 +1,33 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+namespace Gremlin.Net.IntegrationTest.Gherkin.Attributes
+{
+    internal class ThenAttribute : BddAttribute
+    {
+        public ThenAttribute(string message) : base(message)
+        {
+
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/WhenAttribute.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/WhenAttribute.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/WhenAttribute.cs
new file mode 100644
index 0000000..26286c6
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/Attributes/WhenAttribute.cs
@@ -0,0 +1,33 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+namespace Gremlin.Net.IntegrationTest.Gherkin.Attributes
+{
+    internal class WhenAttribute : BddAttribute
+    {
+        public WhenAttribute(string message) : base(message)
+        {
+
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
new file mode 100644
index 0000000..50ea9b8
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/CommonSteps.cs
@@ -0,0 +1,156 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Headers;
+using System.Runtime.CompilerServices;
+using Gherkin.Ast;
+using Gremlin.Net.IntegrationTest.Gherkin.Attributes;
+using Gremlin.Net.Process.Traversal;
+using Gremlin.Net.Structure;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Xunit;
+
+namespace Gremlin.Net.IntegrationTest.Gherkin
+{
+    internal class GeneralDefinitions : StepDefinition
+    {
+        private GraphTraversalSource _g;
+        private dynamic _traversal;
+        private object[] _result;
+
+        private static readonly IDictionary<string, Func<GraphTraversalSource, ITraversal>> FixedTranslations = 
+            new Dictionary<string, Func<GraphTraversalSource, ITraversal>>
+            {
+                { "g.V().has(\"no\").count()", g => g.V().Has("no").Count() }
+            };
+        
+        [Given("the modern graph")]
+        public void ChooseModernGraph()
+        {
+            var connection = ConnectionFactory.CreateRemoteConnection();
+            _g = new Graph().Traversal().WithRemote(connection);
+        }
+
+        [When("iterated to list")]
+        public void IterateToList()
+        {
+            if (!(_traversal is ITraversal))
+            {
+                throw new InvalidOperationException("Traversal should be set before iterating");
+            }
+            IEnumerable enumerable = _traversal.ToList();
+            _result = enumerable.Cast<object>().ToArray();
+        }
+
+        [Given("the traversal of")]
+        public void TranslateTraversal(string traversalText)
+        {
+            if (_g == null)
+            {
+                throw new InvalidOperationException("g should be a traversal source");
+            }
+            _traversal = TraversalTranslations.GetTraversal(traversalText, _g);
+        }
+
+        [Then("the result should be (\\w+)")]
+        public void AssertResult(string characterizedAs, DataTable table)
+        {
+            TableRow[] rows;
+            switch (characterizedAs)
+            {
+                case "empty":
+                    Assert.Equal(0, _result.Length);
+                    return;
+                case "ordered":
+                    rows = table.Rows.ToArray();
+                    Assert.Equal(rows.Length, _result.Length);
+                    for (var i = 0; i < rows.Length; i++)
+                    {
+                        var row = rows[i];
+                        var cells = row.Cells.ToArray();
+                        var typeName = cells[0].Value;
+                        var expectedValue = ConvertExpectedToType(typeName, cells[1].Value);
+                        var resultItem = ConvertResultItem(typeName, _result[i]);
+                        Assert.Equal(expectedValue, resultItem);
+                    }
+                    break;
+                case "unordered":
+                    rows = table.Rows.ToArray();
+                    Assert.Equal(rows.Length, _result.Length);
+                    foreach (var row in rows)
+                    {
+                        var cells = row.Cells.ToArray();
+                        var typeName = cells[0].Value;
+                        var expectedValue = ConvertExpectedToType(typeName, cells[1].Value);
+                        // Convert all the values in the result to the type
+                        var convertedResult = _result.Select(item => ConvertResultItem(typeName, item));
+                        Assert.Contains(expectedValue, convertedResult);
+                    }
+                    break;
+                default:
+                    throw new NotSupportedException($"Result as '{characterizedAs}' not supported");
+            }
+        }
+
+        private object ConvertResultItem(string typeName, object value)
+        {
+            if (typeName == "map")
+            {
+                // We need to convert the original typed value into IDictionary<string, string>
+                return StringMap(
+                    Assert.IsAssignableFrom<IDictionary>(value));
+            }
+            return value;
+        }
+
+        private IDictionary<string, string> StringMap(IDictionary originalMap)
+        {
+            var result = new Dictionary<string, string>(originalMap.Count);
+            foreach (var key in originalMap.Keys)
+            {
+                result.Add(key.ToString(), originalMap[key]?.ToString());
+            }
+            return result;
+        }
+
+        private object ConvertExpectedToType(string typeName, string stringValue)
+        {
+            switch (typeName)
+            {
+                case "numeric":
+                    return Convert.ToInt64(stringValue);
+                case "string":
+                    return stringValue;
+                case "map":
+                    IDictionary<string, JToken> jsonObject = JObject.Parse(stringValue);
+                    return jsonObject.ToDictionary(item => item.Key, item => item.Value.ToString());
+            }
+            throw new NotSupportedException($"Data table result with subtype of {typeName} not supported");
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
new file mode 100644
index 0000000..e1df3a8
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/GherkinTestRunner.cs
@@ -0,0 +1,335 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text.RegularExpressions;
+using Xunit;
+using Gherkin;
+using Gherkin.Ast;
+using Gremlin.Net.IntegrationTest.Gherkin.Attributes;
+using Microsoft.VisualStudio.TestPlatform.Utilities;
+using Newtonsoft.Json.Serialization;
+using Xunit.Abstractions;
+using Xunit.Sdk;
+
+namespace Gremlin.Net.IntegrationTest.Gherkin
+{
+    public class GherkinTestRunner
+    {
+        private readonly ITestOutputHelper _output;
+
+        private static class Keywords
+        {
+            public const string Given = "GIVEN";
+            public const string And = "AND";
+            public const string But = "BUT";
+            public const string When = "WHEN";
+            public const string Then = "THEN";
+        }
+
+        public enum StepBlock
+        {
+            Given,
+            When,
+            Then
+        }
+        
+        private static readonly IDictionary<StepBlock, Type> Attributes = new Dictionary<StepBlock, Type>
+        {
+            { StepBlock.Given, typeof(GivenAttribute) },
+            { StepBlock.When, typeof(WhenAttribute) },
+            { StepBlock.Then, typeof(ThenAttribute) }
+        };
+
+        public GherkinTestRunner(ITestOutputHelper output)
+        {
+            _output = output;
+        }
+
+        [Fact]
+        public void RunGherkinBasedTests()
+        {
+            Console.WriteLine("Starting Gherkin-based tests");
+            var stepDefinitionTypes = GetStepDefinitionTypes();
+            var results = new List<ResultFeature>();
+            foreach (var feature in GetFeatures())
+            {
+                var resultFeature = new ResultFeature(feature);
+                results.Add(resultFeature);
+                foreach (var scenario in feature.Children)
+                {
+                    var failedSteps = new Dictionary<Step, Exception>();
+                    resultFeature.Scenarios[scenario] = failedSteps;
+                    StepBlock? currentStep = null;
+                    StepDefinition stepDefinition = null;
+                    foreach (var step in scenario.Steps)
+                    {
+                        var previousStep = currentStep;
+                        currentStep = GetStepBlock(currentStep, step.Keyword);
+                        if (currentStep == StepBlock.Given && previousStep != StepBlock.Given)
+                        {
+                            stepDefinition?.Dispose();
+                            stepDefinition = GetStepDefinitionInstance(stepDefinitionTypes, step.Text);
+                        }
+                        if (stepDefinition == null)
+                        {
+                            throw new NotSupportedException(
+                                $"Step '{step.Text} not supported without a 'Given' step first");
+                        }
+                        var result = ExecuteStep(stepDefinition, currentStep.Value, step);
+                        if (result != null)
+                        {
+                            failedSteps.Add(step, result);
+                        }
+                    }
+                }
+            }
+            OutputResults(results);
+            Console.WriteLine("Finished Gherkin-based tests");
+        }
+
+        private void WriteOutput(string line)
+        {
+            _output.WriteLine(line);
+        }
+
+        private void OutputResults(List<ResultFeature> results)
+        {
+            var totalScenarios = results.Sum(f => f.Scenarios.Count);
+            var totalFailedScenarios = results.Sum(f => f.Scenarios.Count(s => s.Value.Count > 0));
+            WriteOutput("Gherkin tests summary");
+            WriteOutput($"Total scenarios: {totalScenarios}. " +
+                              $"Passed: {totalScenarios-totalFailedScenarios}. Failed: {totalFailedScenarios}.");
+            if (totalFailedScenarios == 0)
+            {
+                return;
+            }
+            var identifier = 0;
+            var failures = new List<Exception>();
+            foreach (var resultFeature in results)
+            {
+                var failedScenarios = resultFeature.Scenarios.Where(s => s.Value.Count > 0).ToArray();
+                if (failedScenarios.Length > 0)
+                {
+                    WriteOutput($"Feature: {resultFeature.Feature.Name}");   
+                }
+                else
+                {
+                    continue;
+                }
+                foreach (var resultScenario in failedScenarios)
+                {
+                    WriteOutput($"  Scenario: {resultScenario.Key.Name}");
+                    foreach (var step in resultScenario.Key.Steps)
+                    {
+                        Exception failure;
+                        resultScenario.Value.TryGetValue(step, out failure);
+                        if (failure == null)
+                        {
+                            WriteOutput($"    {step.Keyword} {step.Text}");
+                        }
+                        else
+                        {
+                            WriteOutput($"    {++identifier}) {step.Keyword} {step.Text} (failed)");
+                            failures.Add(failure);
+                        }
+                    }
+                }
+            }
+            WriteOutput("Failures:");
+            for (var index = 0; index < failures.Count; index++)
+            {
+                WriteOutput($"{index+1}) {failures[index]}");
+            }
+            throw new Exception($"Gherkin test failed, see summary above for more detail");
+        }
+
+        public class ResultFeature
+        {
+            public Feature Feature { get;}
+
+            public IDictionary<ScenarioDefinition, IDictionary<Step, Exception>> Scenarios { get; }
+
+            public ResultFeature(Feature feature)
+            {
+                Feature = feature;
+                Scenarios = new Dictionary<ScenarioDefinition, IDictionary<Step, Exception>>();
+            }
+        }
+
+        private Exception ExecuteStep(StepDefinition instance, StepBlock stepBlock, Step step)
+        {
+            var attribute = Attributes[stepBlock];
+            var methodAndParameters = instance.GetType().GetMethods()
+                .Select(m =>
+                {
+                    var attr = (BddAttribute) m.GetCustomAttribute(attribute);
+                    if (attr == null)
+                    {
+                        return null;
+                    }
+                    var match = Regex.Match(step.Text, attr.Message);
+                    if (!match.Success)
+                    {
+                        return null;
+                    }
+                    var parameters = new List<object>();
+                    for (var i = 1; i < match.Groups.Count; i++)
+                    {
+                        parameters.Add(match.Groups[i].Value);
+                    }
+                    if (step.Argument is DocString)
+                    {
+                        parameters.Add(((DocString) step.Argument).Content);
+                    }
+                    else if (step.Argument != null)
+                    {
+                        parameters.Add(step.Argument);
+                    }
+                    if (m.GetParameters().Length != parameters.Count)
+                    {
+                        return null;
+                    }
+                    return Tuple.Create(m, parameters.ToArray());
+                })
+                .FirstOrDefault(t => t != null);
+            if (methodAndParameters == null)
+            {
+                throw new InvalidOperationException(
+                    $"There is no step definition method for {stepBlock} '{step.Text}'");
+            }
+            try
+            {
+                methodAndParameters.Item1.Invoke(instance, methodAndParameters.Item2);
+            }
+            catch (TargetInvocationException ex)
+            {
+                // Exceptions should not be thrown
+                // Should be captured for result
+                return ex.InnerException;
+            }
+            catch (Exception ex)
+            {
+                return ex;
+            }
+            // Success
+            return null;
+        }
+
+        private static StepBlock GetStepBlock(StepBlock? currentStep, string stepKeyword)
+        {
+            switch (stepKeyword.Trim().ToUpper())
+            {
+                case Keywords.Given:
+                    return StepBlock.Given;
+                case Keywords.When:
+                    return StepBlock.When;
+                case Keywords.Then:
+                    return StepBlock.Then;
+                case Keywords.And:
+                case Keywords.But:
+                    if (currentStep == null)
+                    {
+                        throw new InvalidOperationException("'And' or 'But' is not supported outside a step");
+                    }
+                    return currentStep.Value;
+            }
+            throw new NotSupportedException($"Step with keyword {stepKeyword} not supported");
+        }
+
+        private static StepDefinition GetStepDefinitionInstance(IEnumerable<Type> stepDefinitionTypes, string stepText)
+        {
+            var type = stepDefinitionTypes
+                .FirstOrDefault(t => t.GetMethods().Any(m =>
+                {
+                    var attr = m.GetCustomAttribute<GivenAttribute>();
+                    if (attr == null)
+                    {
+                        return false;
+                    }
+                    return Regex.IsMatch(stepText, attr.Message);
+                }));
+            if (type == null)
+            {
+                throw new InvalidOperationException($"No step definition class matches Given '{stepText}'");
+            }
+            return (StepDefinition) Activator.CreateInstance(type);
+        }
+
+        private ICollection<Type> GetStepDefinitionTypes()
+        {
+            var assembly = GetType().GetTypeInfo().Assembly;
+            var types = assembly.GetTypes()
+                .Where(t => typeof(StepDefinition).IsAssignableFrom(t) && !t.GetTypeInfo().IsAbstract)
+                .ToArray();
+            if (types.Length == 0)
+            {
+                throw new InvalidOperationException($"No step definitions in {assembly.FullName}");
+            }
+            return types;
+        }
+
+        private IEnumerable<Feature> GetFeatures()
+        {
+            // TODO: go through all the .feature files
+            const string gherkinFile = "/Users/jorge/workspace/temp/count.feature";
+            var parser = new Parser();
+            var doc = parser.Parse(gherkinFile);
+            yield return doc.Feature;
+        }
+
+        private void PrintGherkin()
+        {
+            var gherkinFile = "/Users/jorge/workspace/temp/count.feature";
+            var parser = new Parser();
+            GherkinDocument doc = parser.Parse(gherkinFile);
+            foreach (var scenario in doc.Feature.Children)
+            {
+                WriteOutput("--------");
+                WriteOutput("Scenario: " + scenario.Name);
+                foreach (var step in scenario.Steps)
+                {
+                    WriteOutput("  Step");
+                    WriteOutput("    Keyword: " + step.Keyword);
+                    WriteOutput("    Text: " + step.Text);
+                    WriteOutput("    Argument: " + step.Argument);
+                    if (step.Argument is DocString)
+                    {
+                        WriteOutput("      " + ((DocString)step.Argument).Content);
+                    }
+                    if (step.Argument is DataTable)
+                    {
+                        foreach (var row in ((DataTable)step.Argument).Rows)
+                        {
+                            WriteOutput("      Row: " + string.Join(", ", row.Cells.Select(x => x.Value)));   
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/StepDefinition.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/StepDefinition.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/StepDefinition.cs
new file mode 100644
index 0000000..3412fbd
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/StepDefinition.cs
@@ -0,0 +1,39 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.Dynamic;
+using Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection;
+
+namespace Gremlin.Net.IntegrationTest.Gherkin
+{
+    public abstract class StepDefinition : IDisposable
+    {
+        internal RemoteConnectionFactory ConnectionFactory = new RemoteConnectionFactory();
+
+        public virtual void Dispose()
+        {
+            ConnectionFactory.Dispose();
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalTranslations.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalTranslations.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalTranslations.cs
new file mode 100644
index 0000000..af02b8c
--- /dev/null
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gherkin/TraversalTranslations.cs
@@ -0,0 +1,102 @@
+#region License
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Gremlin.Net.Process.Traversal;
+
+namespace Gremlin.Net.IntegrationTest.Gherkin
+{
+    internal class TraversalTranslations
+    {
+        private static readonly IDictionary<string, Func<GraphTraversalSource, ITraversal>> FixedTranslations = 
+            new Dictionary<string, Func<GraphTraversalSource, ITraversal>>
+            {
+                { "g.V().has(\"no\").count()", g => g.V().Has("no").Count() },
+                { "g.V().fold().count(Scope.local)", g => g.V().Fold<object>().Count(Scope.Local)}
+            };
+
+        internal static ITraversal GetTraversal(string traversalText, GraphTraversalSource g)
+        {
+            Func<GraphTraversalSource, ITraversal> traversalBuilder;
+            if (!FixedTranslations.TryGetValue(traversalText, out traversalBuilder))
+            {
+                return BuildFromMethods(traversalText, g);
+            }
+            return traversalBuilder(g);
+        }
+
+        private static ITraversal BuildFromMethods(string traversalText, GraphTraversalSource g)
+        {
+            var parts = traversalText.Split('.');
+            if (parts[0] != "g")
+            {
+                throw BuildException(traversalText);
+            }
+            ITraversal traversal;
+            switch (parts[1])
+            {
+                case "V()":
+                    traversal = g.V();
+                    break;
+                case "E()":
+                    traversal = g.E();
+                    break;
+                default:
+                    throw BuildException(traversalText);
+            }
+            for (var i = 2; i < parts.Length; i++)
+            {
+                var name = GetCsharpName(parts[i], traversalText);
+                var method = traversal.GetType().GetMethod(name);
+                if (method == null)
+                {
+                    throw new InvalidOperationException($"Traversal method '{parts[i]}' not found for testing");
+                }
+                if (method.IsGenericMethod)
+                {
+                    throw new InvalidOperationException(
+                        $"Can not build traversal to test as '{name}()' method is generic");
+                }
+                traversal = (ITraversal) method.Invoke(traversal, new object[] { new object[0]});
+            }
+            return traversal;
+        }
+
+        private static string GetCsharpName(string part, string traversalText)
+        {
+            if (!part.EndsWith("()"))
+            {
+                throw BuildException(traversalText);
+            }
+            // Transform to PascalCasing and remove the parenthesis
+            return char.ToUpper(part[0]) + part.Substring(1, part.Length - 3);
+        }
+
+        private static Exception BuildException(string traversalText)
+        {
+            return new InvalidOperationException($"Can not build a traversal to test from '{traversalText}'");
+        }
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
index c5e923d..40552ad 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Gremlin.Net.IntegrationTest.csproj
@@ -1,5 +1,4 @@
 <Project Sdk="Microsoft.NET.Sdk">
-
   <PropertyGroup>
     <TargetFramework>netcoreapp1.0</TargetFramework>
     <DebugType>portable</DebugType>
@@ -10,17 +9,14 @@
     <SignAssembly>true</SignAssembly>
     <PublicSign Condition="'$(OS)' != 'Windows_NT'">true</PublicSign>
   </PropertyGroup>
-
   <ItemGroup>
     <None Update="appsettings.json">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
     </None>
   </ItemGroup>
-
   <ItemGroup>
     <ProjectReference Include="..\..\src\Gremlin.Net\Gremlin.Net.csproj" />
   </ItemGroup>
-
   <ItemGroup>
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.3.0" />
     <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
@@ -31,9 +27,12 @@
     <PackageReference Include="OpenCover" Version="4.6.519" />
     <PackageReference Include="coveralls.io" Version="1.3.4" />
   </ItemGroup>
-
   <ItemGroup>
     <Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
   </ItemGroup>
-
-</Project>
+  <ItemGroup>
+    <Reference Include="Gherkin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
+      <HintPath>..\..\..\..\temp\Gherkin.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+</Project>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/tinkerpop/blob/cee68591/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs
----------------------------------------------------------------------
diff --git a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs
index 47d4f06..249db60 100644
--- a/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs
+++ b/gremlin-dotnet/test/Gremlin.Net.IntegrationTest/Process/Traversal/DriverRemoteConnection/RemoteConnectionFactory.cs
@@ -22,16 +22,20 @@
 #endregion
 
 using System;
+using System.Collections.Generic;
 using Gremlin.Net.Driver;
 using Gremlin.Net.Process.Remote;
+using DriverRemoteConnectionImpl = Gremlin.Net.Driver.Remote.DriverRemoteConnection;
 
 namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
 {
-    internal class RemoteConnectionFactory
+    internal class RemoteConnectionFactory : IDisposable
     {
         private static readonly string TestHost = ConfigProvider.Configuration["TestServerIpAddress"];
         private static readonly int TestPort = Convert.ToInt32(ConfigProvider.Configuration["TestServerPort"]);
 
+        private readonly IList<DriverRemoteConnectionImpl> _connections = new List<DriverRemoteConnectionImpl>();
+
         public IRemoteConnection CreateRemoteConnection()
         {
             // gmodern is the standard test traversalsource that the main body of test uses
@@ -40,8 +44,18 @@ namespace Gremlin.Net.IntegrationTest.Process.Traversal.DriverRemoteConnection
 
         public IRemoteConnection CreateRemoteConnection(string traversalSource)
         {
-            return new Net.Driver.Remote.DriverRemoteConnection(
+            var c = new DriverRemoteConnectionImpl(
                 new GremlinClient(new GremlinServer(TestHost, TestPort)), traversalSource);
+            _connections.Add(c);
+            return c;
+        }
+
+        public void Dispose()
+        {
+            foreach (var connection in _connections)
+            {
+                connection.Dispose();
+            }
         }
     }
 }
\ No newline at end of file