TUnit icon indicating copy to clipboard operation
TUnit copied to clipboard

Migration from XUnit is painful.

Open kant2002 opened this issue 1 year ago • 10 comments

Design which requires assert to be awaitable, make migration from XUnit or MSUnit or NUnit painful.

  1. All tests now have to be async Task which is.. you know... lot of typing.

  2. That looks super strange to compare things in awaitable fashion.

Assert.Equal(new char[] { 'D' }, input.ВисокошвидкосніПрінтери);

vs

await Assert.That(new char[] { 'D' }).IsEqualTo(input.ВисокошвидкосніПрінтери);
  1. Same problem but with untyped new.
Assert.Equal(new("PRODUCT-NO", 'A'), input.Перший);
Assert.Equal(new("PRODUCT-NO", 'B'), input.Другий);

become

await Assert.That(new ПосиланняНаПоле("PRODUCT-NO", 'A')).IsEqualTo(input.Перший);
await Assert.That(new ПосиланняНаПоле("PRODUCT-NO", 'B')).IsEqualTo(input.Другий);

notice that I have to write type name.

  1. Other small details, is that you cannot use collection initializers in your asserts, thus require rewriting tests again.

Probably only XUnit related.

var input = Assert.IsType<Input>(операція);

become

await Assert.That(операція).IsTypeOf(typeof(Input));
var input = (Input)операція;
  1. Cannot compare T and T? for simple types.
Assert.Equal((ushort)10, input.Більше);

become

await Assert.That((ushort?)10).IsEqualTo(input.Більше);
  1. Ooh. I just notice. You insist on having specific order of declaration
await Assert.That('A').IsEqualTo(input.ІсходнийФайл);

is invalid, since I have to rewrite like that.

await Assert.That(input.ІсходнийФайл).IsEqualTo('A');

which is understandable, but again. I have to retype a lot of code.

  1. I don't know what's going on, but when comparing arrays I cannot use IsEqualsTo and have to use IsEquivalentTo which is not what I expect from other frameworks.
  2. When I try to compare array of tuples, it does not work. Nor IsEqualsTo nor IsEquivalentTo works for me.
await Assert.That(input.ОписПереміщення).IsEquivalentTo(new (ПосиланняНаПоле ІсходнеПоле, ПосиланняНаПоле[] ЦільовіПоля)[] {
    (new ПосиланняНаПоле("PRODUCT-NO", 'A'), [new ПосиланняНаПоле("PRODUCT-NO", 'W')]),
    (new ПосиланняНаПоле("QUANTITY", 'A'), [new ПосиланняНаПоле("QUANTITY", 'W')]),
});

Summary

Overall I think migration process even for small set of 13 tests which I have , is very painful to my taste.

kant2002 avatar Sep 21 '24 11:09 kant2002

Hi,

I really like what I have seen until now from TUnit and would like to convert our XUnit tests to TUnit. I use a lot IsType and Single, which is very helpful, because it returns the result to do further testing with it. I thought about to write own Asserts, but I don't know how to return a value I can use in the next steps. It seems it is not intended to have a result of an Assert. Is this planned for the next time?

Thanks

SaschaBa avatar Sep 21 '24 14:09 SaschaBa

@kant2002 I'm sorry that you have to make changes but it's a different framework so it's expected to have API differences. I didn't set out to copy the xUnit API exactly. So unfortunately it will be a bit of a manual task for now.

It's actually probably possible to create analyzers with code fixes to help automate the process but that will be quite a big task so it would take quite a bit of time and effort to implement.

thomhurst avatar Sep 21 '24 21:09 thomhurst

Hi,

I really like what I have seen until now from TUnit and would like to convert our XUnit tests to TUnit. I use a lot IsType and Single, which is very helpful, because it returns the result to do further testing with it. I thought about to write own Asserts, but I don't know how to return a value I can use in the next steps. It seems it is not intended to have a result of an Assert. Is this planned for the next time?

Thanks

Heya. Returning values sounds like it could be useful and doable. Leave this with me !

thomhurst avatar Sep 21 '24 21:09 thomhurst

@thomhurst I did not expect API match one-2-one, but most other frameworks, at least for smaller projects can be very easiely migrated, since a lot of things converge.

It would be hard sell to migrate to your framework for existing codebases IMO, where benefits would be move prominent. I mean, I wrote this only to give you hint on how painful would be migrate existing tests suites. I bet MsTest would be the same deal.

The more users you could steal from others, the better to shake status quo a bit 😄 .

kant2002 avatar Sep 22 '24 06:09 kant2002

@kant2002 If it helps at all, when I did this I used a few cycles of regex find and replace and it wasn't incredibly painful.

Xen0byte avatar Sep 23 '24 08:09 Xen0byte

@Xen0byte maybe it make sense to share it, even if not for my sake, but for others? What do you think. Even if it’s rough probably still would be better

kant2002 avatar Sep 23 '24 09:09 kant2002

@kant2002, unfortunately I didn't save them, it was a quick-and-dirty on-the-fly thing, but in hindsight I should have. They're pretty easy to write, it took me about 30 minutes to do an entire solution with about 2.5k tests across multiple projects. The regular expressions would look like something along the lines of the following:

find: Assert.Equal((?<expected>.*), (?<actual>.*)); replace: await Assert.That(${actual}).IsEqualTo(${expected});

NOTE 1: For assertions (example above) I would strongly recommend getting rid of the xUnit assertions library reference before you do this, and not after, because it's easier to keep track of how much you've covered and how much is still pending, by means of the compiler being unhappy.

NOTE 2: Migrating constructors to setup methods, for instance, works roughly the same way, you just need to make sure you include \n for multi-line values.

Xen0byte avatar Sep 25 '24 11:09 Xen0byte

What is the latest on this? I don't want to mimic the xUnit API, so the options are:

  • manually migrate even with the pain points
  • develop an analyzer + code fixes to help automatically convert projects where possible

Is #2 required? It obviously wouldn't get to 100% success, AND would be a substantial amount of work to implement.

thomhurst avatar Oct 10 '24 20:10 thomhurst

I think, it would be good to separate the TUnit test framework and the assertions in two separate libraries, e.g.

  • TUnit.Framework contains the testing infrastructure, but does not have any dependency or include any assertions
  • TUnit.Assertions contains the assertion library
  • when referencing TUnit itself, both the framework and assertions library are included.

This way, when a user wants to migrate from any other framework, he can first adapt the test attributes to use the TUnit framework but keep the "old" assertions, as he does not have conflicting static Assert classes. Once this step is successful, he can in a second step switch over to use the TUnit assertions (or some other framework like fluentassertions).

vbreuss avatar Oct 11 '24 06:10 vbreuss

I think, it would be good to separate the TUnit test framework and the assertions in two separate libraries, e.g.

  • TUnit.Framework contains the testing infrastructure, but does not have any dependency or include any assertions
  • TUnit.Assertions contains the assertion library
  • when referencing TUnit itself, both the framework and assertions library are included.

This way, when a user wants to migrate from any other framework, he can first adapt the test attributes to use the TUnit framework but keep the "old" assertions, as he does not have conflicting static Assert classes. Once this step is successful, he can in a second step switch over to use the TUnit assertions (or some other framework like fluentassertions).

This is already the case.

TUnit.Engine + TUnit.Assertions

thomhurst avatar Oct 11 '24 06:10 thomhurst

I had a quick look at this testing framework and gave up almost immediately because of the async tests. The thing is I have ref structs and they don't play at all with async. My suggestion would be to have non async Assert methods.

dellamonica avatar Dec 27 '24 15:12 dellamonica

I had a quick look at this testing framework and gave up almost immediately because of the async tests. The thing is I have ref structs and they don't play at all with async. My suggestion would be to have non async Assert methods.

@dellamonica I would encourage you to look at using TUnit.Engine for the testing framework and FluentAssertions or xunit.v3.assert for the assertion library. Then you can have sync test methods.

viceroypenguin avatar Dec 27 '24 15:12 viceroypenguin

I had a quick look at this testing framework and gave up almost immediately because of the async tests. The thing is I have ref structs and they don't play at all with async. My suggestion would be to have non async Assert methods.

@dellamonica I would encourage you to look at using TUnit.Engine for the testing framework and FluentAssertions or xunit.v3.assert for the assertion library. Then you can have sync test methods.

That actually went well, thanks.

dellamonica avatar Dec 27 '24 15:12 dellamonica

Here is a Copilot prompt I used to convert some test files. The improvements are obvious. The only limitation I found was that if a file was bigger than 1000 lines it would not go further.

In the current file only

Assert.IsType<T>(a) becomes Assert.That(a).IsTypeOf(typeof(T)) and should be awaited.
Assert.True(a) becomes Assert.That(a).IsTrue() and should be awaited.
Assert.Single(a) becomes Assert.That(a).HasSingleItem() and should be awaited.
Assert.Null(a) becomes await Assert.That(a).IsNull()
Assert.NotNull(a) becomes await Assert.That(a).IsNotNull()
Assert.StartsWith(a, b) becomes await Assert.That(b).StartsWith(a)
Assert.Contains(a, b) becomes await Assert.That(b).Contains(a)
Assert.Throw<T>(a) becomes await Assert.That(a).Throws<T>()

sebastienros avatar Feb 02 '25 00:02 sebastienros