Skip to content

Commit

Permalink
Revert to v2.0.0
Browse files Browse the repository at this point in the history
This reverts most of the changes made in v3 & v4. Mainly, this gets rid of the custom test runner and relies on xunit's built-in test-running logic. This will eliminate flakiness in some of our builds.

Also removing the ISpecification interface and instead relying on AsyncSpecification & Specification base classes.

Updating documentation to reflect the new state of things.

+semver:major
  • Loading branch information
chadly committed Apr 26, 2018
1 parent c8bcd0a commit ed5f869
Show file tree
Hide file tree
Showing 26 changed files with 132 additions and 883 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2013 Chad Lee
Copyright (c) 2018 Archon Information Systems, LLC.

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
13 changes: 1 addition & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Extends xUnit.Net with Behavior Driven Development style fixtures.

Some of the design goals include:
* Use natural C# constructs to control things such as BDD contexts and concerns. For example, the context of the specification is defined in the class constructor and the concern for the fixture is defined by its namespace.
* Don't force me to inherit from any base class to get BDD style tests working. There is an interface `ISpecification` that one can implement to accomplish this. A `Specification` & `AsyncSpecification` base classes are also provided for convenience.
* Async tests are a first class citizen.

See here for a [full introduction](https://www.chadly.net/bdd-with-xunit-net/)

Expand All @@ -19,15 +19,12 @@ dotnet add package xUnit.BDD
* [Write a Scenario](#write-a-scenario)
* [Async Tests](#async-tests)
* [Handling Exceptions](#handling-exceptions)
* [Shared Context](#shared-context)

### Write a Scenario

```cs
using Xunit.Extensions;

[assembly: TestFramework("Xunit.Extensions.ObservationTestFramework", "Xunit.Bdd")]

public class Calculator
{
public int Add(int x, int y) => x + y;
Expand Down Expand Up @@ -177,14 +174,6 @@ public class when_adding_an_inappropriate_number : Specification

The `HandleExceptionsAttribute` will cause the test harness to handle any exceptions thrown by the `Observe` method. You can then make assertions on the thrown exception via the `ThrownException` property. If you leave off the `HandleExceptions` on the test class, it will not handle any exceptions from `Observe`. Therefore, you should only add the attribute if you are expecting an exception so as not to hide test failures.

### Shared Context

When writing test scenarios like this, you "observe" one thing in the `Observe` method and make one or more `Observation`s on the results. Due to this, the test framework overrides [Xunit's default handling of shared test context](https://xunit.github.io/docs/shared-context.html). Instead of creating a new instance of the class for each `Observation` and rerunning the test setup & `Observe` method, the test harness will create the class once, run the setup & `Observe` once, and then run all of the `Observation`s in sequence.

In other words, it treats all `ISpecification` tests as [class fixtures](https://xunit.github.io/docs/shared-context.html#class-fixture) (e.g. shared object instance across tests in a single class).

If you write BDD scenarios as prescribed, this should make no difference to you. It is simply a performance optimization that you should be aware of.

## Building Locally

After cloning, run:
Expand Down
80 changes: 71 additions & 9 deletions src/AsyncSpecification.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Xunit.Extensions
{
/// <summary>
/// The base async specification class
/// </summary>
public abstract class AsyncSpecification : ISpecification, IAsyncLifetime
public abstract class AsyncSpecification : IAsyncLifetime
{
Exception exception;
static readonly ReaderWriterLockSlim sync = new ReaderWriterLockSlim();
static readonly Dictionary<Type, bool> typeCache = new Dictionary<Type, bool>();

/// <summary>
/// The exception that was thrown when Observe was run; null if no exception was thrown.
/// </summary>
public Exception ThrownException { get; set; }
protected Exception ThrownException => exception;

public virtual Task InitializeAsync()
{
return Task.FromResult(0);
}
/// <summary>
/// Initialize the test class all async-like.
/// </summary>
public virtual Task InitializeAsync() => CommonTasks.Completed;

/// <summary>
/// Performs an action, the outcome of which will be observed to validate the specification.
/// Performs the action to observe the outcome of to validate the specification.
/// </summary>
public abstract Task ObserveAsync();

public virtual Task DisposeAsync()
/// <summary>
/// Cleanup the test class all async-like.
/// </summary>
public virtual Task DisposeAsync() => CommonTasks.Completed;

async Task IAsyncLifetime.InitializeAsync()
{
return Task.FromResult(0);
await InitializeAsync();

try
{
await ObserveAsync();
}
catch (Exception ex)
{
if (!HandleException(ex))
throw;
}
}

bool HandleException(Exception ex)
{
exception = ex;
return ShouldHandleException();
}

bool ShouldHandleException()
{
Type type = GetType();

try
{
sync.EnterReadLock();

if (typeCache.ContainsKey(type))
return typeCache[type];
}
finally
{
sync.ExitReadLock();
}

try
{
sync.EnterWriteLock();

if (typeCache.ContainsKey(type))
return typeCache[type];

var attrs = type.GetTypeInfo().GetCustomAttributes(typeof(HandleExceptionsAttribute), true).OfType<HandleExceptionsAttribute>();

return typeCache[type] = attrs.Any();
}
finally
{
sync.ExitWriteLock();
}
}
}
}
9 changes: 9 additions & 0 deletions src/CommonTasks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Threading.Tasks;

namespace Xunit.Extensions
{
static class CommonTasks
{
public static readonly Task Completed = Task.FromResult(0);
}
}
12 changes: 0 additions & 12 deletions src/ISpecification.cs

This file was deleted.

34 changes: 0 additions & 34 deletions src/ObservationAssemblyRunner.cs

This file was deleted.

16 changes: 3 additions & 13 deletions src/ObservationAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,9 @@
using System;
using Xunit;

[assembly: TestFramework("Xunit.Extensions.ObservationTestFramework", "Xunit.Bdd")]

namespace Xunit.Extensions
namespace Xunit.Extensions
{
/// <summary>
/// Identifies a method as an observation which asserts the specification
/// </summary>
[AttributeUsage(AttributeTargets.Method)]
public class ObservationAttribute : Attribute
public class ObservationAttribute : FactAttribute
{
/// <summary>
/// Marks the test so that it will not be run, and gets or sets the skip reason
/// </summary>
public string Skip { get; set; }
}
}
}
61 changes: 0 additions & 61 deletions src/ObservationDiscoverer.cs

This file was deleted.

42 changes: 0 additions & 42 deletions src/ObservationExecutor.cs

This file was deleted.

19 changes: 0 additions & 19 deletions src/ObservationTest.cs

This file was deleted.

Loading

0 comments on commit ed5f869

Please sign in to comment.