Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions pages/ef-mstest-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,8 @@ public async Task VerifyEntity_Queryable()

Mark test methods with `[SharedDb]` to share a single database across all query-only tests. Instead of cloning the template for each test, a shared database is created once and reused. This eliminates per-test DB creation overhead for tests that only read data.<!-- include: shared-db. path: /pages/mdsource/shared-db.include.md -->

`[SharedDb]` can also be applied to a test class, or to the assembly with `[assembly: SharedDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

The shared database is read-only and any write throws, not only `SaveChanges`: `ExecuteUpdate`, `ExecuteDelete`, `ExecuteSqlRaw` and hand-written commands are blocked too. Tests that need to write should use `[PooledDb]` instead.<!-- endInclude -->

<!-- snippet: SharedDbTestsMSTest -->
Expand All @@ -673,6 +675,8 @@ public class SharedDbTests : LocalDbTestBase<TheDbContext>

Mark test methods with `[PooledDb]` to lease a database from a fixed pool instead of creating one per test. The pool is built once from the template, and each test leases a database for its duration, writes inside a transaction, and rolls that transaction back on release so the next test sees the template state again.<!-- include: pooled-db. path: /pages/mdsource/pooled-db.include.md -->

`[PooledDb]` can also be applied to a test class, or to the assembly with `[assembly: PooledDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

Two costs disappear. The per-test file copy and attach is gone, and — usually the larger one — so is repeated query plan compilation: SQL Server keys the plan cache by database, so a database per test means every query is compiled afresh for every test and no plan is ever reused. A small pool lets those plans be reused for the rest of the run.

Pool size is `LocalDbSettings.PoolSize`, configurable via the `LocalDBPoolSize` environment variable and defaulting to `Environment.ProcessorCount`. It bounds how many pooled tests run concurrently, since a database is leased to one test at a time. Set it to `1` to serialise pooled tests onto a single database.
Expand All @@ -681,9 +685,9 @@ Not suited to every test:

* Tests that need their changes committed, or that assert on state outside their own transaction.
* Tests that assert on a timeline of changes. Inside one transaction every system-versioned temporal row shares the transaction start time, so a sequence of state changes collapses into a single instant.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute, or mark the test `[NewDb]` if the attribute is on its class or assembly.

Those tests should be left to create a database per test.<!-- endInclude -->
Those tests should be left to create a database per test, marked `[NewDb]` if `[PooledDb]` is applied to their class or assembly.<!-- endInclude -->

<!-- snippet: PooledDbTestsMSTest -->
<a id='snippet-PooledDbTestsMSTest'></a>
Expand Down
8 changes: 6 additions & 2 deletions pages/ef-nunit-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,8 @@ public async Task VerifyEntity_Queryable()

Mark test methods with `[SharedDb]` to share a single database across all query-only tests. Instead of cloning the template for each test, a shared database is created once and reused. This eliminates per-test DB creation overhead for tests that only read data.<!-- include: shared-db. path: /pages/mdsource/shared-db.include.md -->

`[SharedDb]` can also be applied to a test class, or to the assembly with `[assembly: SharedDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

The shared database is read-only and any write throws, not only `SaveChanges`: `ExecuteUpdate`, `ExecuteDelete`, `ExecuteSqlRaw` and hand-written commands are blocked too. Tests that need to write should use `[PooledDb]` instead.<!-- endInclude -->

<!-- snippet: SharedDbTestsNUnit -->
Expand All @@ -683,6 +685,8 @@ public class SharedDbTests :

Mark test methods with `[PooledDb]` to lease a database from a fixed pool instead of creating one per test. The pool is built once from the template, and each test leases a database for its duration, writes inside a transaction, and rolls that transaction back on release so the next test sees the template state again.<!-- include: pooled-db. path: /pages/mdsource/pooled-db.include.md -->

`[PooledDb]` can also be applied to a test class, or to the assembly with `[assembly: PooledDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

Two costs disappear. The per-test file copy and attach is gone, and — usually the larger one — so is repeated query plan compilation: SQL Server keys the plan cache by database, so a database per test means every query is compiled afresh for every test and no plan is ever reused. A small pool lets those plans be reused for the rest of the run.

Pool size is `LocalDbSettings.PoolSize`, configurable via the `LocalDBPoolSize` environment variable and defaulting to `Environment.ProcessorCount`. It bounds how many pooled tests run concurrently, since a database is leased to one test at a time. Set it to `1` to serialise pooled tests onto a single database.
Expand All @@ -691,9 +695,9 @@ Not suited to every test:

* Tests that need their changes committed, or that assert on state outside their own transaction.
* Tests that assert on a timeline of changes. Inside one transaction every system-versioned temporal row shares the transaction start time, so a sequence of state changes collapses into a single instant.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute, or mark the test `[NewDb]` if the attribute is on its class or assembly.

Those tests should be left to create a database per test.<!-- endInclude -->
Those tests should be left to create a database per test, marked `[NewDb]` if `[PooledDb]` is applied to their class or assembly.<!-- endInclude -->

<!-- snippet: PooledDbTestsNUnit -->
<a id='snippet-PooledDbTestsNUnit'></a>
Expand Down
8 changes: 6 additions & 2 deletions pages/ef-tunit-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,8 @@ public async Task VerifyEntity_Queryable()

Mark test methods with `[SharedDb]` to share a single database across all query-only tests. Instead of cloning the template for each test, a shared database is created once and reused. This eliminates per-test DB creation overhead for tests that only read data.<!-- include: shared-db. path: /pages/mdsource/shared-db.include.md -->

`[SharedDb]` can also be applied to a test class, or to the assembly with `[assembly: SharedDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

The shared database is read-only and any write throws, not only `SaveChanges`: `ExecuteUpdate`, `ExecuteDelete`, `ExecuteSqlRaw` and hand-written commands are blocked too. Tests that need to write should use `[PooledDb]` instead.<!-- endInclude -->

<!-- snippet: SharedDbTestsTUnit -->
Expand All @@ -680,6 +682,8 @@ public class SharedDbTests : LocalDbTestBase<TheDbContext>

Mark test methods with `[PooledDb]` to lease a database from a fixed pool instead of creating one per test. The pool is built once from the template, and each test leases a database for its duration, writes inside a transaction, and rolls that transaction back on release so the next test sees the template state again.<!-- include: pooled-db. path: /pages/mdsource/pooled-db.include.md -->

`[PooledDb]` can also be applied to a test class, or to the assembly with `[assembly: PooledDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

Two costs disappear. The per-test file copy and attach is gone, and — usually the larger one — so is repeated query plan compilation: SQL Server keys the plan cache by database, so a database per test means every query is compiled afresh for every test and no plan is ever reused. A small pool lets those plans be reused for the rest of the run.

Pool size is `LocalDbSettings.PoolSize`, configurable via the `LocalDBPoolSize` environment variable and defaulting to `Environment.ProcessorCount`. It bounds how many pooled tests run concurrently, since a database is leased to one test at a time. Set it to `1` to serialise pooled tests onto a single database.
Expand All @@ -688,9 +692,9 @@ Not suited to every test:

* Tests that need their changes committed, or that assert on state outside their own transaction.
* Tests that assert on a timeline of changes. Inside one transaction every system-versioned temporal row shares the transaction start time, so a sequence of state changes collapses into a single instant.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute, or mark the test `[NewDb]` if the attribute is on its class or assembly.

Those tests should be left to create a database per test.<!-- endInclude -->
Those tests should be left to create a database per test, marked `[NewDb]` if `[PooledDb]` is applied to their class or assembly.<!-- endInclude -->

<!-- snippet: PooledDbTestsTUnit -->
<a id='snippet-PooledDbTestsTUnit'></a>
Expand Down
8 changes: 6 additions & 2 deletions pages/ef-xunitv3-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,8 @@ public async Task VerifyEntity_Queryable()

Mark test methods with `[SharedDb]` to share a single database across all query-only tests. Instead of cloning the template for each test, a shared database is created once and reused. This eliminates per-test DB creation overhead for tests that only read data.<!-- include: shared-db. path: /pages/mdsource/shared-db.include.md -->

`[SharedDb]` can also be applied to a test class, or to the assembly with `[assembly: SharedDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

The shared database is read-only and any write throws, not only `SaveChanges`: `ExecuteUpdate`, `ExecuteDelete`, `ExecuteSqlRaw` and hand-written commands are blocked too. Tests that need to write should use `[PooledDb]` instead.<!-- endInclude -->

<!-- snippet: SharedDbTestsXunitV3 -->
Expand All @@ -679,6 +681,8 @@ public class SharedDbTests : LocalDbTestBase<TheDbContext>

Mark test methods with `[PooledDb]` to lease a database from a fixed pool instead of creating one per test. The pool is built once from the template, and each test leases a database for its duration, writes inside a transaction, and rolls that transaction back on release so the next test sees the template state again.<!-- include: pooled-db. path: /pages/mdsource/pooled-db.include.md -->

`[PooledDb]` can also be applied to a test class, or to the assembly with `[assembly: PooledDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

Two costs disappear. The per-test file copy and attach is gone, and — usually the larger one — so is repeated query plan compilation: SQL Server keys the plan cache by database, so a database per test means every query is compiled afresh for every test and no plan is ever reused. A small pool lets those plans be reused for the rest of the run.

Pool size is `LocalDbSettings.PoolSize`, configurable via the `LocalDBPoolSize` environment variable and defaulting to `Environment.ProcessorCount`. It bounds how many pooled tests run concurrently, since a database is leased to one test at a time. Set it to `1` to serialise pooled tests onto a single database.
Expand All @@ -687,9 +691,9 @@ Not suited to every test:

* Tests that need their changes committed, or that assert on state outside their own transaction.
* Tests that assert on a timeline of changes. Inside one transaction every system-versioned temporal row shares the transaction start time, so a sequence of state changes collapses into a single instant.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute, or mark the test `[NewDb]` if the attribute is on its class or assembly.

Those tests should be left to create a database per test.<!-- endInclude -->
Those tests should be left to create a database per test, marked `[NewDb]` if `[PooledDb]` is applied to their class or assembly.<!-- endInclude -->

<!-- snippet: PooledDbTestsXunitV3 -->
<a id='snippet-PooledDbTestsXunitV3'></a>
Expand Down
6 changes: 4 additions & 2 deletions pages/mdsource/pooled-db.include.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
Mark test methods with `[PooledDb]` to lease a database from a fixed pool instead of creating one per test. The pool is built once from the template, and each test leases a database for its duration, writes inside a transaction, and rolls that transaction back on release so the next test sees the template state again.

`[PooledDb]` can also be applied to a test class, or to the assembly with `[assembly: PooledDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

Two costs disappear. The per-test file copy and attach is gone, and — usually the larger one — so is repeated query plan compilation: SQL Server keys the plan cache by database, so a database per test means every query is compiled afresh for every test and no plan is ever reused. A small pool lets those plans be reused for the rest of the run.

Pool size is `LocalDbSettings.PoolSize`, configurable via the `LocalDBPoolSize` environment variable and defaulting to `Environment.ProcessorCount`. It bounds how many pooled tests run concurrently, since a database is leased to one test at a time. Set it to `1` to serialise pooled tests onto a single database.
Expand All @@ -8,6 +10,6 @@ Not suited to every test:

* Tests that need their changes committed, or that assert on state outside their own transaction.
* Tests that assert on a timeline of changes. Inside one transaction every system-versioned temporal row shares the transaction start time, so a sequence of state changes collapses into a single instant.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute.
* On failure the database cannot be inspected, since the transaction is rolled back. When debugging, temporarily remove the attribute, or mark the test `[NewDb]` if the attribute is on its class or assembly.

Those tests should be left to create a database per test.
Those tests should be left to create a database per test, marked `[NewDb]` if `[PooledDb]` is applied to their class or assembly.
2 changes: 2 additions & 0 deletions pages/mdsource/shared-db.include.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
Mark test methods with `[SharedDb]` to share a single database across all query-only tests. Instead of cloning the template for each test, a shared database is created once and reused. This eliminates per-test DB creation overhead for tests that only read data.

`[SharedDb]` can also be applied to a test class, or to the assembly with `[assembly: SharedDb]`. The nearest attribute wins: a method attribute overrides a class attribute, which overrides an assembly attribute. Mark a test method or class with `[NewDb]` to opt it out and create a database per test. Applying more than one of `[PooledDb]`, `[SharedDb]` and `[NewDb]` to the same method, class, or assembly throws.

The shared database is read-only and any write throws, not only `SaveChanges`: `ExecuteUpdate`, `ExecuteDelete`, `ExecuteSqlRaw` and hand-written commands are blocked too. Tests that need to write should use `[PooledDb]` instead.
21 changes: 21 additions & 0 deletions src/EfLocalDb.MSTest.Tests/ClassLevelPooledDbTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[TestClass]
[PooledDb]
public class ClassLevelPooledDbTests : LocalDbTestBase<TheDbContext>
{
[TestMethod]
public void UsesPooledDb() =>
Assert.IsNotNull(Database.Transaction);

[TestMethod]
[SharedDb]
public void MethodOverridesClass() =>
Assert.AreEqual("Shared", Database.Name);

[TestMethod]
[NewDb]
public void NewDbOptsOut()
{
Assert.IsNull(Database.Transaction);
Assert.AreNotEqual("Shared", Database.Name);
}
}
16 changes: 16 additions & 0 deletions src/EfLocalDb.MSTest.Tests/PooledAndSharedDbTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[TestClass]
public class PooledAndSharedDbTests : LocalDbTestBase<TheDbContext>
{
// SetUp would throw before the test body runs, so skip it here and
// invoke the base implementation from within the test.
public override Task SetUp() => Task.CompletedTask;

[TestMethod]
[PooledDb]
[SharedDb]
public async Task Throws()
{
var exception = await Assert.ThrowsExactlyAsync<Exception>(() => base.SetUp());
Assert.AreEqual("[PooledDb], [SharedDb] and [NewDb] are mutually exclusive. Use only one on a test method.", exception.Message);
}
}
10 changes: 3 additions & 7 deletions src/EfLocalDb.MSTest/LocalDbTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,9 @@ public virtual Task SetUp()
var methodInfo = GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
.First(_ => _.Name == TestContext.TestName && !_.IsGenericMethod);
isSharedDb = methodInfo.GetCustomAttribute<SharedDbAttribute>() != null;
isPooledDb = methodInfo.GetCustomAttribute<PooledDbAttribute>() != null;

if (isPooledDb && isSharedDb)
{
throw new("[PooledDb] and [SharedDb] are mutually exclusive. Use only one on a test method.");
}
var mode = DbAttributeReader.Read<SharedDbAttribute, PooledDbAttribute, NewDbAttribute>(methodInfo, GetType());
isSharedDb = mode == DbMode.Shared;
isPooledDb = mode == DbMode.Pooled;

QueryFilter.Enable();
return Reset();
Expand Down
9 changes: 9 additions & 0 deletions src/EfLocalDb.MSTest/NewDbAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace EfLocalDb;

/// <summary>
/// Runs the test against a new database built from the template. This is the default when no
/// attribute applies, so it is only needed to opt a method or class out of a
/// <see cref="SharedDbAttribute" /> or <see cref="PooledDbAttribute" /> applied to its class or assembly.
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public sealed class NewDbAttribute : Attribute;
Loading
Loading