Skip to content
dotnet-testing-autodata-xunit-integration logo

AutoData 屬性家族:xUnit 與 AutoFixture 的整合應用

dotnet-testing-autodata-xunit-integration

AutoFixture 與 xUnit 整合完整指南。涵蓋 AutoData、InlineAutoData、自訂 Customization 與測試資料屬性。簡化參數化測試資料準備,提升測試可讀性與維護性。 Keywords: AutoData, InlineAutoData, AutoFixture xUnit, [AutoData], [InlineAutoData], AutoDataAttribute, ICustomization, DataAttribute, 參數化測試, Theory AutoData, MemberAutoData, 測試資料屬性, fixture.Cu...

SKILL.md

Full skill instructions

AutoData 屬性家族:xUnit 與 AutoFixture 的整合應用

觸發關鍵字

  • AutoData
  • InlineAutoData
  • MemberAutoData
  • CompositeAutoData
  • xUnit AutoFixture 整合
  • 參數化測試資料
  • 測試參數注入
  • CollectionSizeAttribute
  • 外部測試資料
  • CSV 測試資料
  • JSON 測試資料

概述

AutoData 屬性家族是 AutoFixture.Xunit2 套件提供的功能,將 AutoFixture 的資料產生能力與 xUnit 的參數化測試整合,讓測試參數自動注入,大幅減少測試準備程式碼。

核心特色

  1. AutoData:自動產生所有測試參數
  2. InlineAutoData:混合固定值與自動產生
  3. MemberAutoData:結合外部資料來源
  4. CompositeAutoData:多重資料來源整合
  5. CollectionSizeAttribute:控制集合產生數量

安裝套件

<PackageReference Include="AutoFixture" Version="4.18.1" />
<PackageReference Include="AutoFixture.Xunit2" Version="4.18.1" />
dotnet add package AutoFixture.Xunit2

AutoData:完全自動產生參數

AutoData 是最基礎的屬性,自動為測試方法的所有參數產生測試資料。

基本使用

using AutoFixture.Xunit2;

public class Person
{
    public Guid Id { get; set; }
    
    [StringLength(10)]
    public string Name { get; set; } = string.Empty;
    
    [Range(18, 80)]
    public int Age { get; set; }
    
    public string Email { get; set; } = string.Empty;
    public DateTime CreateTime { get; set; }
}

[Theory]
[AutoData]
public void AutoData_應能自動產生所有參數(Person person, string message, int count)
{
    // Arrange & Act - 參數已由 AutoData 自動產生

    // Assert
    person.Should().NotBeNull();
    person.Id.Should().NotBe(Guid.Empty);
    person.Name.Should().HaveLength(10);     // 遵循 StringLength 限制
    person.Age.Should().BeInRange(18, 80);   // 遵循 Range 限制
    message.Should().NotBeNullOrEmpty();
    count.Should().NotBe(0);
}

透過 DataAnnotation 約束參數

[Theory]
[AutoData]
public void AutoData_透過DataAnnotation約束參數(
    [StringLength(5, MinimumLength = 3)] string shortName,
    [Range(1, 100)] int percentage,
    Person person)
{
    // Assert
    shortName.Length.Should().BeInRange(3, 5);
    percentage.Should().BeInRange(1, 100);
    person.Should().NotBeNull();
}

InlineAutoData:混合固定值與自動產生

InlineAutoData 結合了 InlineData 的固定值特性與 AutoData 的自動產生功能。

基本語法

[Theory]
[InlineAutoData("VIP客戶", 1000)]
[InlineAutoData("一般客戶", 500)]
[InlineAutoData("新客戶", 100)]
public void InlineAutoData_混合固定值與自動產生(
    string customerType,    // 對應第 1 個固定值
    decimal creditLimit,    // 對應第 2 個固定值
    Person person)          // 由 AutoFixture 產生
{
    // Arrange
    var customer = new Customer
    {
        Person = person,
        Type = customerType,
        CreditLimit = creditLimit
    };

    // Assert
    customer.Type.Should().Be(customerType);
    customer.CreditLimit.Should().BeOneOf(1000, 500, 100);
    customer.Person.Should().NotBeNull();
}

參數順序一致性

固定值的順序必須與方法參數順序一致:

[Theory]
[InlineAutoData("產品A", 100)]  // 依序對應 name, price
[InlineAutoData("產品B", 200)]
public void InlineAutoData_參數順序一致性(
    string name,        // 對應第 1 個固定值
    decimal price,      // 對應第 2 個固定值
    string description, // 由 AutoFixture 產生
    Product product)    // 由 AutoFixture 產生
{
    // Assert
    name.Should().BeOneOf("產品A", "產品B");
    price.Should().BeOneOf(100, 200);
    description.Should().NotBeNullOrEmpty();
    product.Should().NotBeNull();
}

⚠️ 重要限制:只能使用編譯時常數

// ✅ 正確:使用常數
[InlineAutoData("VIP", 100000)]
[InlineAutoData("Premium", 50000)]

// ❌ 錯誤:不能使用變數
private const decimal VipCreditLimit = 100000m;
[InlineAutoData("VIP", VipCreditLimit)]  // 編譯錯誤

// ❌ 錯誤:不能使用運算式
[InlineAutoData("VIP", 100 * 1000)]  // 編譯錯誤

需要使用複雜資料時,應使用 MemberAutoData。

MemberAutoData:結合外部資料來源

MemberAutoData 允許從類別的方法、屬性或欄位中獲取測試資料。

使用靜態方法

public class MemberAutoDataTests
{
    public static IEnumerable<object[]> GetProductCategories()
    {
        yield return new object[] { "3C產品", "TECH" };
        yield return new object[] { "服飾配件", "FASHION" };
        yield return new object[] { "居家生活", "HOME" };
        yield return new object[] { "運動健身", "SPORTS" };
    }

    [Theory]
    [MemberAutoData(nameof(GetProductCategories))]
    public void MemberAutoData_使用靜態方法資料(
        string categoryName,   // 來自 GetProductCategories
        string categoryCode,   // 來自 GetProductCategories
        Product product)       // 由 AutoFixture 產生
    {
        // Arrange
        var categorizedProduct = new CategorizedProduct
        {
            Product = product,
            CategoryName = categoryName,
            CategoryCode = categoryCode
        };

        // Assert
        categorizedProduct.CategoryName.Should().Be(categoryName);
        categorizedProduct.CategoryCode.Should().Be(categoryCode);
        categorizedProduct.Product.Should().NotBeNull();
    }
}

使用靜態屬性

public static IEnumerable<object[]> StatusTransitions => new[]
{
    new object[] { OrderStatus.Created, OrderStatus.Confirmed },
    new object[] { OrderStatus.Confirmed, OrderStatus.Shipped },
    new object[] { OrderStatus.Shipped, OrderStatus.Delivered },
    new object[] { OrderStatus.Delivered, OrderStatus.Completed }
};

[Theory]
[MemberAutoData(nameof(StatusTransitions))]
public void MemberAutoData_使用靜態屬性_訂單狀態轉換(
    OrderStatus fromStatus,
    OrderStatus toStatus,
    Order order)
{
    // Arrange
    order.Status = fromStatus;

    // Act
    order.TransitionTo(toStatus);

    // Assert
    order.Status.Should().Be(toStatus);
}

自訂 AutoData 屬性

建立專屬的 AutoData 配置:

DomainAutoDataAttribute

public class DomainAutoDataAttribute : AutoDataAttribute
{
    public DomainAutoDataAttribute() : base(() => CreateFixture())
    {
    }

    private static IFixture CreateFixture()
    {
        var fixture = new Fixture();

        // 設定 Person 的自訂規則
        fixture.Customize<Person>(composer => composer
            .With(p => p.Age, () => Random.Shared.Next(18, 65))
            .With(p => p.Email, () => $"user{Random.Shared.Next(1000)}@example.com")
            .With(p => p.Name, () => $"測試用戶{Random.Shared.Next(100)}"));

        // 設定 Product 的自訂規則
        fixture.Customize<Product>(composer => composer
            .With(p => p.Price, () => Random.Shared.Next(100, 10000))
            .With(p => p.IsAvailable, true)
            .With(p => p.Name, () => $"產品{Random.Shared.Next(1000)}"));

        return fixture;
    }
}

BusinessAutoDataAttribute

public class BusinessAutoDataAttribute : AutoDataAttribute
{
    public BusinessAutoDataAttribute() : base(() => CreateFixture())
    {
    }

    private static IFixture CreateFixture()
    {
        var fixture = new Fixture();

        // 設定 Order 的業務規則
        fixture.Customize<Order>(composer => composer
            .With(o => o.Status, OrderStatus.Created)
            .With(o => o.Amount, () => Random.Shared.Next(1000, 50000))
            .With(o => o.OrderNumber, () => $"ORD{DateTime.Now:yyyyMMdd}{Random.Shared.Next(1000):D4}"));

        return fixture;
    }
}

使用自訂 AutoData

[Theory]
[DomainAutoData]
public void 使用DomainAutoData(Person person, Product product)
{
    person.Age.Should().BeInRange(18, 64);
    person.Email.Should().EndWith("@example.com");
    product.IsAvailable.Should().BeTrue();
}

[Theory]
[BusinessAutoData]
public void 使用BusinessAutoData(Order order)
{
    order.Status.Should().Be(OrderStatus.Created);
    order.Amount.Should().BeInRange(1000, 49999);
    order.OrderNumber.Should().StartWith("ORD");
}

CompositeAutoData:多重資料來源整合

組合多個自訂 AutoData 配置:

public class CompositeAutoDataAttribute : AutoDataAttribute
{
    public CompositeAutoDataAttribute(params Type[] autoDataAttributeTypes) 
        : base(() => CreateFixture(autoDataAttributeTypes))
    {
    }

    private static IFixture CreateFixture(Type[] autoDataAttributeTypes)
    {
        var fixture = new Fixture();

        foreach (var attributeType in autoDataAttributeTypes)
        {
            var createFixtureMethod = attributeType.GetMethod(
                "CreateFixture",
                BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.FlattenHierarchy);

            if (createFixtureMethod != null)
            {
                var sourceFixture = (IFixture)createFixtureMethod.Invoke(null, null)!;

                foreach (var customization in sourceFixture.Customizations)
                {
                    fixture.Customizations.Add(customization);
                }
            }
        }

        return fixture;
    }
}

// 使用方式
[Theory]
[CompositeAutoData(typeof(DomainAutoDataAttribute), typeof(BusinessAutoDataAttribute))]
public void CompositeAutoData_整合多重資料來源(
    Person person,
    Product product,
    Order order)
{
    // DomainAutoData 的設定
    person.Age.Should().BeInRange(18, 64);
    product.IsAvailable.Should().BeTrue();

    // BusinessAutoData 的設定
    order.Status.Should().Be(OrderStatus.Created);
}

CollectionSizeAttribute:控制集合大小

AutoData 預設的集合大小是 3,可透過自訂屬性控制:

CollectionSizeAttribute 實作

using AutoFixture;
using AutoFixture.Kernel;
using AutoFixture.Xunit2;
using System.Reflection;

public class CollectionSizeAttribute : CustomizeAttribute
{
    private readonly int _size;

    public CollectionSizeAttribute(int size)
    {
        _size = size;
    }

    public override ICustomization GetCustomization(ParameterInfo parameter)
    {
        ArgumentNullException.ThrowIfNull(parameter);

        var objectType = parameter.ParameterType.GetGenericArguments()[0];

        var isTypeCompatible = parameter.ParameterType.IsGenericType &&
            parameter.ParameterType.GetGenericTypeDefinition()
                .MakeGenericType(objectType)
                .IsAssignableFrom(typeof(List<>).MakeGenericType(objectType));

        if (!isTypeCompatible)
        {
            throw new InvalidOperationException(
                $"{nameof(CollectionSizeAttribute)} 指定的型別與 List 不相容: " +
                $"{parameter.ParameterType} {parameter.Name}");
        }

        var customizationType = typeof(CollectionSizeCustomization<>).MakeGenericType(objectType);
        return (ICustomization)Activator.CreateInstance(customizationType, parameter, _size)!;
    }

    private class CollectionSizeCustomization<T> : ICustomization
    {
        private readonly ParameterInfo _parameter;
        private readonly int _repeatCount;

        public CollectionSizeCustomization(ParameterInfo parameter, int repeatCount)
        {
            _parameter = parameter;
            _repeatCount = repeatCount;
        }

        public void Customize(IFixture fixture)
        {
            fixture.Customizations.Add(
                new FilteringSpecimenBuilder(
                    new FixedBuilder(fixture.CreateMany<T>(_repeatCount).ToList()),
                    new EqualRequestSpecification(_parameter)));
        }
    }
}

使用 CollectionSizeAttribute

[Theory]
[AutoData]
public void CollectionSize_控制自動產生集合大小(
    [CollectionSize(5)] List<Product> products,
    [CollectionSize(3)] List<Order> orders,
    Customer customer)
{
    // Assert
    products.Should().HaveCount(5);
    orders.Should().HaveCount(3);
    customer.Should().NotBeNull();

    products.Should().AllSatisfy(product =>
    {
        product.Name.Should().NotBeNullOrEmpty();
        product.Price.Should().BeGreaterOrEqualTo(0);
    });
}

外部測試資料整合

測試專案檔案設定

在 .csproj 中設定外部資料檔案:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0</​TargetFramework>
  </​PropertyGroup>

  <ItemGroup>
    <!-- CSV 檔案 -->
    <Content Include="TestData\*.csv">
      <CopyToOutputDirectory>PreserveNewest</​CopyToOutputDirectory>
    </​Content>
    
    <!-- JSON 檔案 -->
    <Content Include="TestData\*.json">
      <CopyToOutputDirectory>PreserveNewest</​CopyToOutputDirectory>
    </​Content>
  </​ItemGroup>

  <ItemGroup>
    <PackageReference Include="CsvHelper" Version="33.0.1" />
  </​ItemGroup>
</​Project>

CSV 檔案整合

TestData/​products.csv

ProductId,Name,Category,Price,IsAvailable
1,"iPhone 15","3C產品",35900,true
2,"MacBook Pro","3C產品",89900,true
3,"AirPods Pro","3C產品",7490,false
4,"Nike Air Max","運動用品",4200,true

CSV 讀取與整合

using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;

public class ExternalDataIntegrationTests
{
    public static IEnumerable<object[]> GetProductsFromCsv()
    {
        var csvPath = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "products.csv");
        
        using var reader = new StreamReader(csvPath);
        var config = new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            HeaderValidated = null,
            MissingFieldFound = null
        };
        
        using var csv = new CsvReader(reader, config);
        var records = csv.GetRecords<ProductCsvRecord>().ToList();
        
        foreach (var record in records)
        {
            yield return new object[]
            {
                record.ProductId,
                record.Name,
                record.Category,
                record.Price,
                record.IsAvailable
            };
        }
    }

    [Theory]
    [MemberAutoData(nameof(GetProductsFromCsv))]
    public void CSV整合測試_產品驗證(
        int productId,
        string productName,
        string category,
        decimal price,
        bool isAvailable,
        Customer customer,
        Order order)
    {
        // Assert - CSV 資料
        productId.Should().BePositive();
        productName.Should().NotBeNullOrEmpty();
        category.Should().BeOneOf("3C產品", "運動用品");
        price.Should().BePositive();

        // Assert - AutoFixture 產生的資料
        customer.Should().NotBeNull();
        order.Should().NotBeNull();
    }
}

public class ProductCsvRecord
{
    public int ProductId { get; set; }
    public string Name { get; set; } = string.Empty;
    public string Category { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public bool IsAvailable { get; set; }
}

JSON 檔案整合

TestData/​customers.json

[
  {
    "customerId": 1,
    "name": "張三",
    "email": "[email protected]",
    "type": "VIP",
    "creditLimit": 100000
  },
  {
    "customerId": 2,
    "name": "李四",
    "email": "[email protected]",
    "type": "Premium",
    "creditLimit": 50000
  }
]

JSON 讀取與整合

using System.Text.Json;

public static IEnumerable<object[]> GetCustomersFromJson()
{
    var jsonPath = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "customers.json");
    var jsonContent = File.ReadAllText(jsonPath);
    
    var options = new JsonSerializerOptions
    {
        PropertyNameCaseInsensitive = true
    };
    
    var customers = JsonSerializer.Deserialize<List<CustomerJsonRecord>>(jsonContent, options)!;
    
    foreach (var customer in customers)
    {
        yield return new object[]
        {
            customer.CustomerId,
            customer.Name,
            customer.Email,
            customer.Type,
            customer.CreditLimit
        };
    }
}

[Theory]
[MemberAutoData(nameof(GetCustomersFromJson))]
public void JSON整合測試_客戶驗證(
    int customerId,
    string name,
    string email,
    string customerType,
    decimal creditLimit,
    Order order)
{
    // Assert - JSON 資料
    customerId.Should().BePositive();
    name.Should().NotBeNullOrEmpty();
    email.Should().Contain("@");
    customerType.Should().BeOneOf("VIP", "Premium", "Regular");
    creditLimit.Should().BePositive();

    // Assert - AutoFixture 產生的資料
    order.Should().NotBeNull();
}

資料來源設計模式

階層式資料組織

namespace AutoData.Tests.DataSources;

/// <summary>
/// 測試資料來源基底類別
/// </​summary>
public abstract class BaseTestData
{
    protected static string GetTestDataPath(string fileName)
    {
        return Path.Combine(Directory.GetCurrentDirectory(), "TestData", fileName);
    }
}

/// <summary>
/// 產品測試資料來源
/// </​summary>
public class ProductTestDataSource : BaseTestData
{
    public static IEnumerable<object[]> BasicProducts()
    {
        yield return new object[] { "iPhone", 35900m, true };
        yield return new object[] { "MacBook", 89900m, true };
        yield return new object[] { "AirPods", 7490m, false };
    }

    public static IEnumerable<object[]> ElectronicsProducts()
    {
        // 從 CSV 檔案讀取
        var csvPath = GetTestDataPath("electronics.csv");
        // ... 讀取邏輯
    }
}

/// <summary>
/// 客戶測試資料來源
/// </​summary>
public class CustomerTestDataSource : BaseTestData
{
    public static IEnumerable<object[]> VipCustomers()
    {
        yield return new object[] { "張三", "VIP", 100000m };
        yield return new object[] { "李四", "VIP", 150000m };
    }
}

可重用資料集

/// <summary>
/// 可重用的測試資料集
/// </​summary>
public static class ReusableTestDataSets
{
    public static class ProductCategories
    {
        public static IEnumerable<object[]> All()
        {
            yield return new object[] { "3C產品", "TECH" };
            yield return new object[] { "服飾配件", "FASHION" };
            yield return new object[] { "居家生活", "HOME" };
        }

        public static IEnumerable<object[]> Electronics()
        {
            yield return new object[] { "手機", "MOBILE" };
            yield return new object[] { "筆電", "LAPTOP" };
        }
    }

    public static class CustomerTypes
    {
        public static IEnumerable<object[]> All()
        {
            yield return new object[] { "VIP", 100000m, 0.15m };
            yield return new object[] { "Premium", 50000m, 0.10m };
            yield return new object[] { "Regular", 20000m, 0.05m };
        }
    }
}

與 Awesome Assertions 協作

[Theory]
[InlineAutoData("VIP", 100000)]
[InlineAutoData("Premium", 50000)]
[InlineAutoData("Regular", 20000)]
public void AutoData與AwesomeAssertions協作_客戶等級驗證(
    string customerLevel,
    decimal expectedCreditLimit,
    [Range(1000, 15000)] decimal orderAmount,
    Customer customer,
    Order order)
{
    // Arrange
    customer.Type = customerLevel;
    customer.CreditLimit = expectedCreditLimit;
    order.Amount = orderAmount;

    // Act
    var canPlaceOrder = customer.CanPlaceOrder(order.Amount);
    var discountRate = CalculateDiscount(customer.Type, order.Amount);

    // Assert - 使用 Awesome Assertions 語法
    customer.Type.Should().Be(customerLevel);
    customer.CreditLimit.Should().Be(expectedCreditLimit);
    customer.CreditLimit.Should().BePositive();
    
    order.Amount.Should().BeInRange(1000m, 15000m);
    canPlaceOrder.Should().BeTrue();
    discountRate.Should().BeInRange(0m, 0.3m);
}

private static decimal CalculateDiscount(string customerType, decimal orderAmount)
{
    var baseDiscount = customerType switch
    {
        "VIP" => 0.15m,
        "Premium" => 0.10m,
        "Regular" => 0.05m,
        _ => 0m
    };

    var largeOrderBonus = orderAmount > 30000m ? 0.05m : 0m;
    return Math.Min(baseDiscount + largeOrderBonus, 0.3m);
}

最佳實踐

應該做

  1. 善用 DataAnnotation

    • 在參數上使用 [StringLength]、[Range] 約束資料範圍
    • 確保產生的資料符合業務規則
  2. 建立可重用的自訂 AutoData

    • 為不同領域建立專屬的 AutoData 屬性
    • 集中管理測試資料的產生規則
  3. 使用 MemberAutoData 處理複雜資料

    • 當 InlineAutoData 無法滿足需求時使用
    • 支援變數、運算式和外部資料來源
  4. 組織測試資料來源

    • 建立階層式的資料來源結構
    • 將相關資料集中管理

應該避免

  1. 在 InlineAutoData 使用非常數值

    • InlineAutoData 只接受編譯時常數
    • 需要動態值時改用 MemberAutoData
  2. 過度複雜的 CompositeAutoData

    • 避免組合過多的 AutoData 來源
    • 保持配置的可理解性
  3. 忽略參數順序

    • InlineAutoData 的固定值必須與參數順序一致
    • 錯誤的順序會導致型別不符

程式碼範本

請參考 templates 資料夾中的範例檔案:

與其他技能的關係

  • autofixture-basics:本技能的前置知識
  • autofixture-customization:自訂化策略可用於 AutoData 屬性
  • autofixture-nsubstitute-integration:下一步學習目標
  • awesome-assertions-guide:搭配使用提升測試可讀性

參考資源

原始文章

本技能內容提煉自「老派軟體工程師的測試修練 - 30 天挑戰」系列文章:

官方文件

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View