When TDD doesn't click, something else should click "harder"!

Yaser Al-Najjar - Nov 23 '18 - - Dev Community

Earlier, @ben wrote these awesome two posts:

  1. What was your TDD aha moment?

  2. When Test-driven Development Clicks

In case you don't know what TDD is

TDD (Test Driven Development) is a way of developing software by writing tests first.

Huh... tests first?!

Heck yeah, write your tests BEFORE writing your actual code, otherwise you're not doing TDD.

Why even bother with TDD?

Uncle Bob says it all, you can't refactor your code:

you fear what you have created, you lost control of it... now it controls you; you no longer are the master!

Buuut... TDD never worked for me 😓

I tried TDD three times:

  1. Spotify-like android app
  2. University-application web app
  3. Even a demo app just to learn that damn thing!

So, I thought it's time to ask around, I've written a question on reddit before ten months: Did controller testing became ridiculously tedious?

I've learned a lot... alas, it still doesn't click 😞

Why doesn't it click?

Lemme show the three examples I've tried TDD:

Here I tried testing downloading a page using a wrapper method DownloadPage that uses the HttpClient in .NET framework internally, so what's the point of testing a method build on top of a throughly tested a framework?

public async Task<string> DownloadPage(string songName)
{
    try
    {
        using (HttpClient wClient = new HttpClient())
        {
            var queryString = new StringContent("search=" + songName, Encoding.UTF8, "application/x-www-form-urlencoded");
            Task<HttpResponseMessage> task = wClient.PostAsync(new Uri(_songsSource), queryString);
            var result = await task;
            result.EnsureSuccessStatusCode();
            if (task.Status == TaskStatus.RanToCompletion) return await result.Content.ReadAsStringAsync();
            else return ErrorOutputs.ErrorServer;
        }
    }
    catch
    {
        if (CheckInternetConnection()) return ErrorOutputs.ErrorServer;
        else return ErrorOutputs.ErrorNoInternet;
    }
}

[TestMethod]
public void DownloadPageShouldFailWhenNotHavingInternet()
{
    //Arrange
    string path = @"C:\Users\DarkOne\Downloads\test2.html";
    InternetUtils.SetSource(InternetUtils.SourceMp3Int);

    //Act
    var downloadedHtml = DownloadPage("Sia");

    //Assert
    System.IO.File.WriteAllText(path, downloadedHtml.Result);
    Assert.AreNotEqual(downloadedHtml.Result, ErrorOutputs.ErrorNoInternet);
}
Enter fullscreen mode Exit fullscreen mode

My second take was testing if a user is logged in then he can show that page, but the auth is part of the AspNetCore.Identity.EntityFrameworkCore package, so why testing something that is proven to be working (meaning you just have to write a working-code based on their documentation instead of testing it)

public class StudentController : Controller
{
    // GET: Agent/Student/Details/5
    [Route("/Agent/Student/Details/{id}")]
    public async Task<IActionResult> Details(int id)
    {
        var student = await _repository.StudentData.GetById(id);
        if (student == null)
        {
            return NotFound();
        }
        if (await _authorizationService.AuthorizeAsync(User, student, new HisOwnStudentRequirement()
        {
            return View(student.ToStudentDetailsViewModel(_environment));
        }
        return new ChallengeResult();
    }
}

[Fact]
public void ShowDataJustForHisOwn_ForAgent_Details()
{
    int studentId = 4;
    var agent = new Agent() { UserName = JunkyData.AgentsEmails[0] };
    JunkyDataManager.FixJunkyStudents(agent);

    MyMvc
        .Controller<StudentController>()
        .WithAuthenticatedUser(user =>
        {
            user.InRole(nameof(Role.Agent));
            user.WithUsername(agent.UserName);
            user.WithIdentifier(agent.UserName);
        })
        .WithDbContext(dbContext => dbContext
            .WithEntities(entities => entities.AddRange(JunkyDataManager.JunkyStudents)))
        .Calling(c => c.Details(studentId))
        .ShouldReturn()
        .View()
        .WithModelOfType<StudentDetailsViewModel>()
        .Passing(s =>
            Assert.Equal(JunkyDataManager.JunkyStudents[studentId - 1].Name, s.Name));
}
Enter fullscreen mode Exit fullscreen mode

In reddit they suggest putting my code into a service not into my controller, and test that service instead.

I simply can't, cuz the service should get glued to the framework internals to do its job... that's why!

Service or not service, with unit testing, you end up testing the framework NOT the logic!

I won't mention my third take, cuz the same thing happened again... testing the framework!


The pragmatic approach

Last time I checked Longman dictionary, it defines pragmatic (in a nice font) as:

longman

So, I will quote myself (not sure if that's valid 😁):

When someone tells the word "pragmatic approach", be sure he ain't referring to a "dirty approach".

So, what's the pragmatic approach when it comes to TDD?

We've tried it... it's BAAAD !

Pretty simple: Not doing TDD altogether and doing integration testing.

We did integration testing against our Coretabs Academy API:

https://github.com/coretabs-academy/website-v2/blob/master/tests/postman/academy_api_collection.json

But here comes the tricky part, we tested our business logic within our APIs; testing your API should just be a matter of checking the HTTP status code of your response... that's all!

(e.g: is hitting GET /workshops returning 200 OK?).


F#$k TDD, it seems NOT working, give me the solution!!

Let's keep that to the other day (another post coming soon).

Update: read second part here: https://dev.to/0xrumple/bdd-rather-than-tdd-result-oriented-testing-3n67

Opinionated tips

opinions

I should mention those to make things clear(er).

  1. TDD is great to build your own frameworks / libraries.

  2. TDD works best with DDD
    Suppose you're using MVC architectural style, where are you gonna store your business logic? model or controller?
    DDD (Domain Driven Design) suggests it's better to have rich models, meaning you're gonna test your models instead of controllers. (more on DDD on the references).

  3. Don't test the framework, test what's the purpose of the component you use.
    For example in Django, don't test if the ORM is gonna update a specific object... cuz IT SHOULD!

  4. Never UNIT test your UI (frontend)
    It's just a glue test, any change on the UI, you will update your tests. It's like testing 1 + 1 = 2 right?
    Then when you change the equation
    1 + 2 = 2... ah, failing
    1 + 2 = 3... finally passing!
    There is no point of testing such stuff.

  5. Rather than testing your UI, use a typing system in your frontend
    I know this isn't enough, but I think that's the most efficient way there is. Whether TypeScript or elm or whatever saves your time from reloading the browser to see if it works.

References

I can't sleep without putting those, I would feel guilty otherwise 😁

  1. Uncle Bob (portioned) talk about TDD: https://www.youtube.com/watch?v=OrsT94FJOQc

  2. Cover meme: https://giphy.com/gifs/CDJo4EgHwbaPS

  3. Opinion meme: https://giphy.com/gifs/discussion-fandom-opinion-5XNEIKcohVG8w

  4. DDD great course from Julie Lerman & Steve Smith in Pluralsight: https://app.pluralsight.com/library/courses/domain-driven-design-fundamentals/table-of-contents

  5. (I need to sleep, plz lemme put this as well) Longman pragmatic definition: https://www.ldoceonline.com/dictionary/pragmatic

. . . . . . . . . . . . . . . .
Terabox Video Player