-
Notifications
You must be signed in to change notification settings - Fork 5
C# test suite PoC #50
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
base: main
Are you sure you want to change the base?
Changes from all commits
e06b5dc
ce6c4b6
d25aa4c
e7026c3
250229d
1c77041
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
// :replace-start: { | ||
// "terms": { | ||
// "_order": "order", | ||
// "_aggDb": "aggDb" | ||
// } | ||
// } | ||
|
||
namespace Examples.Aggregation; | ||
|
||
using MongoDB.Driver; | ||
using MongoDB.Bson; | ||
|
||
public class GroupTotal | ||
{ | ||
private IMongoDatabase? _aggDB; | ||
private IMongoCollection<Order>? _orders; | ||
|
||
public void LoadSampleData() | ||
{ | ||
var uri = DotNetEnv.Env.GetString("CONNECTION_STRING", "Env variable not found. Verify you have a .env file with a valid connection string."); | ||
// :snippet-start: connection-string | ||
// :uncomment-start: | ||
//var uri = "mongodb+srv://mongodb-example:27017"; | ||
// :uncomment-end: | ||
// :snippet-end: | ||
var client = new MongoClient(uri); | ||
_aggDB = client.GetDatabase("agg_tutorials_db"); | ||
// :snippet-start: add-sample-data | ||
_orders = _aggDB.GetCollection<Order>("orders"); | ||
_orders.DeleteMany(Builders<Order>.Filter.Empty); | ||
|
||
_orders.InsertMany(new List<Order> | ||
{ | ||
new Order | ||
{ | ||
CustomerId = "[email protected]", | ||
OrderDate = DateTime.Parse("2020-05-30T08:35:52Z"), | ||
Value = 231 | ||
}, | ||
new Order | ||
{ | ||
CustomerId = "[email protected]", | ||
OrderDate = DateTime.Parse("2020-01-13T09:32:07Z"), | ||
Value = 99 | ||
}, | ||
new Order | ||
{ | ||
CustomerId = "[email protected]", | ||
OrderDate = DateTime.Parse("2020-01-01T08:25:37Z"), | ||
Value = 63 | ||
}, | ||
new Order | ||
{ | ||
CustomerId = "[email protected]", | ||
OrderDate = DateTime.Parse("2019-05-28T19:13:32Z"), | ||
Value = 2 | ||
}, | ||
new Order | ||
{ | ||
CustomerId = "[email protected]", | ||
OrderDate = DateTime.Parse("2020-11-23T22:56:53Z"), | ||
Value = 187 | ||
}, | ||
new Order | ||
{ | ||
CustomerId = "[email protected]", | ||
OrderDate = DateTime.Parse("2020-08-18T23:04:48Z"), | ||
Value = 4 | ||
}, | ||
new Order | ||
{ | ||
CustomerId = "[email protected]", | ||
OrderDate = DateTime.Parse("2020-12-26T08:55:46Z"), | ||
Value = 4 | ||
}, | ||
new Order | ||
{ | ||
CustomerId = "[email protected]", | ||
OrderDate = DateTime.Parse("2021-02-28T07:49:32Z"), | ||
Value = 1024 | ||
}, | ||
new Order | ||
{ | ||
CustomerId = "[email protected]", | ||
OrderDate = DateTime.Parse("2020-10-03T13:49:44Z"), | ||
Value = 102 | ||
} | ||
}); | ||
// :snippet-end: | ||
} | ||
|
||
public List<BsonDocument> PerformAggregation() | ||
{ | ||
if (_aggDB == null || _orders == null) | ||
{ | ||
throw new InvalidOperationException("You must call LoadSampleData before performing aggregation."); | ||
} | ||
|
||
// :snippet-start: match | ||
var results = _orders.Aggregate() | ||
.Match(o => o.OrderDate >= DateTime.Parse("2020-01-01T00:00:00Z") && o.OrderDate < DateTime.Parse("2021-01-01T00:00:00Z")) | ||
// :snippet-end: | ||
// :snippet-start: sort-order-date | ||
.SortBy(o => o.OrderDate) | ||
// :snippet-end: | ||
// :snippet-start: group | ||
.Group( | ||
o => o.CustomerId, | ||
g => new | ||
{ | ||
CustomerId = g.Key, | ||
FirstPurchaseDate = g.First().OrderDate, | ||
TotalValue = g.Sum(i => i.Value), | ||
TotalOrders = g.Count(), | ||
Orders = g.Select(i => new { i.OrderDate, i.Value }).ToList() | ||
} | ||
) | ||
// :snippet-end: | ||
// :snippet-start: sort-first-order | ||
.SortBy(c => c.FirstPurchaseDate) | ||
.As<BsonDocument>(); | ||
// :snippet-end: | ||
|
||
foreach (var result in results.ToList()) | ||
{ | ||
Console.WriteLine(result); | ||
} | ||
|
||
return results.ToList(); | ||
} | ||
} | ||
// :replace-end: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ "CustomerId" : "[email protected]", "FirstPurchaseDate" : { "$date" : "2020-01-01T08:25:37Z" }, "TotalValue" : 63, "TotalOrders" : 1, "Orders" : [{ "OrderDate" : { "$date" : "2020-01-01T08:25:37Z" }, "Value" : 63 }] } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know how this is used...should it be pretty-formatted? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just pulling it in from the docs page where it's currently used. We don't pretty format it there, so I kept the existing formatting. (Presumably because pretty formatting it would make it much longer, and that would take up a lot of real estate on the page, but I'm making an assumption.) |
||
{ "CustomerId" : "[email protected]", "FirstPurchaseDate" : { "$date" : "2020-01-13T09:32:07Z" }, "TotalValue" : 436, "TotalOrders" : 4, "Orders" : [{ "OrderDate" : { "$date" : "2020-01-13T09:32:07Z" }, "Value" : 99 }, { "OrderDate" : { "$date" : "2020-05-30T08:35:52Z" }, "Value" : 231 }, { "OrderDate" : { "$date" : "2020-10-03T13:49:44Z" }, "Value" : 102 }, { "OrderDate" : { "$date" : "2020-12-26T08:55:46Z" }, "Value" : 4 }] } | ||
{ "CustomerId" : "[email protected]", "FirstPurchaseDate" : { "$date" : "2020-08-18T23:04:48Z" }, "TotalValue" : 191, "TotalOrders" : 2, "Orders" : [{ "OrderDate" : { "$date" : "2020-08-18T23:04:48Z" }, "Value" : 4 }, { "OrderDate" : { "$date" : "2020-11-23T22:56:53Z" }, "Value" : 187 }] } |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
using MongoDB.Bson; | ||
using MongoDB.Bson.Serialization.Attributes; | ||
|
||
namespace Examples.Aggregation; | ||
|
||
// :snippet-start: class-declaration | ||
public class Order | ||
{ | ||
[BsonId] | ||
public ObjectId Id { get; set; } | ||
public string CustomerId { get; set; } | ||
public DateTime OrderDate { get; set; } | ||
public int Value { get; set; } | ||
} | ||
// :snippet-end: |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<OutputType>Exe</OutputType> | ||
<TargetFramework>net9.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="DotNetEnv" Version="3.1.1" /> | ||
<PackageReference Include="MongoDB.Driver" Version="3.4.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Console.WriteLine("This project is not intended to be run as a console application. Run the tests using the 'Tests' solution."); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
# C# Driver Code Example Test PoC | ||
|
||
This is a PoC to explore testing C# Driver code examples for MongoDB documentation. | ||
|
||
The structure of this C# project is as follows: | ||
|
||
- `driver.sln`: This solution contains the following projects: | ||
- `Examples`: This project contains example code and output to validate. | ||
- `Tests`: This project contains the test infrastructure to actually run | ||
the tests by invoking the example code. | ||
|
||
## To run the tests locally | ||
|
||
### Create a MongoDB Docker Deployment | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? I've never used a Docker instance to test C# projects. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's a holdover from the other test suites - not specific to C#. But my recommendation is for us to test things locally unless Atlas is required, as local testing doesn't require spinning up cloud infrastructure. |
||
|
||
To run these tests locally, you need a MongoDB Docker deployment. Make sure Docker is | ||
running, and then: | ||
|
||
1. Pull the MongoDB image from Docker Hub: | ||
|
||
```shell | ||
docker pull mongo | ||
``` | ||
2. Run the container: | ||
|
||
```shell | ||
docker run --name mongodb-test -d -p 27017:27017 mongo | ||
``` | ||
|
||
3. Verify the container is running: | ||
|
||
```shell | ||
docker ps | ||
``` | ||
|
||
The output resembles: | ||
|
||
```text | ||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES | ||
ef70cce38f26 mongo "/usr/local/bin/runn…" 29 hours ago Up 29 hours (healthy) 127.0.0.1:63201->27017/tcp mongodb-test | ||
``` | ||
|
||
You may note the actual port is different than `27017`, if something else is already running on | ||
`27017` on your machine. Note the port next to the IP address for running the tests. Alternately, you can get just | ||
the port info for your container using the following command: | ||
|
||
```shell | ||
docker port mongodb-test | ||
``` | ||
|
||
The output resembles: | ||
|
||
```text | ||
27017/tcp -> 0.0.0.0:27017 | ||
27017/tcp -> [::]:27017 | ||
``` | ||
|
||
### Create a .env file | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Depending on my initial comment about using the 3-party library, this may change. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, good point. I've changed the way I'm loading this so it's now being used properly to load .env vars at the beginning of the test suite run instead of having to read the file over and over again in various places to get the vars. I think the third-party lib is justifiable for env var management, and according to NuGet, this one has 12 million users. I think it's probably safe in this case to assume it's reasonably maintained. |
||
|
||
Create a file named `.env` at the root of the `/driver` directory. | ||
Add the following values to your .env file, similar to the following example: | ||
|
||
``` | ||
CONNECTION_STRING="mongodb://localhost:27017" | ||
SOLUTION_ROOT="/Users/dachary.carey/workspace/docs-code-examples/usage-examples/csharp/driver/" | ||
``` | ||
|
||
- `CONNECTION_STRING`: replace the port with the port where your local deployment is running. | ||
- `SOLUTION_ROOT`: insert the path to the `driver` directory on your filesystem. | ||
|
||
### Install the dependencies | ||
|
||
This test suite requires you to have `.NET` v9.0 installed. If you | ||
do not yet have .NET installed, refer to | ||
[the .NET installation page](https://learn.microsoft.com/en-us/dotnet/core/install/macos) | ||
for details. | ||
|
||
From the root of each project directory, run the following command to install | ||
dependencies: | ||
|
||
``` | ||
dotnet restore | ||
``` | ||
|
||
### Run the tests | ||
|
||
You can run tests from the command line or through your IDE. | ||
|
||
#### Run All Tests from the command line | ||
|
||
From the `/drivers` directory, run: | ||
|
||
``` | ||
dotnet test | ||
``` | ||
|
||
#### Run Individual Tests from the command line | ||
|
||
You can run a single test within a given test suite (file). | ||
|
||
From the `/drivers` directory, run: | ||
|
||
``` | ||
dotnet test --filter "FullyQualifiedName=YourNamespace.YourTestClass.YourTestMethod" | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using Examples.Aggregation; | ||
|
||
namespace Tests; | ||
|
||
public class GroupTotalTest | ||
{ | ||
private GroupTotal _example; | ||
[SetUp] | ||
public void Setup() | ||
{ | ||
_example = new GroupTotal(); | ||
_example.LoadSampleData(); | ||
} | ||
|
||
[Test] | ||
public void TestOutputMatchesDocs() | ||
{ | ||
var results = _example.PerformAggregation(); | ||
|
||
var solutionRoot = DotNetEnv.Env.GetString("SOLUTION_ROOT", "Env variable not found. Verify you have a .env file with a valid connection string."); | ||
var outputLocation = "Examples/Aggregation/GroupTotalOutput.txt"; | ||
var fullPath = Path.Combine(solutionRoot, outputLocation); | ||
var fileData = TestUtils.ReadBsonDocumentsFromFile(fullPath); | ||
|
||
Assert.That(results.Count, Is.EqualTo(fileData.Length), $"Result count {results.Count} does not match output example length {fileData.Length}."); | ||
for (var i = 0; i < fileData.Length; i++) | ||
{ | ||
Assert.That(fileData[i], Is.EqualTo(results[i]), $"Mismatch at index {i}: expected {fileData[i]}, got {results[i]}."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ooo you did it here. Nice! |
||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
namespace Tests; | ||
|
||
using DotNetEnv; | ||
|
||
[SetUpFixture] | ||
public class TestSuiteSetup | ||
{ | ||
[OneTimeSetUp] | ||
public void GlobalSetup() | ||
{ | ||
Env.TraversePath().Load(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
using MongoDB.Bson; | ||
|
||
public static class TestUtils | ||
{ | ||
public static BsonDocument[] ReadBsonDocumentsFromFile(string filePath) | ||
{ | ||
var bsonDocuments = new List<BsonDocument>(); | ||
|
||
foreach (var line in File.ReadLines(filePath)) | ||
{ | ||
if (!string.IsNullOrWhiteSpace(line)) | ||
{ | ||
var bsonDoc = BsonDocument.Parse(line); | ||
bsonDocuments.Add(bsonDoc); | ||
} | ||
} | ||
|
||
return bsonDocuments.ToArray(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net9.0</TargetFramework> | ||
<LangVersion>latest</LangVersion> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
<IsPackable>false</IsPackable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="coverlet.collector" Version="6.0.2"/> | ||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"/> | ||
<PackageReference Include="NUnit" Version="4.2.2"/> | ||
<PackageReference Include="NUnit.Analyzers" Version="4.3.0"/> | ||
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"/> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<Using Include="NUnit.Framework"/> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\Examples\Examples.csproj" /> | ||
</ItemGroup> | ||
|
||
</Project> |
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.
Idiomatic C# doesn't use
_variable
except in private getters/setters. Also, it's recommended that variables names are more explicit. In this case, I'd go withsampleDatabase
oraggregationDatabase
or similar.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.
same as all cases below this, too.
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.
FWIW, the DB name comes from an existing code example. This is the DB name used across 5 pages across all of the programming languages, so changing it would be non-trivial and out of scope of this PR.