-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
CSHARP-5338: Improve integration test performance by test fixtures #1332
base: main
Are you sure you want to change the base?
CSHARP-5338: Improve integration test performance by test fixtures #1332
Conversation
|
||
protected TFixture Fixture { get; } | ||
|
||
protected void AssertStages(IEnumerable<BsonDocument> stages, params string[] expectedStages) |
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.
All Assert-
and Translate-
methods were simply copied from existing Linq3IntegrationTest
base class (which probably has to be removed)
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.
A lot of these Translate
methods only apply to LINQ integration tests, does it matter that they are in a more general purpose class now called IntegrationTest
when not all integration tests use them?
We could derive Linq3IntegrationTest
from IntegrationTest
to keep the LINQ specific methods contained.
Or we could just decide it's OK for them to be bundled here. In which case the Linq3IntegrationTest
class could be removed once nothing is using it any more.
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 like the idea to derive Linq3IntegrationTest
from IntegrationTest
. Will check if we can do it now, or should do it as the very last step of the refactoring if there are any conflicts.
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've decided to introduce new LinqIntegrationTest
base class.
For 2 reasons:
- we will be using the class to test both V3 and V2 LinqProvider (as we mostly doing)
- In such way we can gradually update tests and once done - we will remove 'old' base classes.
using MongoDB.TestHelpers.XunitExtensions; | ||
using Xunit; | ||
|
||
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira | ||
{ | ||
public class CSharp702Tests : Linq3IntegrationTest | ||
public class CSharp702Tests : IntegrationTest<CSharp702Tests.TestDataFixture> |
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.
The main difference with comparing to the current test class structure:
- Test class should be inherited from
IntegrationTest
base class - For typical test class that uses only 1 set of data we should create TestDataFixture as nested class and provide it as a type parameter for base IntegrationTest
- Test class should have ctor to pass the Fixture created by xUnit infrastructure
- Test collection is available through the Fixture property
- Test data should be moved from GetCollection method to TestDataFixture
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 fix the class name while we're at it? Doesn't match the filename.
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.
Class was renamed.
protected TFixture Fixture { get; } | ||
|
||
protected void AssertStages(IEnumerable<BsonDocument> stages, params string[] expectedStages) | ||
{ |
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.
BTW, we already have 2 sets of such methods: in Linq3IntegrationTest
class and also in Linq3TestHelpers
static class. We probably ought to standardize the approach and have either static helper class or use base IntegrationTest
class.
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.
Linq3TestHelpers
is a very old class that is not used in any new tests, but hasn't been refactored out yet of older classes. Once we refactor to no longer use Linq3TestHelpers
it can definitely be deleted.
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'll try to consolidate all usages into the just introduced LinqIntegrationTest base class and remove "old" methods.
|
||
protected abstract IEnumerable<TModel> GetInitialData(); | ||
|
||
protected virtual string CollectionName => _collectionName.Value; |
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.
Default collection name would be taken from the parent class name, as I suggest to have fixture as a nested class of the test class.
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 like a great improvement to our tests. Waiting to see how faster our tests get when the rest of the tests are migrated.
} | ||
} | ||
|
||
public IMongoClient MongoDbClient => DriverTestConfiguration.Client; |
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 create own disposable MongoClient
?
But then it's better to be shared across the tests.
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.
Done. Client(s) will be created per fixture and will be disposed together with fixture. See TemporaryDatabaseFixture class for implementation.
} | ||
} | ||
|
||
public abstract class IntegrationTest : IntegrationTest<TemporaryDatabaseFixture> |
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 one is not needed?
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.
It will be used for tests that uses TemporaryDatabaseFixture
instead of TemporaryCollectionFixture
. I'll show an example in next commit.
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.
Please find example of usage in CSharp4066Tests
test class.
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 a better naming to reflect that. And then why not having matching class for TemporaryCollectionFixture
?
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've removed the class for now to avoid confusions. We might consider to re-introduce it later.
|
||
namespace MongoDB.Driver.TestHelpers | ||
{ | ||
public abstract class TemporaryCollectionFixture<TModel> : TemporaryDatabaseFixture |
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 might need to readjust the naming, as more tests are migrated to use this approach. Something like ReadOnlyCollectionFixture...
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.
Overall I think this approach is great! Nice!
|
||
protected TFixture Fixture { get; } | ||
|
||
protected void AssertStages(IEnumerable<BsonDocument> stages, params string[] expectedStages) |
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.
A lot of these Translate
methods only apply to LINQ integration tests, does it matter that they are in a more general purpose class now called IntegrationTest
when not all integration tests use them?
We could derive Linq3IntegrationTest
from IntegrationTest
to keep the LINQ specific methods contained.
Or we could just decide it's OK for them to be bundled here. In which case the Linq3IntegrationTest
class could be removed once nothing is using it any more.
protected TFixture Fixture { get; } | ||
|
||
protected void AssertStages(IEnumerable<BsonDocument> stages, params string[] expectedStages) | ||
{ |
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.
Linq3TestHelpers
is a very old class that is not used in any new tests, but hasn't been refactored out yet of older classes. Once we refactor to no longer use Linq3TestHelpers
it can definitely be deleted.
@@ -48,7 +54,7 @@ public class CSharp702Tests : Linq3IntegrationTest | |||
public void Query2_using_list_should_work( | |||
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider) | |||
{ | |||
var collection = GetCollection(linqProvider); | |||
var collection = Fixture.GetCollection<Model>(linqProvider); |
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 not just:
var collection = Fixture.GetCollection(linqProvider);
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 guess it's because there is a contradiction between TModel
and BsonDocument
in GetInitialData
.
There is probably a better way to solve that contradiction (see GetRawInitialData
proposal).
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.
Yes, in this particular test we are inserting the data using one model, but want to read the data as another.
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've decided to make InitializeCollection virtual, so derived fixtures are free to use any initialization logic.
} | ||
|
||
private class Profile | ||
public class ProfileModel |
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.
Do you remember why you renamed the class?
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.
The name conflict with some namespace. I did not investigate this deep, just renamed the model to get rid of conflict.
private static readonly string __timeStamp = DateTime.Now.ToString("yyyyMMdd-HHmmss"); | ||
|
||
protected readonly string _databaseName; | ||
private readonly ConcurrentBag<string> _createdCollections = new(); |
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.
Rename field _createdCollectionNames
because we are only storing the names, not the collections.
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.
Done
} | ||
} | ||
|
||
public IMongoClient MongoDbClient => DriverTestConfiguration.Client; |
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 would remove this property.
We want to encourage using GetClient(linqProvider)
and not using this property by mistake.
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.
Done
using MongoDB.TestHelpers.XunitExtensions; | ||
using Xunit; | ||
|
||
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira | ||
{ | ||
public class CSharp702Tests : Linq3IntegrationTest | ||
public class CSharp702Tests : IntegrationTest<CSharp702Tests.TestDataFixture> |
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 fix the class name while we're at it? Doesn't match the filename.
private class Model | ||
{ | ||
public int Id { get; set; } | ||
public List<string> List { get; set; } | ||
public HashSet<string> HashSet { get; set; } | ||
} | ||
|
||
public class TestDataFixture : TemporaryCollectionFixture<BsonDocument> |
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.
public class CollectionFixture : TemporaryCollectionFixture<MyModel>
I would name this class CollectionFixture
rather than TestDataFixture
. To me the most important thing is that it is a "collection fixture". Initializing the test data is a secondary function.
Or Model
if you don't get the same naming conflict with Amazon,SecurityServices
that I was getting.
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.
Done
|
||
public class TestDataFixture : TemporaryCollectionFixture<BsonDocument> | ||
{ | ||
protected override IEnumerable<BsonDocument> GetInitialData() |
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.
GetRawInitialData
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.
InitializeCollection is virtual now as per your another suggestion.
public IntegrationTestAttribute() | ||
: base("Integration") | ||
{ | ||
|
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.
minor: remove empty line
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.
Done
} | ||
} | ||
|
||
public abstract class IntegrationTest : IntegrationTest<TemporaryDatabaseFixture> |
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 a better naming to reflect that. And then why not having matching class for TemporaryCollectionFixture
?
} | ||
|
||
public IMongoClient GetClient(LinqProvider provider) | ||
=> DriverTestConfiguration.GetLinqClient(provider); |
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.
The question regarding the client still remains. Do we want to use this opportunity to start working with disposable clients?
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.
Done. See TemporaryDatabaseFixture
for implementation.
{ | ||
database.DropCollection(collection); | ||
} | ||
} |
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 not to drop the database instead?
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.
Currently it would be almost the same, but if we approach running in parallel task - then it will be safer to remove the collection as database might be in use by other tests/fixtures.
if (string.IsNullOrEmpty(collectionName)) | ||
{ | ||
var stack = new System.Diagnostics.StackTrace(); | ||
var frame = stack.GetFrame(1); // skip 1 frame to get the calling method info |
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.
Nice,
I wanted to suggest [CallerMemberName]
, but we having type name is better.
namespace MongoDB.Driver.Tests | ||
{ | ||
[IntegrationTest] | ||
public abstract class IntegrationTest<TFixture> : IClassFixture<TFixture> |
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.
Probably worth inheriting for LoggableTestClass
, to have logging and timeouts for all.
Ideally all test would derive from LoggableTestClass
.
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.
IntegrationTest
is now inherited from LoggableTestClass
. Let's discuss offline if we need some extra functionality around.
var frame = stack.GetFrame(1); // skip 1 frame to get the calling method info | ||
var method = frame.GetMethod(); | ||
collectionName = $"{method.DeclaringType.Name}.{method.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.
It's worth having some tests that consume same collection, overwrite same data. To see how we avoid collisions.
So we are ready for parallelization.
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.
For test Theories - yep. It will be more complicated, as we will need to add some extra suffixes to the names probably (based on test parameters? or test sequential number). Will try to find a way to work with this.
CSharpDriver.sln.DotSettings
Outdated
@@ -1,4 +1,14 @@ | |||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> |
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 file is really hard to review, but we need the changes to let Rider/ReSharper add Integration Tests from template. I can demo how it works on some stand up.
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 we should split templates change from the fixtures changes?
It's better to make sure that template work correctly for both VS and Rider, so we don't tie these two efforts together.
f1f0475
to
c2c4ad2
Compare
CSharpDriver.sln.DotSettings
Outdated
@@ -1,4 +1,14 @@ | |||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> |
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 we should split templates change from the fixtures changes?
It's better to make sure that template work correctly for both VS and Rider, so we don't tie these two efforts together.
|
||
namespace MongoDB.Driver.Tests | ||
{ | ||
public class DatabaseFixture: IDisposable |
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.
nit: space before ':'
}); | ||
} | ||
|
||
public IMongoCollection<T> GetCollection<T>(Action<MongoClientSettings> configure = null, string collectionName = null) |
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.
Should this be protected? Just to signal that these methods are intended for derived classes.
|
||
public virtual bool ResetOnEachGet => false; | ||
|
||
public override IMongoCollection<T> GetCollection<T>(Action<MongoClientSettings> configure = null, string collectionName = null) |
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.
Do we have cases where T != TDocument?
|
||
public IMongoDatabase GetDatabase(Action<MongoClientSettings> configure = null) | ||
{ | ||
return _fixture.GetDatabase(settings => |
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.
minor: expression body ?
if (string.IsNullOrEmpty(collectionName)) | ||
{ | ||
var stack = new System.Diagnostics.StackTrace(); | ||
var frame = stack.GetFrame(2); // skip 2 frame to get the calling method info (this method and IntegrationTest method) |
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.
nit: frames
{ | ||
database.DropCollection(collection); | ||
} | ||
} |
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.
Don't we want just to drop the DB instead?
{ | ||
private static readonly string __timeStamp = DateTime.Now.ToString("yyyyMMdd-HHmmss"); | ||
|
||
private readonly string _databaseName = $"CsharpDriver-{__timeStamp}"; |
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.
nit: CSharp
private readonly HashSet<string> _usedCollections = new(); | ||
|
||
public virtual void Dispose() | ||
{ |
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.
Dispose the client.
{ | ||
[IntegrationTest] | ||
public abstract class IntegrationTest<TFixture> : LoggableTestClass, IClassFixture<TFixture> | ||
where TFixture : DatabaseFixture |
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.
Do we want to consider reusing the client between tests as well (ICollectionFixture
)?
So we don't dispose it after each tests class. For example single client for all LinqIntegrationTest
?
f4578b4
to
1e68de6
Compare
Most of the integration tests we have currently re-inserting the same data for each run. For the tests that is reading data (like a most LINQ tests) it's inefficient. Proposed changes let us utilize xUnit's Fixture to share the test data for the test collection. It means that Fixture will hold the test data, which will be inserted only one before tests will be run for a test class. Also fixture will be disposed after all tests were run, so we can cleanup the test collection.
I've choose 20 last tests in Jira folder because all of them are integration and refactored to use the approach. On mine local machine I saw like a 4 times improvement for this tests.