Metadata-Version: 2.4
Name: speclike
Version: 0.0.0.24
Summary: Partial support library for structured testing
Author: minoru_jp
License: MIT License
        
        Copyright (c) 2025 minoru_jp
        
        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 the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
        
Project-URL: Homepage, https://github.com/minoru-jp/speclike
Project-URL: Source, https://github.com/minoru-jp/speclike
Project-URL: Issues, https://github.com/minoru-jp/speclike/issues
Keywords: spec,test,marker,pytest,structured,dbc,contract,asyncio
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: pytest
Requires-Dist: pytest>=7.0; extra == "pytest"
Dynamic: license-file

# speclike

**A helper library for pytest designed to make test code clearer and more structured.**

The following is reproduced from `__init__.py`.

```python
"""

A helper library for pytest designed to make test code clearer and more structured.

It divides tests into two conceptual types:
    - Externally defined tests  (intended for **test logic reuse** across multiple cases)
    - Individual tests

Externally defined tests are composed of two function definitions that together form a single test:
    Dispatcher:
        Describes the Arrange and Assert phases of AAA testing.  
        Serves as a kind of test logic template.
    Actor:
        Describes the Act phase.  
        It is called from within the dispatcher template and contains the code that exercises the target behavior.

The placement of these functions is as follows:
    Dispatcher:
        Defined either at the top level or inside a class inheriting from `ExSpec`.  
        Must declare the arguments given to the actor by using `@case.actsig(...)`.
    Actor:
        Defined inside a class inheriting from `Spec`.  
        Uses `@case.ex(...)` to specify its corresponding dispatcher.  
        The method name must be `"_"`.
    Individual tests:
        Defined inside a class inheriting from `Spec`.

All of these definitions use decorators provided by the `Case` class,
which also handles labeling tests (for example: `@case.feature`, `@case.edge`, `@case.error`, etc.).

Two notable helpers are skipping and parametrization.  
Parametrization is provided through `.follows`, which automatically combines
the given parameters with the function’s argument information to generate a
corresponding `pytest.mark.parametrize`.

The decorator order and interaction with unrelated decorators are not yet finalized.  
In particular, when labeling or marking a dispatcher, place `@case.actsig` **inside** other decorators.

Example:
    ```python
    @case.edge_pass.follows(-1, 0, 1)
    @case.actsig(value=int)
    def check_near_zero(act, value):
        act(value)  # expect success, no exception.
    ```

Currently, test generation only occurs for classes inheriting from `Spec`.  
The system is still under development and may be unstable, though the API is mostly settled.


AI generated sample code:

# ------------------------------------------------------------
# Test target (user_service.py)
# ------------------------------------------------------------

class UserAlreadyExistsError(Exception):
    pass

class UserService:
    '''A simple service that registers users in memory.'''

    def __init__(self):
        self._users = {}

    def register(self, username: str, email: str):
        if username in self._users:
            raise UserAlreadyExistsError(f'{username} already exists')
        if '@' not in email:
            raise ValueError('Invalid email')
        self._users[username] = email
        return {'username': username, 'email': email}

    def get_user(self, username: str):
        return self._users.get(username)

    
# ------------------------------------------------------------
# Test module
# ------------------------------------------------------------
import pytest
from speclike import Case, Spec, ExSpec
from user_service import UserService, UserAlreadyExistsError

# ============================================================
# Case instance (decorator entry point)
# ============================================================
case = Case(as_pytestmark=True)

# ============================================================
# Dispatcher definitions (Arrange + Assert)
# ============================================================
@case.feature
@case.actsig(username=str, email=str)
def register_success(act, username, email):
    '''Dispatcher for successful user registration.'''
    # Arrange
    svc = UserService()
    # Act
    result = act(svc, username, email)
    # Assert
    assert result['username'] == username
    assert result['email'] == email
    assert svc.get_user(username) == email

@case.error
@case.actsig(username=str, email=str)
def register_failures(act, username, email):
    '''Dispatcher for expected registration failures.'''
    svc = UserService()
    # Act & Assert
    with pytest.raises(Exception) as e:
        act(svc, username, email)
    # Verify the raised error type
    assert isinstance(e.value, (ValueError, UserAlreadyExistsError))


# ============================================================
# Grouping dispatchers in an ExSpec class
# ============================================================
class UserDispatchers(ExSpec):

    @case.critical
    @case.actsig(username=str)
    def duplicate_registration(self, act, username):
        '''Dispatcher to check duplicate registration scenario.'''
        svc = UserService()
        svc.register(username, 'first@example.com')
        with pytest.raises(UserAlreadyExistsError):
            act(svc, username)


# ============================================================
# Spec: defines concrete Actors (Act phase)
# ============================================================
class UserRegistration(Spec):

    @case.ex(register_success)
    def _(self, svc: UserService, username: str, email: str):
        '''Actor for successful registration.'''
        return svc.register(username, email)

    @case.ex(register_failures)
    def _(self, svc: UserService, username: str, email: str):
        '''Actor for invalid or duplicate registration.'''
        return svc.register(username, email)

    @case.ex(UserDispatchers.duplicate_registration)
    def _(self, svc: UserService, username: str):
        '''Actor for duplicate registration test.'''
        svc.register(username, 'second@example.com')

    @case.edge.follows((['foo'],), (['bar', 'baz'],))
    def test_user_list_invariant(self, usernames):
        '''Individual test to ensure invariant of user list.'''
        svc = UserService()
        for name in usernames:
            svc.register(name, f'{name}@example.com')
        assert all('@' in v for v in svc._users.values())


$ pytest -v
=========================== test session starts ===========================
collected 5 items

test_user_service.py::TestUserRegistration::test_register_success PASSED
test_user_service.py::TestUserRegistration::test_register_failures PASSED
test_user_service.py::TestUserRegistration::test_duplicate_registration PASSED
test_user_service.py::TestUserRegistration::test_user_list_invariant[foo] PASSED
test_user_service.py::TestUserRegistration::test_user_list_invariant[bar,baz] PASSED
============================ 5 passed in 0.05s ============================

"""

from speclike.speclike import Spec, ExSpec, Case, CaseBase

__all__ = [
    "Spec", "ExSpec", "CaseBase", "Case"
]

```

Installation

```bash
pip install speclike
```

---

## Status

This project is in **very early development (alpha stage)**.  
APIs and behavior may change without notice.

---

## License

MIT License © 2025 minoru_jp
