自己ホストを使ったspecflowの設定Web API


アットLeaveWizard.com 我々は、行動駆動開発(BDD)のファンであり、実際のビジネス用語の要件をキャプチャするためにGHerkinの構文の使用.多くの場合よりも、BDDはセレンとUI駆動仕様のテストと同義になりました.しかしながら、私はBDDが「単位」のレベルで私達の理解を再定義することによって等しく「ユニットテスト」レベルで等しくなることができると信じています、そして、それはそれの出口点(APIコントローラなど)からのアプリケーションの振舞いをそれが出口点(外部データベースへのアクセスなど)にテストします.しかし、使用するとき、これを達成する方法に関するドキュメンテーションの明確な不足があるようです.NET 5以降では、このポストでは、あなた自身のプロジェクトのためにこれを行う方法を説明します.

TLドクターコードを見せてください
あなたがちょうどこれのためにコードを見たいならば、我々を見てくださいsample project on Github . これはデフォルトを使用した実装例です.デモンストレーション目的のためのネット5天気予報Web APIプロジェクト.

何を達成しようとしているか.
テストソフトウェアでは、特定の実装の詳細ではなく、アプリケーションの期待される動作をテストしていることを確認します.Test Driven Development(TDD)は多くの低レベルのユニットテストを実装することを目的としているが、多くの人々はテストがしばしば変更する必要があるテストコードの膨大な量のため、リファクタリングコードへのバリアになることがあることを多くの人々が見つけている.テストが実装にしっかり結合されているため、クラスレベルのメソッドとプロパティのテストです.これが非常に役に立つ状況があります、そして、私は確かにこれらの下位レベルテストのどれも持ってはいけないということを提案していません、しかし、我々はクラスレベルより上にコードの行動をテストするのを目指しなければなりません、しかし、UIレベルの下で.で.MnetとWeb APIプロジェクトのセルフホスティングを有効にしているネット5以降では、彼らは生産においてコントローラを起動することが可能です.したがって、APIエントリポイントから外部データ依存性までの動作をテストすることができます.

コードを調べる
The LeaveWizard.WeatherForecast.Api プロジェクトはサンプルです.ネット天気予報のアプリケーションの実装でネット天気予報のエンドポイントをリファクタリングのプロバイダと呼ばれるWeatherForecastProvider インタフェースでIWeatherForecastProvider バックエンドデータを模擬するために有効にします.
また、私たちはLeaveWizard.WeatherForecast.Api.Specs 期待される動作の仕様を含むプロジェクト.

機能ファイル
機能ファイルは、テストする機能をキャプチャします.このプロジェクトでは、フィーチャーフォルダーに格納されている単純な機能ファイルだけがこのようになります.
Feature: Weather Prediction
    The weather prediction service allows users to predict the weather

Scenario: Can predicate a storm
    Given there is a storm coming
    When a request is made to predict the weather
    Then a storm is correctly predicted
これは期待される振る舞いを表すgherkin構文です.私はこの機能ファイル/シナリオの「品質」にあまり関心を持ちません.重要なことは、私たちが与えられたとき、例を示します.

その時、
次に、シナリオの文構造を提供する場合、各ステップ型の期待を簡潔に記述する.

与えられる
与えられたステップは過去に起こったことに関するすべてです、それは過去時制で書かれなければなりません.
これは、シナリオが起こっているコンテキストをキャプチャするために使用されます.
与えられたステップはどんな実世界コードも実行してはいけませんが、代わりにシナリオをサポートするために必要とされるmocksと他の文脈詳細をセットアップするのに使用されるべきです.
与えられた命令が任意の順序にあることができることに注意することは重要である、そして、手段によって、命令のセットを決定する.


現在の段階で発生します.それは、観察されることができる若干の行動に結果として起こるイベントです.
シナリオごとに単一のステートメントがある必要があります.「時と」のようなシナリオがあれば、このシナリオを2つの別々のシナリオに分けることができるかどうか考えてください.

Then
次にステップは、予想される結果をどのように観察すべきかを定義するために使用される

コードにステップを結ぶ
SpecFlowを使用すると、ステップファイルを作成することにより、機能ファイル内の手順をコードに接続できます.これらの例はSteps folder , 以下は天気予報ステップファイルの例です.
    [Binding]
    public class WeatherPredictionSteps
    { 
        private readonly WebApiDriver _webApiDriver;
        private readonly WeatherForecastContext _weatherForecastContext;

        public WeatherPredictionSteps( 
            WebApiDriver webApiDriver,
            WeatherForecastContext weatherForecastContext) 
        {
            _webApiDriver = webApiDriver;
            _weatherForecastContext = weatherForecastContext;
        }

        [Given(@"there is a storm coming")]
        public void GivenThereIsAStormComing()
        {
            _weatherForecastContext.PredictAStorm();
        }

        [When(@"a request is made to predict the weather")]
        public void WhenARequestIsMadeToPredictTheWeather()
        {
            var response = _webApiDriver.ExecuteGet<List<WeatherForecast>>(EndpointRoutes.GetWeatherForecast);
            response.StatusCode.Should().Be(HttpStatusCode.OK);

            _weatherForecastContext.ReceivedForecast = response.ResponseData; 
        }

        [Then(@"a storm is correctly predicted")]
        public void ThenAStormIsCorrectlyPredicted()
        {
            _weatherForecastContext
                .ReceivedForecast
                .Should()
                .BeEquivalentTo(_weatherForecastContext.PredictedForecast);
        }
    }
ここで、コードは、指定されたテキストを使用して、指定されたテキストに一致する正規表現を使用し、必要なコードを実行する属性を使用して、ステップに関連付けられていることがわかります.

正しいコンテキストの取得
複数のステップを含む複数のステップファイルを持つことができます.手順は、特定の機能に関連付けられている必要はありません.別の機能を使用してステップを再利用することができますので、ステップファイルに状態を格納しないことが重要です.代わりに、コンテキストオブジェクトを使いたい.これは単に与えられたテストコンテキストに関連しているかもしれないデータを保持するPOCOですWeatherForecastContext これは、予測された予測の種類と予測結果を格納するだけである.
これにより、コンテキストを渡すことができ、これが必要とされる情報にアクセスできます.例えば、WeatherForecastProviderFake 私たちが天気予報文脈を注入するのを見ることができます、そして、この例では単に予測された予想を返します、しかし、これは文脈オブジェクトによって提供されるどんな他の情報にも基づいてどんなデータに戻るかについての決定をすることができました.必要に応じてより多くの文脈を注入することも可能です.

機能の駆動
また、上記の例ではAを持っていますWebApiDriver . これは実際にWeb APIとの相互作用を行うためのロジックを含んでいます.HttpClientとの相互作用を所有し、テスト実行の最後に配置する責任があります.下記のように、WebAppDriverは、AppHostingContextをコンストラクターパラメーターとして受け取り、これを使用して、ホスティング環境で使用するクライアントを作成します.
    public class WebApiDriver : IDisposable
    {
        private readonly AppHostingContext _appHostingContext;
        private readonly WebApiContext _webApiContext;
        private readonly UserContext _userContext;
        private readonly StringBuilder _log = new();

        private HttpClient _httpClient;

        public HttpClient HttpClient
        {
            get
            {
                if (_httpClient == null)
                    _httpClient = _appHostingContext.CreateClient();
                return _httpClient;
            }
        }

        public WebApiDriver(AppHostingContext appHostingContext, 
                            WebApiContext webApiContext, 
                            UserContext userContext)
        {
            _appHostingContext = appHostingContext;
            _webApiContext = webApiContext;
            _userContext = userContext;
        }

        public void Dispose()
        {
            if (_httpClient != null)
            {
                _httpClient.Dispose();
                _httpClient = null;
            }
        }

セルフホスティング環境
The AppHostingContext セルフホスティング環境のライフサイクルを管理する責任があります.これはSpecFlowWebApplicationFactory セルフホストのWebアプリケーションを作成するには.

Webアプリケーションファクトリ
SpecFlowWebApplicationFactoryは単に組み込みを使用します.SpecFlow固有のホスト環境を作成するためのNet 5 Host Builder機能
public class SpecFlowWebApplicationFactory : WebApplicationFactory<SpecFlowStartup>
    {
        private readonly IComponentContext _componentContext;

        public SpecFlowWebApplicationFactory(IComponentContext componentContext)
        {
            _componentContext = componentContext;
        }

        protected override IHostBuilder CreateHostBuilder()
        {
            var builder = Host.CreateDefaultBuilder()
                .UseServiceProviderFactory(x => new AutofacServiceProviderFactory())
                .ConfigureWebHostDefaults(x =>
                {
                    x.UseStartup(ctx => new SpecFlowStartup(ctx.Configuration, _componentContext)).UseTestServer();
                });
            return builder;
        }

        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            base.ConfigureWebHost(builder);
            builder.ConfigureLogging((context, loggingBuilder) =>
            {
                loggingBuilder.ClearProviders();
                loggingBuilder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, DebugLoggerProvider>());
            });
        }

        #region Debug Logger

        #endregion
    }
ご覧の通り特別なものSpecFlowStartup を継承するクラスStartup 天気予報APIプロジェクトによって使用されるクラス.これは以下のようなキーメソッドを上書きします:
  • MVC環境の設定:依存性注入のためのコントローラの登録
  • 認証の設定:Authの独自のテストバージョンを使用する
  • 追加の依存関係の登録:元のアプリケーションの動作をオーバーライドするために使用できるテストクラス

  • AutoFcの使用
    コードのこのバージョンでは、私たちの依存注入のためのautoFactを使用しています.これはProgram.cs 単にNUGETパッケージをインストールすることによってUseServiceProviderFactory(new AutofacServiceProviderFactory()) . 必要なのは、スタートアップファイルに次のコードを追加することです.これはアプリケーションの起動中に「自動的」と呼ばれ、必要な依存関係を登録することができます.
            public void ConfigureContainer(ContainerBuilder builder)
            {
                RegisterDependencies(builder);
            }
    
            protected virtual void RegisterDependencies(ContainerBuilder builder)
            {
                builder.RegisterType<WeatherForecastProvider>().As<IWeatherForecastProvider>();
            }
    

    ホストの配置
    我々が必要とする最後のものは、我々がされるならば、ホスティング環境を処分することです.これはSpecFlow hook これは単にテストの実行が完了した後にアプリケーションを停止するホスティングコンテキストを指示します.

    概要
    上記のプロジェクト構成を使用することによって、私たちのBDD仕様をドライブして、自己ホスティング機能を利用しているAPIエントリーポイントからコードを実行するためにSpecFlowを使うことができます.NET 5 .これは、我々の仕様が彼らが生産環境の中で呼び出されて、より大きなテスト報道をするので、APIを呼び出すことができることを意味します-我々は認可属性のようなものをテストすることができます.また、私たちがテストを我々のテストをより堅牢にして、将来容易にリファクタリングすることを可能にするように結びつけないように、フィルタ等はAPIの内容をブラックボックスのより多くと扱うのを可能にします.我々はまだクラスレベルのユニットテストを使用して、より詳細なアサーションを必要とするコードをテストする必要がありますが、ブラウザーを自動化する必要がなくても、より高い抽象レベルでSpecFlow仕様を使用することで、バックのためのより大きな袋を持つより安定したテスト環境を提供します.
    私はこれがあなたのspecflowとBDDの旅に役立つ願っています.フィードバックは常に歓迎.