{"id":25776572,"url":"https://github.com/bodar/yatspec","last_synced_at":"2025-02-27T06:06:42.938Z","repository":{"id":28984432,"uuid":"32510995","full_name":"bodar/yatspec","owner":"bodar","description":"Yet Another Test Specification Library","archived":false,"fork":false,"pushed_at":"2021-07-02T16:31:53.000Z","size":8029,"stargazers_count":57,"open_issues_count":15,"forks_count":24,"subscribers_count":12,"default_branch":"master","last_synced_at":"2024-12-06T22:18:31.734Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bodar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-03-19T09:02:18.000Z","updated_at":"2024-08-06T06:13:25.000Z","dependencies_parsed_at":"2022-07-26T03:48:52.919Z","dependency_job_id":null,"html_url":"https://github.com/bodar/yatspec","commit_stats":null,"previous_names":[],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bodar%2Fyatspec","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bodar%2Fyatspec/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bodar%2Fyatspec/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bodar%2Fyatspec/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bodar","download_url":"https://codeload.github.com/bodar/yatspec/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240987435,"owners_count":19889334,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-27T06:01:26.897Z","updated_at":"2025-02-27T06:06:42.932Z","avatar_url":"https://github.com/bodar.png","language":"Java","funding_links":[],"categories":["测试"],"sub_categories":[],"readme":"\u003ch1\u003e\u003cimg src=\"https://raw.githubusercontent.com/wiki/bodar/yatspec/logo.jpg\" align=\"absmiddle\"/\u003e YatSpec\u003c/h1\u003e\n\nYatSpec is a [BDD test framework](https://en.wikipedia.org/wiki/Behavior-driven_development). YatSpec runs your JUnit tests and generates human-readable HTML reports.\n \nYatSpec replaces test tools like Concordion and Fit by allowing your tests to stay maintainable (i.e refactoring support \nin you favourite IDE) while still producing human readable documentation.\nIf you are doing [three amigos](https://www.agilealliance.org/glossary/three-amigos/) and only developers modify the \ntests there is no need to maintain a set of HTML or text specification files, just use pure Java to write your tests and use YatSpec to generate the HTML report.\n\nAlternative tools:\n* [Cucumber](https://cucumber.io/) - YatSpec is different because you write your tests in pure Java and JUnit not in plain text .feature files.\n* [Concordion](https://concordion.org/) - YatSpec is different because you write your tests in pure Java and JUnit instead of HTML. \n* [Fit](http://fit.c2.com/) - YatSpec is different because you write your tests in pure Java and JUnit instead of HTML.  \n\nCurrently this library supports several features:\n  * Generate [HTML](https://github.com/bodar/yatspec/raw/wiki/example.html) based on a [JUnit Java](/test/com/googlecode/yatspec/junit/SpecificationExampleTest.java) file\n  * BDD \"Given / When / Then\" support with automatic capturing and display of \"givens\" and captured inputs and output.\n  * Tabular data tests by using [@TableRunner](src/com/googlecode/yatspec/junit/TableRunner.java) \n  with [@Table](src/com/googlecode/yatspec/junit/Table.java) and [@Row](src/com/googlecode/yatspec/junit/Row.java) support for JUnit Methods (similar to in MBUnit / NUnit)\n  * [@Notes](src/com/googlecode/yatspec/junit/Notes.java) to add comments to tests\n  * [@LinkingNote](/src/com/googlecode/yatspec/junit/LinkingNote.java) to [generate links between tests](https://www.youtube.com/watch?v=CFMkD-t363c)  \n\n### Quick Start ###\n\nThe simplest example would be to add the JUnit [@RunWith](http://junit.sourceforge.net/javadoc/org/junit/runner/RunWith.html) attribute specifying the [SpecRunner](/src/com/googlecode/yatspec/junit/SpecRunner.java) class to your Test class.\n\n```java\n@RunWith(SpecRunner.class)\npublic class ExampleTest {\n    @Test\n    public void reallySimpleExample() throws Exception {\n        assertThat(\"The quick brown fox\".contains(\"fox\"), is(true));\n    }\n}\n```\n\nIf you ran this test you would see the following in standard output (console):\n\n```\n(...)\nHtml output:\n/tmp/com/googlecode/yatspec/junit/ExampleTest.html\n```\n\nIf you opened the HTML file you would see:\n\n![Screenshot](https://raw.githubusercontent.com/wiki/bodar/yatspec/yatspec.png)\n\nSo lets quickly explain what has happened:\n\n  * The fully qualified class name is turned into the directory structure and filename. i.e. `com.company.ExampleTest` -\u003e `com/company/ExampleTest.html`\n    * The root output folder can be [configured](Configuration.md)\n  * A table of contents is generated for every test method\n  * The method body is interpreted into a text specification\n  * Tests are colour according to the following scheme\n    * Green == Passed\n    * Red == Failed\n    * Orange == Not Run\n\n### YatSpec Tutorial ###\n\nLet us have a look at an example application that connects to a third party API to fetch data and process it.\nIn this example, Weather Application connects DarkSky APIs to fetch the weather forecast and extract the current wind speed in London.\nWe will test that Weather Application API using YatSpec. \n\nThe whole example is available at https://github.com/wojciechbulaty/examples/tree/master/weather-yatspec-example\n\n##### YatSpec Tutorial Step 0: Set up the project #####\nStart with adding YatSpec to your project. If you use maven, add a dependency to `pom.xml`:\n````xml\n\u003cdependency\u003e\n    \u003cgroupId\u003ecom.googlecode.yatspec\u003c/groupId\u003e\n    \u003cartifactId\u003eyatspec\u003c/artifactId\u003e\n    \u003cversion\u003e1.27\u003c/version\u003e\n    \u003cscope\u003etest\u003c/scope\u003e\n\u003c/dependency\u003e\n````\nYou will also need a repository in your `pom.xml`:\n````xml\n\u003crepositories\u003e\n    \u003crepository\u003e\n        \u003cid\u003ehttp://repo.bodar.com/\u003c/id\u003e\n        \u003curl\u003ehttp://repo.bodar.com/\u003c/url\u003e\n    \u003c/repository\u003e\n\u003c/repositories\u003e\n````\n\n##### YatSpec Tutorial Step 1: Create a test #####\nCreate your first YatSpec test. Its just a JUnit test written in a BDD-style. The YatSpec `SpecRunner` will pick it up and generate a HTML output based on the test method body.\n````java\n@RunWith(SpecRunner.class)\npublic class WeatherApplicationTest extends TestState {\n    private final WeatherApplication weatherApplication = new WeatherApplication();\n\n    @Rule\n    public WireMockRule darkSkyAPIStub = new WireMockRule();\n\n    private HttpResponse httpResponse;\n    private String responseBody;\n\n    @Before\n    public void setUp() {\n        weatherApplication.start();\n    }\n\n    @After\n    public void tearDown() {\n        weatherApplication.stop();\n    }\n\n    @Test\n    public void servesWindSpeedBasedOnDarkSkyResponse() throws IOException {\n        givenDarkSkyForecastForLondonContainsWindSpeed(\"12.34\");\n        whenIRequestForecast();\n        thenTheWindSpeedIs(\"12.34mph\");\n    }\n\n    private void whenIRequestForecast() throws IOException {\n        Request get = Request.Get(\"http://localhost:\" + weatherApplication.port() + \"/wind-speed\");\n        Response response = get.execute();\n        httpResponse = response.returnResponse();\n        responseBody = EntityUtils.toString(httpResponse.getEntity());\n    }\n\n    private void thenTheWindSpeedIs(String expected) throws IOException {\n        assertEquals(expected, responseBody);\n    }\n\n    private void givenDarkSkyForecastForLondonContainsWindSpeed(String windSpeed) throws IOException {\n        darkSkyAPIStub.stubFor(get(urlEqualTo(\"/forecast/e67b0e3784104669340c3cb089412b67/51.507253,-0.127755\"))\n                .willReturn(aResponse().withBody(darkSkyResponseBody(windSpeed))));\n    }\n\n    private String darkSkyResponseBody(String windSpeed) throws IOException {\n        return format(IOUtils.toString(getClass().getClassLoader().getResourceAsStream(\"darksky-response-body.json\")), windSpeed);\n    }\n}\n````\n##### YatSpec Tutorial Step 2: Run the test and inspect the HTML output #####\nRun the test (in IntelliJ or Eclipse) and inspect the standard output.\nIt will point you to the location of the HTML report\n````\n(...)\nYatspec output:\nC:\\Users\\Wojtek\\AppData\\Local\\Temp\\com\\wbsoftwareconsutlancy\\WeatherApplicationTest.html\n```` \nOpen that file in a browser and you will see the test results.\n![Screenshot](https://raw.githubusercontent.com/wiki/bodar/yatspec/tutorial/yatspec_tutorial_basic_test.PNG)\n\n##### YatSpec Tutorial Step 3: Storing test state #####\nAs you can see in the example test, if you would like to store the result of a \"when\" method and save it to be asserted on in the \"then\"\nmethod, the recommended way is to use a class attribute, for example `httpResponse` or `responseBody` in this case.\n\nIf your tests became complex and you have many scenarios per tests class, you might end up having many class attributes used by different tests.\nIn that case, we recommend splitting the test class into multiple test classes. That should result in easier to follow test scenarios.\n##### YatSpec Tutorial Step 4: Use captured inputs and outputs #####\nYou can use the `log(String, Object)` method to add request and response payloads to \"captured inputs and outputs\", which will result in adding them the HTML test report.\nFor example, you can save the request and response body by calling the `log` method:\n```java\nprivate void whenIRequestForecast() throws IOException {\n    Request get = Request.Get(\"http://localhost:\" + weatherApplication.port() + \"/wind-speed\");\n    log(\"Request from client to \" + WEATHER_APPLICATION, get);\n    Response response = get.execute();\n    httpResponse = response.returnResponse();\n    responseBody = EntityUtils.toString(httpResponse.getEntity());\n    log(\"Response from \" + WEATHER_APPLICATION + \" to client\", toString(httpResponse, responseBody));\n}\n```\nAnd they will show in the generated HTML report:\n![Screenshot](https://raw.githubusercontent.com/wiki/bodar/yatspec/tutorial/yatspec_tutorial_captured_inputs_outputs.PNG)\n\n##### YatSpec Tutorial Step 5: Use interesting givens #####\nOnce you have used the `log` method to save requests and responses in the captured inputs and outputs, you can use `interestingGivens`\nto highlight interesting bits in those payloads, in out case it will be the wind speed:\n````java\nprivate void givenDarkSkyForecastForLondonContainsWindSpeed(String windSpeed) throws IOException {\n    interestingGivens.add(\"Wind speed\", windSpeed);\n    darkSkyAPIStub.stubFor(get(urlEqualTo(\"/forecast/e67b0e3784104669340c3cb089412b67/51.507253,-0.127755\"))\n            .willReturn(aResponse().withBody(darkSkyResponseBody(windSpeed))));\n}\n````\nThat results it highlighting it in yellow on the HTML report:\n![Screenshot](https://raw.githubusercontent.com/wiki/bodar/yatspec/tutorial/yatspec_tutorial_interesting_givens.PNG)\n\n##### YatSpec Tutorial Step 6: Generate a sequence diagram #####\nWe can now proceed to generate a sequence diagram that will visualise how.\nIt is an especially useful technique in microservice architectures where there are many components communicating with each other.\nYou do it by adding some extra code, so that the whole test looks like this:\n````java\n@RunWith(SpecRunner.class)\npublic class WeatherApplicationTest extends TestState implements WithCustomResultListeners {\n    private static final String WEATHER_APPLICATION = \"WeatherApplication\";\n\n    private final WeatherApplication weatherApplication = new WeatherApplication();\n\n    @Rule\n    public WireMockRule darkSkyAPIStub = new WireMockRule();\n\n    private HttpResponse httpResponse;\n    private String responseBody;\n\n    @Before\n    public void setUp() {\n        weatherApplication.start();\n        darkSkyAPIStub.addMockServiceRequestListener(new LogWiremockInYatspecRequest(this, WEATHER_APPLICATION, \"DarkSky\"));\n    }\n\n    @After\n    public void tearDown() {\n        weatherApplication.stop();\n        addSequenceDiagram();\n    }\n\n    @Test\n    public void servesWindSpeedBasedOnDarkSkyResponse() throws IOException {\n        givenDarkSkyForecastForLondonContainsWindSpeed(\"12.34\");\n        whenIRequestForecast();\n        thenTheWindSpeedIs(\"12.34mph\");\n    }\n\n    private void addSequenceDiagram() {\n        super.log(\"Sequence diagram\", new SequenceDiagramGenerator()\n                .generateSequenceDiagram(new ByNamingConventionMessageProducer().messages(capturedInputAndOutputs)));\n    }\n\n    private void whenIRequestForecast() throws IOException {\n        Request get = Request.Get(\"http://localhost:\" + weatherApplication.port() + \"/wind-speed\");\n        log(\"Request from client to \" + WEATHER_APPLICATION, get);\n        Response response = get.execute();\n        httpResponse = response.returnResponse();\n        responseBody = EntityUtils.toString(httpResponse.getEntity());\n        log(\"Response from \" + WEATHER_APPLICATION + \" to client\", toString(httpResponse, responseBody));\n    }\n\n    private String toString(HttpResponse response, String responseBody) throws IOException {\n        StringBuilder result = new StringBuilder();\n        result.append(\"HTTP\").append(\" \").append(response.getStatusLine().getStatusCode()).append(\"\\n\");\n        if (response.getAllHeaders() != null) {\n            Arrays.stream(response.getAllHeaders()).forEach(h -\u003e result.append(h.getName()).append(\": \").append(h.getValue()).append(\"\\n\"));\n        }\n        result.append(\"\\n\").append(\"\\n\").append(responseBody);\n        return result.toString();\n    }\n\n    private void thenTheWindSpeedIs(String expected) throws IOException {\n        assertEquals(expected, responseBody);\n    }\n\n    private void givenDarkSkyForecastForLondonContainsWindSpeed(String windSpeed) throws IOException {\n        interestingGivens.add(\"Wind speed\", windSpeed);\n        darkSkyAPIStub.stubFor(get(urlEqualTo(\"/forecast/e67b0e3784104669340c3cb089412b67/51.507253,-0.127755\"))\n                .willReturn(aResponse().withBody(darkSkyResponseBody(windSpeed))));\n    }\n\n    private String darkSkyResponseBody(String windSpeed) throws IOException {\n        return format(IOUtils.toString(getClass().getClassLoader().getResourceAsStream(\"darksky-response-body.json\")), windSpeed);\n    }\n\n    @Override\n    public Iterable\u003cSpecResultListener\u003e getResultListeners() throws Exception {\n        return ImmutableSet.of(new HtmlResultRenderer().withCustomHeaderContent(getHeaderContentForModalWindows()).withCustomRenderer(SvgWrapper.class, new DontHighlightRenderer\u003c\u003e()));\n    }\n}\n````\nYou can actually click on the arrows and see the payloads.\n![Screenshot](https://raw.githubusercontent.com/wiki/bodar/yatspec/tutorial/yatspec_tutorial_sequence_diagram.PNG)\n\n##### YatSpec Tutorial Step 7: Sad path test #####\nQuite often you will want to also test more than one scenario per test. For example, a hypothetitcal situation where you wanna \nsee what happens if the third party returns an error:\n````java\n@Test\npublic void reportsErrorWhenDarkSkyReturnsANonSuccessfulResponse() throws IOException {\n    givenDarkSkyReturnsAnError(\"500\");\n    whenIRequestForecast();\n    thenTheResponseContains(\"Error while fetching data from DarkSky APIs\");\n}\n````\n![Screenshot](https://raw.githubusercontent.com/wiki/bodar/yatspec/tutorial/yatspec_tutorial_sad_path.PNG)\n##### YatSpec Tutorial Step 8: Use notes #####\nYou can use a @Notes or a [@LinkingNote](https://www.youtube.com/watch?v=CFMkD-t363c) to add notes to the test output.\n````java\n@Test\n@Notes(\"The DarkSky response is quite big and complex, out weather application extracts one attribute from it\")\npublic void servesWindSpeedBasedOnDarkSkyResponse() throws IOException {\n    givenDarkSkyForecastForLondonContainsWindSpeed(\"12.34\");\n    whenIRequestForecast();\n    thenTheWindSpeedIs(\"12.34mph\");\n}\n````\n##### YatSpec Tutorial Step 9: Create a table test #####\nSometimes you would like to test the same scenario for with different types test data. You can create a table test for that:\n````java\n@Test\n@Table({@Row(\"500\"), @Row(\"503\")})\npublic void reportsErrorWhenDarkSkyReturnsANonSuccessfulResponse(String darkSkyResponseCode) throws IOException {\n    givenDarkSkyReturnsAnError(darkSkyResponseCode);\n    whenIRequestForecast();\n    thenTheResponseContains(\"Error while fetching data from DarkSky APIs\");\n}\n```` \n##### YatSpec Tutorial Step 10: Tutorial code #####\nThe whole example is available at https://github.com/wojciechbulaty/examples/tree/master/weather-yatspec-example \n\n---\n\nMaven repo  =\u003e http://repo.bodar.com/\n\n### Java Support ###\nVersion 1.1 requires Java 7 or higher. Version 217 is the last build that supports Java 6.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbodar%2Fyatspec","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbodar%2Fyatspec","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbodar%2Fyatspec/lists"}