question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Feature Request] add automatic method-level discovery

See original GitHub issue

The biggest differentiator of Expecto is in data driven parametrized tests, where you can easily compose and generate tests without any of the awkward stuff of [x/n]unit. ([<MemberData("some-string")>] 😱 ).

For single test cases (aka xunit [<Fact>]), I prefer the method syntax, to be honest. This is also true (for me personally) in F#, but even more so in C#.

I propose to either add a new Attribute, or allow to place [<Tests>] on a method.

Now, please take the following design with a big sack of salt. I am not married to the exact details, but would like any way to simplify method-as-test.

Proposed Design:

  1. use the existing attributes ([P/F]TestsAttribute)
  2. automatically discover static generator methods which return Test or seq<Tes> in addition to properties. This would allow to take advantage of C# sequences.
  3. automatically discover all static test methods with the attribute. Automatically wrap them in TestCases of class/method. Throw if they take any parameters, or return anything except unit/void/async/Task.
  4. Do not automatically discover instance methods, but maybe add one or more helper functions to the lib. something like discoverTestMethodsInInstance : object -> Test.
public static class MyTests
{
    // automatically discovered
    [Tests]
    public static void Test()
    {
    }

    // automatically discovered
    [FTests]
    public static async Task TaskTest()
    {
        await Task.Delay(1);
    }

    // automatically discovered
    [PTests]
    public static IEnumerable<Test> TestGenerator()
    {
        yield return Runner.TestCase("1", () => { });
        yield return Runner.TestCase("2", () => { });
    }
}

public class InstanceTests
{
    string _databaseConnectionString;
    public InstanceTests(string databaseConnectionString)
    {
        _databaseConnectionString = databaseConnectionString
    }

    // NOT automatically discovered
    [Tests]
    public void InstanceTest()
    {
        // use _databaseConnectionString
    }

    [PTests]
    public static IEnumerable<Test> InstanceTestGenerator()
    {
        // use _databaseConnectionString
        yield return Runner.TestCase("1", () => { });
        yield return Runner.TestCase("2", () => { });
    }
}

class Program
{
    // this is initialized in Main, and then automatically discovered
    [Tests]
    public static Test InstanceTests { get; set; }

    public static void Main(string[] args)
    {
        // maybe I read the database connection strings from a configuration file, or ...
        InstanceTests =
            Runner.TestList("MyInstanceTests", new[] {
                Runner.DiscoverTestsInObject(new InstanceTests("some-database")),
                Runner.DiscoverTestsInObject(new InstanceTests("another-database"))
            });

        Runner.RunTestsInAssembly(Impl.ExpectoConfig.defaultConfig, args);
    }
}

Existing workaround:

Because expecto is so data-driven, I can do the same as proposed here from externally with a few helper methods.

Downside:

Currently, expecto is really simple, it just discovers all static properties, no magic. Maybe it would be better to keep it that way, and implement my proposed logic outside in helper functions.

Issue Analytics

  • State:open
  • Created 5 years ago
  • Comments:5 (2 by maintainers)

github_iconTop GitHub Comments

1reaction
Cianticcommented, Aug 6, 2019

I was so frustrated I came up my own quick an dirty solution, enjoy!

open Expecto
open System
open System.Reflection

[<Tests>]
let ``Test async version`` = async {
    do! Async.Sleep 1500
    Expect.equal true false "I fail!"
}

[<Tests>]
let ``Test discovered by the discoverer!`` () =
    Expect.equal true false "I fail!"

[<EntryPoint>]
let main argv =
    let myTests = 
        Assembly.GetExecutingAssembly().GetTypes() 
        |> Array.choose (fun t -> 
            // Create test case based on methodInfo.
            let createTestCase (mi: MethodInfo) =
                let a = Delegate.CreateDelegate(typedefof<Action>, mi) :?> Action
                testCase mi.Name a.Invoke

            // Create test case based on propertyInfo, assuming Async<unit>.
            let createTestCaseAsync (pi: PropertyInfo) =
                testCaseAsync pi.Name (pi.GetValue(null) :?> Async<unit>)

            // Get all members with TestsAttribute
            let testCases = t.GetMembers() |> Array.choose(fun (mi: MemberInfo) -> 
                mi.CustomAttributes 
                |> Seq.tryFind (fun attr -> attr.AttributeType = typeof<TestsAttribute>)
                |> Option.map (fun _ -> mi)) |> Array.choose (fun mi ->
                    match mi with 
                    | :? MethodInfo as mi -> Some (createTestCase mi)
                    | :? PropertyInfo as pi -> Some (createTestCaseAsync pi)
                    | _ -> None) |> List.ofArray
                        
            if List.isEmpty testCases 
            then None 
            else Some (testList t.Name testCases)
        ) |> List.ofArray
        
    runTestsWithArgs defaultConfig argv (testList "All tests" myTests)

Can be improved substantially of course, but for the time being it works…

0reactions
Cianticcommented, Aug 6, 2019

I was using xUnit with [<Fact>], but didn’t like the output so I switched to Expecto.

It would be helpful if one wants to migrate from xUnit to have similar behavior, just placing an attribute above module level method. (I’m using F#)

Also I loose the navigation I’m used to, I use VSCode ionide and it’s great symbol search with xUnit:

image

I think I can’t live without my symbol search working for tests.

    [<Fact>]
    let ``Test addFlag option not found`` () = ...

    [<Fact>]
    let ``Test addValue option not found`` () = ...
Read more comments on GitHub >

github_iconTop Results From Across the Web

PRTG Manual: Add an Auto-Discovery Group
Select Add Auto-Discovery Group from the context menu of the probe or group to which you want to add the new auto-discovery group....
Read more >
Administering Device Discovery
If you reject the logging level change, Cisco DCNM does not enable the feature. During Auto-Synchronization with Managed Devices. If you use ...
Read more >
How to automatically number your discovery requests … in ...
I've been searching for the best way to create auto numbering for discovery requests: dare I say in WordPerfect I had the most...
Read more >
Automatic Transaction Discovery Rules
To access the Rule Editor, double-click an Automatic Transaction Discovery rule for an app agent. Alternatively, you can create a new automatic transaction ......
Read more >
What Is a Feature Request? Definition and Examples
Feature requests can be used to decide how to iterate on an existing product. Alongside finding a channel for reaching out to the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found