Pisac testy żeby testowac np wyrażenia regularne, czy tak sie nie robi?

0

Mam model w ktorym mam kilka wyrazen regularnych do sprawdzenia danych wprowadzanych przez uzytkownika. Nie wiem czy te regexy sa na pewnoe dobre dla kazdego przypadku i dlatego chcialem je sobie testowac. Czy to dobry pomysl zeby w testach do calego projektu umiuescic tez sprawdzenie czy napisane wyrazenie regularne zwraca poprawny wynik?

1

Jeśli są istotne z punkty widzenia działania systemu - testowałabym.

0

Nie wiem czy te regexy sa na pewnoe dobre dla kazdego przypadku

I się nie dowiesz XD Regexy ciężko ogarnąć i zawsze jakiegoś przypadku nie przewidzisz i będą bugi (zwykle są, jak się robi coś na Regexpach).

Ale to właśnie argument na tym, żeby to sprawdzać, czyli np. jak robisz walidację e-maila, żeby wpisać poprawne i niepoprawne maile*, i sprawdzać czy system zareaguje / odrzuci.

Z drugiej strony nie jestem pewien, czy to powinno być testowane na poziomie wyrażeń regularnych (wyrażenia regularne to w pewnym sensie szczegół implementacyjny), czy nie lepiej np. bardziej testować samo działanie systemu/części systemu(np. walidacji), a to, czy będzie to robione za pomocą regexpów czy inną metodą, zostawić już nieokreślone.

Bo co jeśli system się rozrasta i np. załóżmy, że robisz prostą wyszukiwarkę na stronę. Początkowo parsujesz zapytania do wyszukiwarki za pomocą regexpów, a potem się okazuje, że regexpy nie wystarczą do bardziej zaawansowanych zapytań i w końcu piszesz własny mini-parser. Albo odwrotnie - okazuje się, że nawet regexpy to overkill i robisz jakiegoś zwykłego splita na stringu.

Ew. robisz coś jeszcze innego i zamiast 1 regexpa, robisz to na 2-3 regexpach, które po kolei parsujesz i wyłuskujesz z nich po kolei dane (bo akurat tak ci wygodnie). Albo po prostu regexp jest tylko częścią operacji, bo po użyciu regexpu jeszcze wykonujesz jakieś dodatkowe operacje na tych danych, więc wtedy i tak samo przetestowanie regexpa nie wiele ci da.

tak więc jak dla mnie testowanie regexpów jako regexpów trochę jest śliskie. Moim zdaniem lepiej testować zachowanie systemu/poszczególnych jego części, a nie takie szczegóły jak regexpy.

*chociaż czytałem ostatnio, że reguły walidacji poprawności adresów e-mail są zupełnie inne, niż większość ludzi myśli. https://haacked.com/archive/2007/08/21/i-knew-how-to-validate-an-email-address-until-i.aspx/

0

Nie samo wyrażenie tylko klasę walidatora, w której tego wyrażenia używasz.

0
[somekind napisał(a)]:

Nie samo wyrażenie tylko klasę walidatora, w której tego wyrażenia używasz.

Mam te wyrażenia w modelach. Np w modelu User mam między innymi wyrażenie testujące poprawność wprowadzonego maila itp.

0

To chyba masz coś nie tak.

0

@somekind:

mam mniej więcej coś takiego:

public class User
{
 private readonly Regex UserNameRegex = new Regex(pattern);
...

 public void UserName(string username)
 {
  if(!UserNameRegex(username)
    throw new UserException(exceptionType, "Invalid user name");

...
  }

}
0

A skąd bierzesz wartość username? Z jakiegoś pola tekstowego w GUI?

0

Tak, użytkownik będzie wpisywał do pola tekstowego.

0

I tak każdą wartość oddzielnie będziesz przesyłał jako string i sprawdzał w ten sposób?

Ja bym zebrał wszystkie wartości wpisane na GUI do jakiegoś prostego DTO, a potem napisał walidator do tego DTO, który sprawdza je całe i potem zwraca od razu listę wszystkich błędów we wprowadzonych danych. Bez rzucania wyjątków, bo one nie są od tego.

0

@somekind: wzorowałem się na Gankiewiczu,
On robił w projekcie np taki model:


using System;
using System.Text.RegularExpressions;

namespace Passenger.Core.Domain
{
    public class User
    {
        private static readonly Regex NameRegex = new Regex("^(?![_.-])(?!.*[_.-]{2})[a-zA-Z0-9._.-]+(?<![_.-])$");

        public Guid Id { get; protected set; }
        public string Email { get; protected set; }
        public string Password { get; protected set; }
        public string Salt { get; protected set; }
        public string Username { get; protected set; }
        public string FullName { get; protected set; }
        public string Role { get; protected set; }
        public DateTime CreatedAt { get; protected set; }
        public DateTime UpdatedAt { get; protected set; }
    
        protected User()
        {
        }

        public User(Guid userId, string email, string username, string role, 
            string password, string salt)
        {
            Id = userId;
            SetEmail(email);
            SetUsername(username);
            SetRole(role);
            SetPassword(password, salt);
            CreatedAt = DateTime.UtcNow;
        }
        
        public void SetUsername(string username) 
        {
            if(!NameRegex.IsMatch(username))
            {
                throw new DomainException(ErrorCodes.InvalidUsername, 
                    "Username is invalid.");
            }

            if (String.IsNullOrEmpty(username))
            {
                throw new DomainException(ErrorCodes.InvalidUsername, 
                    "Username is invalid.");
            }

            Username = username.ToLowerInvariant();
            UpdatedAt = DateTime.UtcNow;
        }

        public void SetEmail(string email) 
        {
            if (string.IsNullOrWhiteSpace(email)) 
            {
                throw new DomainException(ErrorCodes.InvalidEmail, 
                    "Email can not be empty.");
            }
            if (Email == email) 
            {
                return;
            }

            Email = email.ToLowerInvariant();
            UpdatedAt = DateTime.UtcNow;
        }

        public void SetRole(string role)
        {
            if (string.IsNullOrWhiteSpace(role))
            {
                throw new DomainException(ErrorCodes.InvalidRole, 
                    "Role can not be empty.");
            }
            if (Role == role)
            {
                return;
            }
            Role = role;
            UpdatedAt = DateTime.UtcNow;
        }

        public void SetPassword(string password, string salt)
        {
            if (string.IsNullOrWhiteSpace(password))
            {
                throw new DomainException(ErrorCodes.InvalidPassword, 
                    "Password can not be empty.");
            }
            if (string.IsNullOrWhiteSpace(salt))
            {
                throw new DomainException(ErrorCodes.InvalidPassword, 
                    "Salt can not be empty.");
            }
            if (password.Length < 4) 
            {
                throw new DomainException(ErrorCodes.InvalidPassword, 
                    "Password must contain at least 4 characters.");
            }
            if (password.Length > 100) 
            {
                throw new DomainException(ErrorCodes.InvalidPassword, 
                    "Password can not contain more than 100 characters.");
            }
            if (Password == password)
            {
                return;
            }
            Password = password;
            Salt = salt;
            UpdatedAt = DateTime.UtcNow;
        }
    }
}

Jak sądzisz ten model jest zły? Nie wzorować się na tym kolesiu?

0

To jest dobry przykład, po prostu podejście obiektowe. Zamiast DTO i walidatora masz klasę, który sam nie dopuszcza do utworzenia błędnego obiektu.
Tylko nadal nie ma sensu testowanie samego regexa, testuj metodę, która go używa, czyli SetUsername.

0

tak bedę robił, dziękuje za podpowiedź
@somekind a możesz jeszcze powiedzieć coś, czy dobrze rzucać wyjątki w modelu tak jak jest to na tym przykładzie. I jeszcze jak mógłbyś rozwiąnąć myśl, że wyjątki służą do czegoś innego, byłbym wdzięczny.

0

Sorry za zwłokę w odpowiedzi, od tamtej pory jestem w rozjazdach i odpisuję na stos postów.

Gdy to pisałem miałem inny obraz problemu w głowie, tutaj wszystko jest jak najbardziej prawidłowe - mamy model User, który posiada walidację błędów i sam nie pozwala na to, aby znaleźć się w niepoprawnym stanie.

Z wyjątkami chodziło mi o niestosowanie ich do weryfikowania danych wprowadzanych przez użytkownika. Jako programiści powinniśmy spodziewać się, że użytkownik wprowadzi niepoprawne dane, więc ich pojawienie się nie jest sytuacją wyjątkową lecz normalną. (Dlatego np. można dane zebrane z GUI wsadzić do jakiegoś DTO, a potem walidować klasą przeznaczoną do tego celu, która zwraca listę komunikatów o błędach, a nie rzuca wyjątki.)

1 użytkowników online, w tym zalogowanych: 0, gości: 1